├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── LICENSE.txt ├── README-EN.md ├── README.md ├── asset └── logo.png ├── dist ├── client.js └── server.js ├── l10n ├── bundle.l10n.json └── bundle.l10n.zh-CN.json ├── package-lock.json ├── package.json ├── package.nls.json ├── package.nls.zh-CN.json ├── scripts └── build.js ├── src ├── assist.ts ├── client.ts ├── explorer.ts ├── framework.ts ├── frameworks │ ├── element-ui │ │ ├── attribute.ts │ │ ├── document.ts │ │ ├── globalAttribute.ts │ │ ├── index.ts │ │ ├── jsTag.ts │ │ └── tag.ts │ └── index.ts ├── index.vue ├── monitor.ts ├── server.ts ├── util │ ├── traverse.ts │ └── util.ts └── vue │ ├── snippets-html.ts │ └── snippets-js.ts ├── tsconfig.build-ci.json ├── tsconfig.build.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | *.tsbuildinfo 4 | *.vsix 5 | .vscode-test-web 6 | helper.json -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Client", 9 | "type": "extensionHost", 10 | "request": "launch", 11 | "runtimeExecutable": "${execPath}", 12 | "args": [ 13 | // "--disable-extensions", 14 | "--extensionDevelopmentPath=${workspaceRoot}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceRoot}/out/**/*.js" 18 | ], 19 | "preLaunchTask": { 20 | "type": "npm", 21 | "script": "dev" 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": [ 13 | "$tsc" 14 | ] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "dev", 19 | "isBackground": true, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "presentation": { 25 | "panel": "dedicated", 26 | "reveal": "never" 27 | }, 28 | "problemMatcher": [ 29 | "$tsc-watch" 30 | ] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | out 2 | scripts 3 | src 4 | tsconfig.build.json 5 | tsconfig.build.tsbuildinfo 6 | meta.json 7 | stats.html 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## version 3.5.0 2 | 1. 表单生成器位置调整问题修复 3 | 2. 表单生成器生成代码冲突问题解决 4 | 3. alt + enter生成重复方法处理 5 | 4. 增强api接口生成兼容性 6 | 7 | # version 3.0.0 8 | * 增加Pro功能 9 | * 通过swagger一键生成后端对接接口 10 | ![swagger](https://www.80fight.cn/helper/swagger.gif) 11 | 12 | # version 2.5.0 13 | 14 | 1. underlying refactoring to completely solve performance problems. 15 | 2. support [Element Plus](/document/element-plus.md) framework,247 code blocks are supported。 16 | 3. resolve issue [71](https://github.com/jiaolong1021/vue-helper/issues/71)、[64](https://github.com/jiaolong1021/vue-helper/issues/64)、 17 | [57](https://github.com/jiaolong1021/vue-helper/issues/57)、[54](https://github.com/jiaolong1021/vue-helper/issues/54)、[40](https://github.com/jiaolong1021/vue-helper/issues/40) 18 | 19 | ## release 2.4.7 notes 20 | resolve issue [14](https://github.com/jiaolong1021/vue-helper/issues/14) 21 | resolve issue [15](https://github.com/jiaolong1021/vue-helper/issues/15) 22 | resolve issue [16](https://github.com/jiaolong1021/vue-helper/issues/16) 23 | optimize block select function. 24 | 25 | ## release 2.4.6 notes 26 | add $refs tips 27 | 28 | ## release 2.4.5 notes 29 | optimize some bug. 30 | 31 | ## release 2.4.4 notes 32 | resolve issue [11](https://github.com/jiaolong1021/vue-helper/issues/11)
33 | optimize some bug. 34 | 35 | ## release 2.4.3 notes 36 | vue file support js tips 37 | 38 | ## release 2.4.0 notes 39 | optimize local file import 40 | 41 | ## release 2.3.8 notes 42 | support local file property and methods tips through showQuickFix (default ctrl + space). 43 | ![](https://common.xxpie.com/local-tips.gif) 44 | 45 | ## release 2.3.7 notes 46 | support local file property and methods tips through showQuickFix (default ctrl + space). 47 | ![](https://common.xxpie.com/local-tips.gif) 48 | 49 | ## release 2.3.6 notes 50 | support router definition jump 51 | ![](https://common.xxpie.com/router-jump.gif) 52 | 53 | ## release 2.3.5 notes 54 | optimize html block select 55 | 56 | ## release 2.3.4 notes 57 | fixed bug 58 | [issue9](https://github.com/jiaolong1021/vue-helper/issues/9) 59 | 60 | ## release 2.3.3 notes 61 | fixed bug 62 | [issue9](https://github.com/jiaolong1021/vue-helper/issues/9) 63 | 64 | ## release 2.3.2 notes 65 | alt + shift + enter to auto import 66 | bug fixed 67 | 68 | ## release 2.3.1 notes 69 | 1. support internal component tips. 70 | alt + shift + enter to auto import 71 | ![](https://common.xxpie.com/helps-internalComponent.gif) 72 | 2. support import file tips 73 | ![](https://common.xxpie.com/helps-import.gif) 74 | you can set vue-helper.componentIgnore to ignore files to search component.
75 | set vue-helper.componentPath to assign the search dir.
76 | set vue-helper.componentPrefix to replace prefix in file path. 77 | ![](https://common.xxpie.com/helps-componentSet.png) 78 | 79 | ## release 2.3.0 notes 80 | support internal component tips. 81 | ![](https://common.xxpie.com/helper-components.png) 82 | alt + shift + enter to auto import 83 | ![](https://common.xxpie.com/helper-components-auto.png) 84 | 85 | ## release 2.2.6 notes 86 | support internal component tips. 87 | ![](https://common.xxpie.com/helper-components.png) 88 | 89 | ## release 2.2.5 notes 90 | solve short cut (backspace) can not delete in seach extention, or search. 91 | 92 | ## release 2.2.4 notes 93 | optimize block selection 94 | 95 | ## release 2.2.3 notes 96 | solve short cut (backspace) can not delete in seach extention, or search. 97 | now only work in editor. 98 | 99 | ## release 2.2.0 notes 100 | 1. optimize block select function. 101 | 2. remove some unused code. 102 | 103 | ## release 2.1.16 notes 104 | optimize jump to definition 105 | 106 | ## release 2.1.15 notes 107 | solve issue #5 [definition jump feature can't jump to async function ](https://github.com/jiaolong1021/vue-helper/issues/5) 108 | 109 | ## release 2.1.14 notes 110 | optimize rem transfer to px 111 | 112 | ## release 2.1.13 notes 113 | optimize some bugs. 114 | 115 | ## release 2.1.11 notes 116 | + enhance element-ui tips. 117 | [el-timeline, el-timeline-item, el-divider, el-calendar, el-image, el-backtop, el-drawer] 118 | + object key value optimize. [alt + shift + enter] 119 | ![](https://common.xxpie.com/helper-key-value.gif) 120 | 121 | ## release 2.1.10 notes 122 | add common code snippets for vue 123 | 124 | ## release 2.1.9 notes 125 | optimize img, br complete tag 126 | 127 | ## release 2.1.8 notes 128 | remove jquery 129 | add el-table-column tips 130 | optimize block select 131 | 132 | ## release 2.1.7 notes 133 | optimize iview tag, 134 | 135 | ## release 2.1.6 notes 136 | use webpack to Bundle Extension 137 | 138 | ## release 2.1.4 notes 139 | optimize iview tag 140 | 141 | ## release 2.1.3 notes 142 | modify snippets short cut, remove begin with vg or vo. all begin with v 143 | 144 | ## release 2.1.0 notes 145 | add common code snippets for vue 146 | all begin with v 147 | | prefix | vue html snippet | 148 | | --- | --- | 149 | | vfor | v-for="(item, index) in items" :key="index" | 150 | | vcomponent | \\ | 151 | | vka | \\ | 152 | | vtransition | \\ | 153 | | vtg | \\ | 154 | | vrl | \\ | 155 | | vrlt | \\ | 156 | | vrv | \\ | 157 | 158 | | prefix | vue javascript snippet | 159 | | --- | --- | 160 | | vgsilent | Vue.config.silent = true | 161 | | vgeh | Vue.config.errorHandler = function (err, vm, info) {} | 162 | | vgwh | Vue.config.warnHandler = function (msg, vm, trace) {} | 163 | | vgextend | Vue.extend({template: template}) | 164 | | vgset | Vue.set(target, key, value) | 165 | | vgdelete | Vue.delete(target, key) | 166 | | vgdirective | Vue.directive({id, [definition]}) | 167 | | vgfilter | Vue.filter({id, [definition]}) | 168 | | vgcomponent | Vue.component({id, [definition]}) | 169 | | vgnt | Vue.nextTick({}) | 170 | | vguse | Vue.use(plugin) | 171 | | vgmixin | Vue.mixin({mixin}) | 172 | | vgcompile | Vue.compile(template) | 173 | | vodata | data() { return {} } | 174 | | vomounted | mounted () {} | 175 | | vobm | beforeMount () {} | 176 | | vocreated | created () {} | 177 | | vobc | beforeCreate () {} | 178 | | voupdated | updated () {} | 179 | | vobu | beforeUpdate () {} | 180 | | voactivated | activated () {} | 181 | | vodeactivated | deactivated () {} | 182 | | vobd | beforeDestroy () {} | 183 | | vodestroyed | destroyed () {} | 184 | | voprops | props: {} | 185 | | vopd | propsData: {} | 186 | | vocomputed | computed: {} | 187 | | vomethods | methods: {} | 188 | | vowatch | watch: {} | 189 | | vowo | key: { deep: true, immediate: true, handler: function (val, oldVal}) { } } | 190 | | vodirectives | directives: {} | 191 | | vofilters | filters: {} | 192 | | vocomponents | components: {} | 193 | | vomixins | mixins:[] | 194 | | voprovide | provide: {} | 195 | | voinject | inject: [] | 196 | | vomodel | model: {prop: '', event: ''} | 197 | | vorender | render(h) {} | 198 | | vnew | new Vue({}) | 199 | | vnt | this.$nextTick(() => {}) | 200 | | vdata | this.$data | 201 | | vprops| this.$props | 202 | | vel | this.$el | 203 | | voptions | this.$options | 204 | | vparent | this.$parent | 205 | | vroot | this.$root | 206 | | vchildren | this.$children | 207 | | vslots | this.$slots | 208 | | vss | this.$scopedSlots.default({}) | 209 | | vrefs | this.$refs | 210 | | vis | this.$isServer | 211 | | vattrs | this.$attrs | 212 | | vlisteners | this.$listeners | 213 | | vwatch | this.$watch(expOrFn, callback, [opitons]) | 214 | | vset | this.$set(target, key, value) | 215 | | vdelete | this.$delete | 216 | | von | this.$on(event, callback) | 217 | | vonce | this.$once(event, callback) | 218 | | voff | this.$off(event, callback) | 219 | | vemit | this.$emit(event, args) | 220 | | vmount | this.$mount() | 221 | | vfu | this.$forceUpdate() | 222 | | vdestroy | this.$destroy() | 223 | 224 | ## release 2.0.4 notes 225 | optimize block select function. 226 | 227 | ## release 2.0.3 notes 228 | optimize block select function. 229 | 230 | ## release 2.0.1 notes 231 | optimize tips for javascript 232 | ![](https://common.xxpie.com/helper-tips-opt.gif) 233 | tips.json 234 | ``` 235 | { 236 | "plus": { 237 | "field": { 238 | "ROUTE_SPEASKER": "" 239 | }, 240 | "method": { 241 | "getRecorder": { 242 | "params": ["url, id, styles, extras", "url, id, styles"], 243 | "returnType": "object", 244 | "return": "name" 245 | }, 246 | "get": { 247 | "params": "url, id, styles, extras", 248 | "returnType": "object", 249 | "return": "name" 250 | } 251 | } 252 | }, 253 | "name": { 254 | "field": { 255 | "hello": "" 256 | } 257 | } 258 | } 259 | ``` 260 | ### release 2.0.0 notes 261 | support tips for javascript through local json file. 262 | you can config like this: 263 | ![](https://common.xxpie.com/helper-tips-config.png) 264 | tips result like this: 265 | ![](https://common.xxpie.com/helper-tips.gif) 266 | 267 | ### release 1.7.2 notes 268 | go to definition supports auto add index path 269 | 270 | ### release 1.7.1 notes 271 | enhance go to definition in javascript file. 272 | detail config see release 1.4.2 notes 273 | 274 | ### release 1.7.0 notes 275 | 1. optimize choice for code block 276 | 2. add vue html attr select function. shortkey(**alt + x**) 277 | ![](https://common.xxpie.com/helper-choice-attr.gif) 278 | 279 | ### release 1.6.12 notes 280 | optimize write snippet 281 | 282 | ### release 1.6.11 notes 283 | optimize write snippet 284 | 285 | ### release 1.6.10 notes 286 | add snippet for pagination and regExp for phone and email [prefix with reg-] 287 | 288 | ### release 1.6.9 notes 289 | add snippets for element-ui 290 | 291 | ### release 1.6.8 notes 292 | optimize jump definition function 293 | 294 | ### release 1.6.7 notes 295 | optimize jump definition function 296 | 297 | ### release 1.6.6 notes 298 | enhance select block ability for css select. 299 | 300 | ### release 1.6.5 notes 301 | support select variable for obj and array. shortkey(**alt + x**) 302 | ![](https://common.xxpie.com/helper-variable-block.gif) 303 | 304 | ### release 1.6.4 notes 305 | optimize select function 306 | 307 | ### release 1.6.3 notes 308 | support global vue component jump 309 | 310 | ### release 1.6.2 notes 311 | fixed relative path jump. 312 | 313 | ### release 1.6.1 notes 314 | fixed relative path jump. 315 | 316 | ### release 1.6.0 notes 317 | add select block fucntion. shortkey(**alt + x**)
318 | now support function, html tag, if, for, while, json, array block select 319 | ![](https://common.xxpie.com/helper-selectBlock.gif) 320 | 321 | ### release 1.5.1 notes 322 | fix bug jump to definition with postfix 323 | 324 | ### release 1.5.0 notes 325 | exchange rem to px or exchange px to rem for all file through command 326 | ![](https://common.xxpie.com/helper-rem-file.gif) 327 | 328 | ### release 1.4.5 notes 329 | rem px exchange, shortkey (**alt + z**) 330 | ![](https://common.xxpie.com/helper-rem.gif) 331 | rem px exchange setting 332 | ![](https://common.xxpie.com/helper-rem-config.png) 333 | 334 | ### release 1.4.4 notes 335 | 1. optimize go to definition function 336 | 2. optimize alias setting. 337 | 338 | ### release 1.4.3 notes 339 | 1. support path ignore index 340 | ![](https://common.xxpie.com/helper-path-index.gif) 341 | 2. iview Input add on-search method tip 342 | 343 | ### release 1.4.2 notes 344 | support add alias through user settings. (use for jump to definition function) 345 | alias support relative path 346 | ![](https://common.xxpie.com/helper-setting-alias.png) 347 | support iview, element-ui tag jump to definition 348 | ![](https://common.xxpie.com/helper-iview-jump.gif) 349 | 350 | ### release 1.4.1 notes 351 | solve issue: 352 | [#2 Move to Definition function does not work](https://github.com/jiaolong1021/vue-helper/issues/2) 353 | 354 | ### release 1.4.0 notes 355 | jump to definition function support self define component. 356 | not supoort global component, must import by import or require. 357 | the jump path support begin with @ and relation path 358 | ![](https://common.xxpie.com/helper-definition-file-jump.gif) 359 | 360 | ### release 1.3.10 notes 361 | optimize jump to definition function 362 | 363 | ### release 1.3.9 notes 364 | add iview page snippets. iv-page, iv-page-data, iv-page-method 365 | 366 | ### release 1.3.8 notes 367 | close tag support tag attributu name include . 368 | 369 | ### release 1.3.7 notes 370 | 1. close tag support tag attributu name include : or @ 371 | 2. iview split, cell, divider snippet. 372 | 373 | ### release 1.3.6 notes 374 | close tag support tag attributu name include : or @ 375 | 376 | ### release 1.3.5 notes 377 | add iview icon tip, optimize close tag function 378 | 379 | ### release 1.3.4 notes 380 | add iview modal snippets 381 | 382 | ### release 1.3.3 notes 383 | add iview form, rules snippets 384 | 385 | ### release 1.3.2 notes 386 | 1. optimize go to definition method 387 | 2. add iview form code snippets. 388 | ![](https://common.xxpie.com/helper-iv-form.gif) 389 | 390 | ### release 1.3.0 notes 391 | property or method go to definiton in current page (keyword: cmd(mac) | ctrl(win)) 392 | ![](https://common.xxpie.com/helper-go-to-define.gif) 393 | 394 | ### release 1.2.1 notes 395 | optimize backspace handle 396 | 397 | ### release 1.2.0 notes 398 | 1. now support element tag see document through hover. 399 | ![](https://common.xxpie.com/helper-element-tag.gif) 400 | 2. enhance tag close function 401 | ![](https://common.xxpie.com/helper-tag-slip.gif) 402 | 3. fix bugs 403 | 404 | ### release 1.1.8 notes 405 | 1. optimize attribute hover 406 | 2. add line empty delete funciton(enter backspace key). 407 | ![](https://common.xxpie.com/helper-line-delete.gif) 408 | 409 | ### release 1.1.5 notes 410 | optimize close tag function、 {{}} function, add change log. 411 | 412 | ### release 1.1.4 notes 413 | optimize columns tip and message snippet show 414 | 415 | ### 1.1.3 416 | --- 417 | auto close tag optinize, table columns attribute tips 418 | ![](https://common.xxpie.com/helper-columns.gif) 419 | ![](https://common.xxpie.com/helper-columns2.gif) 420 | 421 | ### version 1.1.2 422 | #### 1. vue hook function tip 423 | ![](https://common.xxpie.com/helper-hook-function.gif) 424 | #### 2. method completion snippets (keyboard shortcut: alt + shift + enter) 425 | ![](https://common.xxpie.com/helper-methods.gif) 426 | #### 3. autoclose html tag 427 | ![](https://common.xxpie.com/helper-autoclose.gif) 428 | #### 4. {{}} completion in vue template 429 | ![](https://common.xxpie.com/helper-{{}}.gif) 430 | 431 | ### version 1.0.0 432 | ## basic functions introduce 433 | --- 434 | ### 1. see document detail through hover tag (**now only support iview**) 435 | ![](https://common.xxpie.com/helper-document.gif) 436 | 437 | ### 2. edit through tag name (friendly tip tag name about framework element-uivuxiview) 438 | ![](https://common.xxpie.com/helper-tag.gif) 439 | 440 | ### 3. tag attribute tip 441 | ![](https://common.xxpie.com/helper-attr.gif) 442 | 443 | ### 4. method tip (tip begin: element -> el-、iview -> iv-) 444 | ![](https://common.xxpie.com/helper-method.gif) 445 | 446 | ### questions feedback 447 | if you has any questions or good idea, you can feedback through issue. 448 | 449 | **Enjoy!** -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present SHENJIAOLONG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - present SHENJIAOLONG Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | # vue-helper 2 | 3 |
4 | aim to create a full-link efficiency improvement development tool for VUE development 5 |
6 | 7 | Enhance the VUE development experience. support framework Element-UIElement-PlusAnt Design Vue. Enhance your Vue-development experience with visualizations, foundational templates, and rich materials. 8 | 9 | ## Getting Started 10 | Please follow the documentation at [vue-helper](http://vue-helper.80fight.cn/)! 11 | 12 | ## version 3.5.0 13 | 1. jump compatible vetur, Vue-Official 14 | 2. api generate support swagger admin 15 | 3. Variable generation shortcut key is replaced with[alt + enter], you can change it by yourself. [detail](http://vue-helper.80fight.cn/en/document/pro/mv.html) 16 | 4. to solve the problem generate variables with multiple touches 17 | 18 | ## Features 19 | ### 1、framework support element-ui、element-plus、ant-design-vue 20 |

1.1、code block generate

21 | 22 |

1.2、attribute tip

23 | 24 |

1.3、docuement see, or go official

25 | 26 |

2、go to definition of method、variable

27 | 28 |

3、component jump

29 | 30 |

4、expand select [alt + x]

31 | 32 |

5、enhance [alt + enter]

33 | 34 | 35 | more detail see document [vue-helper](http://vue-helper.80fight.cn/)! 36 | 37 | **Enjoy it!** 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *# vue-helper 2 | 3 | [English](https://github.com/jiaolong1021/vue-helper/blob/HEAD/README-EN.md) 4 | 5 |
6 | 旨在打造vue开发的全链路提效开发工具 7 |
8 | 9 | 增强vue开发体验,支持 Element-UIElement-PlusAnt Design Vue。通过可视化、基础模板、丰富物料增强 vue 开发。 10 | 11 | ## 好物推荐 ✨✨✨ 12 | * AI 编码助手: [阿里-通义灵码](https://developer.aliyun.com/topic/lingma/september?taskCode=18486&recordId=1627dde5f632a75ff7177759fffb99a0) 13 | * 豆包助手: [字节跳动-MarsCode](https://www.marscode.cn/events/s/iSDf7sB6/) 14 | * 创意白板: [博思白板](https://boardmix.cn/invitation/invitee/?code=P6cgeIlptlVu) 15 | 16 | ## vue-helper 能让你获得什么? 17 | vue-helper能让你的开发效率提高30%到100%。 18 | 19 | ## 如何提高效率? 20 | vue-helper具有大量功能,所以想掌握好每个好用的功能,都必须实战才行。 21 | 22 | ## 视频教程 23 | [bilibili地址](https://space.bilibili.com/179666728/channel/seriesdetail?sid=4376604&ctype=0) 24 | 25 | ## 快速开始 26 | 请查看文档 [vue-helper](http://vue-helper.80fight.cn/)! 27 | 28 | ## version 4.2.7 29 | 1. 自定义框架提示 30 | 31 | 2. 打造个人知识库 32 | 33 | 3. 组件导入优化 34 | 4. 性能优化 35 | 5. fixed: [101](https://github.com/jiaolong1021/vue-helper/issues/101) 36 | 37 | ## 功能特性 38 | ### 1、框架支持 element-ui、element-plus、ant-design-vue 39 |

1.1、组件代码块生成

40 | 41 |

1.2、属性提示

42 | 43 |

1.3、文档查看,官网跳转

44 | 45 |

2、变量、方法快速定位

46 |

当你还在使用vue2的时候,这个功能绝对对你有用。
当你在变量或者方法上按住ctrl键时,能够自动定位到相应的定义变量/方法上。

47 | 48 |

3、全局组件跳转

49 |

在工程项目中的vue文件、全局组件等外部组件都支持跳转。

50 | 51 |

4、扩选 [alt + x]

52 |

通过快捷键 [**alt + x**] 扩选代码范围,支持属性、标签、对象、函数扩选

53 | 54 |

5、增强 [alt + enter]

55 | 56 | **Enjoy it!** 57 | * -------------------------------------------------------------------------------- /asset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiaolong1021/vue-helper/cd22be4854a2f5e5c10c6f53d4a96e8b7ca07261/asset/logo.png -------------------------------------------------------------------------------- /dist/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // out/server.js 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | exports.activate = void 0; 6 | function activate(context) { 7 | console.log("vue-helper activate", context.extension.id); 8 | } 9 | exports.activate = activate; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-helper", 3 | "displayName": "vue-helper", 4 | "version": "4.2.2", 5 | "description": "vue enhance, extension for Element-UI, Element Plus, Ant Desigin Vue", 6 | "keywords": [ 7 | "vue-helper", 8 | "element-ui", 9 | "Element Plus", 10 | "Ant", 11 | "Ant Desigin Vue", 12 | "vue", 13 | "vscode" 14 | ], 15 | "icon": "asset/logo.png", 16 | "publisher": "shenjiaolong", 17 | "author": "shenjiaolong", 18 | "homepage": "http://vue-helper.80fight.cn/", 19 | "license": "MIT", 20 | "main": "dist/client", 21 | "l10n": "./l10n", 22 | "directories": { 23 | "lib": "lib", 24 | "test": "__tests__" 25 | }, 26 | "publishConfig": { 27 | "registry": "https://registry.npm.taobao.org/" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/jiaolong1021/vue-helper.git" 32 | }, 33 | "gitHead": "b1cc40df44b9cd73ba96d8b7f0369cc3ce690c45", 34 | "scripts": { 35 | "dev": "concurrently \"npm:dev-tsc\" \"npm:dev-build\"", 36 | "dev-tsc": "tsc -b tsconfig.build.json -w", 37 | "dev-build": "node scripts/build -- --watch", 38 | "build": "node scripts/build -- --minify", 39 | "release": "npm run build && npm run publish", 40 | "publish": "vsce package", 41 | "test": "echo \"Error: run tests from root\" && exit 1" 42 | }, 43 | "engines": { 44 | "vscode": "^1.74.0" 45 | }, 46 | "categories": [ 47 | "Snippets", 48 | "Other" 49 | ], 50 | "activationEvents": [ 51 | "onStartupFinished", 52 | "onLanguage:vue" 53 | ], 54 | "contributes": { 55 | "commands": [ 56 | { 57 | "command": "vue-helper.backspace", 58 | "title": "vue-helper: backspace" 59 | }, 60 | { 61 | "command": "vue-helper.blockSelect", 62 | "title": "vue-helper: blockSelect" 63 | }, 64 | { 65 | "command": "vue-helper.funcEnhance", 66 | "title": "vue-helper: funcEnhance" 67 | }, 68 | { 69 | "command": "vue-helper.login", 70 | "title": "vue-helper.login" 71 | }, 72 | { 73 | "command": "vue-helper.generateConfig", 74 | "title": "vue-helper.generateConfig" 75 | }, 76 | { 77 | "command": "vue-helper.token.clear", 78 | "title": "vue-helper.token.clear" 79 | }, 80 | { 81 | "command": "vue-helper.form", 82 | "title": "%extension.form%" 83 | }, 84 | { 85 | "command": "vue-helper.upload.block", 86 | "title": "%extension.block%" 87 | }, 88 | { 89 | "command": "vue-helper.common.position", 90 | "title": "%common.position%", 91 | "icon": "$(eye)" 92 | }, 93 | { 94 | "command": "vue-helper.common.link", 95 | "title": "%common.link%", 96 | "icon": "$(link)" 97 | }, 98 | { 99 | "command": "vue-helper.common.refresh", 100 | "title": "%common.refresh%", 101 | "icon": "$(refresh)" 102 | }, 103 | { 104 | "command": "vue-helper.add.component", 105 | "title": "%add.component%" 106 | }, 107 | { 108 | "command": "vue-helper.add.page", 109 | "title": "%add.page%" 110 | }, 111 | { 112 | "command": "vue-helper.upload.component", 113 | "title": "%upload.component%" 114 | }, 115 | { 116 | "command": "vue-helper.add.file", 117 | "title": "%add.file%" 118 | } 119 | ], 120 | "views": { 121 | "explorer": [ 122 | { 123 | "id": "vue-helper.common", 124 | "name": "%extension.common%", 125 | "icon": "$(json)", 126 | "visibility": "hidden", 127 | "contextualTitle": "vue-helper", 128 | "when": "vue-helper.pro" 129 | } 130 | ] 131 | }, 132 | "menus": { 133 | "editor/context": [ 134 | { 135 | "command": "vue-helper.form", 136 | "when": "!inOutput && vue-helper.pro", 137 | "group": "navigation" 138 | }, 139 | { 140 | "command": "vue-helper.upload.block", 141 | "when": "!inOutput && vue-helper.pro", 142 | "group": "navigation" 143 | } 144 | ], 145 | "view/item/context": [ 146 | { 147 | "command": "vue-helper.common.position", 148 | "group": "inline", 149 | "when": "view == vue-helper.common && viewItem == vue-helper.common" 150 | }, 151 | { 152 | "command": "vue-helper.common.link", 153 | "group": "inline", 154 | "when": "view == vue-helper.common && viewItem == vue-helper.common" 155 | } 156 | ], 157 | "view/title": [ 158 | { 159 | "command": "vue-helper.common.refresh", 160 | "group": "navigation", 161 | "when": "view == vue-helper.common" 162 | } 163 | ], 164 | "explorer/context": [ 165 | { 166 | "command": "vue-helper.add.component", 167 | "group": "0_vue-helper", 168 | "when": "vue-helper.pro" 169 | }, 170 | { 171 | "command": "vue-helper.add.page", 172 | "group": "0_vue-helper", 173 | "when": "explorerResourceIsFolder && vue-helper.pro" 174 | }, 175 | { 176 | "command": "vue-helper.add.file", 177 | "group": "0_vue-helper", 178 | "when": "!explorerResourceIsFolder && vue-helper.self" 179 | }, 180 | { 181 | "command": "vue-helper.upload.component", 182 | "group": "1_vue-helper", 183 | "when": "vue-helper.pro" 184 | } 185 | ] 186 | }, 187 | "keybindings": [ 188 | { 189 | "key": "alt+x", 190 | "command": "vue-helper.blockSelect", 191 | "when": "editorTextFocus" 192 | }, 193 | { 194 | "key": "alt+enter", 195 | "command": "vue-helper.funcEnhance", 196 | "when": "editorTextFocus" 197 | }, 198 | { 199 | "key": "backspace", 200 | "command": "vue-helper.backspace", 201 | "when": "vue-helper.backspace && textInputFocus && editorLangId =~ /^vue$|^typescript$|^javascript$|^html$|^json$|^css$|^scss$/" 202 | } 203 | ], 204 | "configuration": { 205 | "title": "vue-helper", 206 | "type": "object", 207 | "properties": { 208 | "vue-helper.use-vue-snippets": { 209 | "type": "boolean", 210 | "default": true 211 | }, 212 | "vue-helper.show-new-after-upgrade": { 213 | "type": "boolean", 214 | "default": true 215 | }, 216 | "vue-helper.config-auto-generate": { 217 | "type": "boolean", 218 | "default": true 219 | }, 220 | "vue-helper.component-dir": { 221 | "type": "string", 222 | "default": "" 223 | }, 224 | "vue-helper.hub": { 225 | "type": "string", 226 | "default": "" 227 | }, 228 | "vue-helper.framework": { 229 | "type": "string", 230 | "default": "" 231 | } 232 | } 233 | } 234 | }, 235 | "devDependencies": { 236 | "@types/node": "^20.12.2", 237 | "@types/uuid": "^9.0.0", 238 | "@types/vscode": "^1.74.0", 239 | "@vscode/l10n-dev": "^0.0.35", 240 | "axios": "^0.21.1", 241 | "camelcase": "^7.0.1", 242 | "concurrently": "^8.2.2", 243 | "esbuild": "0.15.18", 244 | "execa": "^6.1.0", 245 | "js-beautify": "^1.15.1", 246 | "jsonc-parser": "^3.2.0", 247 | "jsonwebtoken": "^9.0.2", 248 | "opn": "^6.0.0", 249 | "uuid": "^9.0.0", 250 | "vsce": "latest" 251 | }, 252 | "dependencies": { 253 | "@vscode/l10n": "^0.0.18", 254 | "glob": "^10.3.12" 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "extentions.blockSelect": "vue-helper: blockSelect" 3 | } -------------------------------------------------------------------------------- /package.nls.zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "extentions.blockSelect": "vue-helper: 块选择" 3 | } -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | require('esbuild').build({ 5 | entryPoints: { 6 | client: './out/client.js', 7 | server: './out/server.js', 8 | }, 9 | bundle: true, 10 | metafile: process.argv.includes('--metafile'), 11 | outdir: './dist', 12 | external: [ 13 | 'vscode', 14 | 'typescript', // vue-component-meta 15 | ], 16 | format: 'cjs', 17 | platform: 'node', 18 | tsconfig: 'tsconfig.build.json', 19 | define: { 'process.env.NODE_ENV': '"production"' }, 20 | minify: process.argv.includes('--minify'), 21 | watch: process.argv.includes('--watch'), 22 | plugins: [ 23 | { 24 | name: 'umd2esm', 25 | setup(build) { 26 | build.onResolve({ filter: /^(vscode-.*|estree-walker|jsonc-parser)/ }, args => { 27 | const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] }) 28 | // Call twice the replace is to solve the problem of the path in Windows 29 | const pathEsm = pathUmdMay.replace('/umd/', '/esm/').replace('\\umd\\', '\\esm\\') 30 | return { path: pathEsm } 31 | }) 32 | }, 33 | }, 34 | ], 35 | }).then(() => { 36 | }).catch(() => process.exit(1)) 37 | -------------------------------------------------------------------------------- /src/assist.ts: -------------------------------------------------------------------------------- 1 | import { commands, window, Position, TextEditor, Selection, workspace, Range } from 'vscode' 2 | import ExplorerProvider from './explorer' 3 | 4 | export default class Assist { 5 | private explorer: ExplorerProvider 6 | 7 | constructor(explorer: ExplorerProvider) { 8 | this.explorer = explorer 9 | } 10 | 11 | public register() { 12 | this.explorer.context.subscriptions.push(commands.registerCommand('vue-helper.blockSelect', () => { 13 | this.blockSelect() 14 | })) 15 | this.explorer.context.subscriptions.push(commands.registerCommand('vue-helper.funcEnhance', () => { 16 | this.funcEnhance() 17 | })) 18 | this.explorer.context.subscriptions.push(commands.registerCommand('vue-helper.backspace', () => { 19 | this.backspce() 20 | })) 21 | this.explorer.setContext('vue-helper.backspace', true) 22 | } 23 | 24 | asNormal(key: string, modifiers?: string) { 25 | switch (key) { 26 | case 'enter': 27 | if (modifiers === 'ctrl') { 28 | return commands.executeCommand('editor.action.insertLineAfter'); 29 | } else { 30 | return commands.executeCommand('type', { source: 'keyboard', text: '\n' }); 31 | } 32 | case 'tab': 33 | if (workspace.getConfiguration('emmet').get('triggerExpansionOnTab')) { 34 | return commands.executeCommand('editor.emmet.action.expandAbbreviation'); 35 | } else if (modifiers === 'shift') { 36 | return commands.executeCommand('editor.action.outdentLines'); 37 | } else { 38 | return commands.executeCommand('tab'); 39 | } 40 | case 'backspace': 41 | return commands.executeCommand('deleteLeft'); 42 | } 43 | } 44 | 45 | // 回退删除处理 46 | async backspce() { 47 | let editor: any = window.activeTextEditor; 48 | if(!editor) { 49 | this.asNormal('backspace'); 50 | return; 51 | } 52 | // 多选择点删除处理 53 | if(window.activeTextEditor?.selections.length && window.activeTextEditor?.selections.length > 1) { 54 | let selections = window.activeTextEditor?.selections; 55 | let selectionList: Array = []; 56 | for (let index = 0; index < selections.length; index++) { 57 | const selection = selections[index]; 58 | if(selection.start.line === selection.end.line && selection.start.character === selection.end.character) { 59 | if(selection.anchor.character > 0) { 60 | selectionList.push(new Selection(new Position(selection.anchor.line, selection.anchor.character - 1), selection.anchor)); 61 | } else if (selection.anchor.line > 0) { 62 | let len = editor.document.lineAt(selection.anchor.line - 1).text.length; 63 | selectionList.push(new Selection(new Position(selection.anchor.line - 1, len), selection.anchor)); 64 | } 65 | } else { 66 | selectionList.push(selection); 67 | } 68 | } 69 | await editor.edit((editBuilder: any) => { 70 | for (let i = 0; i < selectionList.length; i++) { 71 | editBuilder.delete(selectionList[i]); 72 | } 73 | }); 74 | return; 75 | } 76 | if(window.activeTextEditor?.selection.start.line === window.activeTextEditor?.selection.end.line 77 | && window.activeTextEditor?.selection.start.character === window.activeTextEditor?.selection.end.character) { 78 | // 首行 79 | if(editor.selection.anchor.line === 0) { 80 | if(editor.selection.anchor.character > 0) { 81 | await editor.edit((editBuilder: any) => { 82 | editBuilder.delete(new Selection(new Position(editor.selection.anchor.line, editor.selection.anchor.character - 1), editor.selection.anchor)); 83 | }); 84 | } 85 | 86 | } else { 87 | let isLineEmpty = editor.document.lineAt(editor.selection.anchor.line).text.trim() === ''; 88 | // 整行都是空格 89 | if(isLineEmpty) { 90 | let preText = ''; 91 | let line = editor.selection.anchor.line; 92 | while(preText.trim() === '' && line >= 0) { 93 | line -= 1; 94 | preText = editor.document.lineAt(line).text; 95 | } 96 | await editor.edit((editBuilder: any) => { 97 | editBuilder.delete(new Selection(new Position(line, preText.length), editor.selection.anchor)); 98 | }); 99 | } else { 100 | let startPosition: Position; 101 | let endPosition: Position = editor.selection.anchor; 102 | let preLineText = editor.document.getText(new Range(new Position(endPosition.line, 0), endPosition)); 103 | if(endPosition.character === 0 || preLineText.trim() === '') { 104 | startPosition = new Position(endPosition.line - 1, editor.document.lineAt(endPosition.line - 1).text.length); 105 | } else { 106 | startPosition = new Position(endPosition.line, endPosition.character - 1); 107 | // 对{}, (), [], '', "", <>进行成对删除处理 108 | let txt = editor.document.getText(new Range(new Position(endPosition.line, endPosition.character - 1), endPosition)); 109 | if(editor.document.lineAt(endPosition.line).text.length > endPosition.character) { 110 | let nextTxt = editor.document.getText(new Range(endPosition, new Position(endPosition.line, endPosition.character + 1))); 111 | if((txt === '{' && nextTxt === '}') 112 | || (txt === '(' && nextTxt === ')') 113 | || (txt === '\'' && nextTxt === '\'') 114 | || (txt === '"' && nextTxt === '"') 115 | || (txt === '[' && nextTxt === ']') 116 | || (txt === '<' && nextTxt === '>')) { 117 | endPosition = new Position(endPosition.line, endPosition.character + 1); 118 | } 119 | } 120 | } 121 | await editor.edit((editBuilder: any) => { 122 | editBuilder.delete(new Selection(startPosition, endPosition)); 123 | }); 124 | } 125 | } 126 | } else { 127 | // 选择块 128 | this.asNormal('backspace'); 129 | } 130 | } 131 | 132 | // 函数增强 133 | public funcEnhance() { 134 | let editor = window.activeTextEditor; 135 | if (!editor) { return; } 136 | let character = (window.activeTextEditor?.selection.anchor.character || 0) - 1; 137 | let txt = window.activeTextEditor?.document.lineAt(window.activeTextEditor?.selection.anchor.line).text; 138 | let word: string = ''; 139 | let isWordEnd = false 140 | let type = '' // '0': 变量 '1': 函数 141 | if (txt && txt.includes('"')) { 142 | type = '0' 143 | } 144 | while(txt && character && character > 0) { 145 | let wordSingle = txt[character] 146 | if (wordSingle === '"') { 147 | type = '0' 148 | isWordEnd = true 149 | } 150 | if (wordSingle === ' ') { 151 | break 152 | } 153 | if (!isWordEnd) { 154 | word = wordSingle + word; 155 | } 156 | if (wordSingle === '@') { 157 | type = '1' 158 | } 159 | --character; 160 | } 161 | // 没有参数往后找 162 | character = (window.activeTextEditor?.selection.anchor.character || 0); 163 | while(txt && character && txt.length > character) { 164 | if (txt[character] === '"') { 165 | break; 166 | } 167 | word = word + txt[character]; 168 | ++character; 169 | } 170 | // 如果是函数引用方式,则认为是生成函数 171 | if (word.includes(')')) { 172 | type = '1' 173 | } 174 | console.log('type' + type) 175 | 176 | switch (type) { 177 | default: 178 | this.autoEnhance() 179 | break; 180 | } 181 | } 182 | 183 | public autoEnhance() { 184 | let editor: any = window.activeTextEditor; 185 | if (!editor) { return; } 186 | let txt = editor.document.lineAt(editor.selection.anchor.line).text; 187 | if(editor.document.lineCount <= editor.selection.anchor.line + 1) { return; } 188 | // 组件自动导入 189 | if (/<.*>\s?<\/.*>/gi.test(txt.trim()) || /<.*\/>/gi.test(txt.trim())) { 190 | this.autoImportComponent(txt, editor, editor.selection.anchor.line); 191 | return; 192 | } 193 | // 本地文件自动导入 194 | let nextLineTxt = editor.document.lineAt(editor.selection.anchor.line + 1).text; 195 | 196 | let baseEmpty = txt.replace(/(\s)\S.*/gi, '$1'); 197 | let replaceTxt = ` {\n${baseEmpty}${this.explorer.tabSize}\n${baseEmpty}}`; 198 | // 本行全是空 199 | if(/^\s*$/gi.test(txt) || txt === '') { 200 | replaceTxt = 'name (params)' + replaceTxt; 201 | } else if (/[0-9a-zA-Z]\s{0,1}:\s{0,1}[\w\"\']/gi.test(txt)) { 202 | // key: value 203 | replaceTxt = ',\n' + baseEmpty; 204 | } else if(txt.indexOf(')') === -1) { 205 | replaceTxt = ' (params)' + replaceTxt; 206 | } 207 | // 判断下一行是否是单行注释 208 | if(/\s*\/\/\s+.*/gi.test(nextLineTxt)) { 209 | if(editor.document.lineCount <= editor.selection.anchor.line + 2) { return; } 210 | nextLineTxt = editor.document.lineAt(editor.selection.anchor.line + 2).text; 211 | } 212 | // 下一行是一个函数 213 | if (/.*(.*).*{.*/gi.test(nextLineTxt)) { 214 | let isCond = false; 215 | let txtTrim = txt.trim(); 216 | const condList = ['if', 'for', 'while', 'switch']; 217 | condList.forEach(cond => { 218 | if (txtTrim.indexOf(cond) === 0) { 219 | isCond = true; 220 | } 221 | }); 222 | if (!isCond) { 223 | replaceTxt += ','; 224 | } 225 | } 226 | editor.edit((editBuilder: any) => { 227 | editBuilder.insert(new Position(editor.selection.anchor.line, txt.length + 1), replaceTxt); 228 | }); 229 | let newPosition = editor.selection.active.translate(1, (baseEmpty + this.explorer.tabSize).length); 230 | editor.selection = new Selection(newPosition, newPosition); 231 | } 232 | 233 | // 组件自动导入 234 | autoImportComponent(txt: string, editor: TextEditor, line: number) { 235 | let tag = txt.trim().replace(/<([\w-]*)[\s>].*/gi, '$1'); 236 | for (let i = 0; i < this.explorer.vueFiles.length; i++) { 237 | const vf : any = this.explorer.vueFiles[i]; 238 | if (tag === vf.name) { 239 | let name = vf.name; 240 | // 不重复插入引入 241 | if (editor.document.getText().includes(`import ${name}`)) { 242 | return 243 | } 244 | let countLine = editor.document.lineCount; 245 | // 找script位置 246 | while (!/^\s*\s*$/.test(editor.document.lineAt(line).text)) { 247 | if (countLine > line) { 248 | line++; 249 | } else { 250 | break; 251 | } 252 | } 253 | let activeEditorPath = this.explorer.getActiveEditorDir(editor.document.uri.path) 254 | let importString = `import ${name} from '${this.explorer.getVueRelativePath(activeEditorPath, vf.path)}'\n`; 255 | if (editor.document.lineAt(line).text.includes('setup')) { 256 | // 组合式 257 | editor.edit((editBuilder) => { 258 | importString = importString.replace(/\\/gi, '/'); 259 | editBuilder.insert(new Position(line + 1, 0), importString); 260 | }); 261 | return 262 | } 263 | if (editor.document.lineAt(line + 1).text.includes('export default')) { 264 | line += 1; 265 | } else { 266 | line += 1; 267 | if (countLine < line) { 268 | return; 269 | } 270 | // 找import位置 271 | while (/import /gi.test(editor.document.lineAt(line).text.trim())) { 272 | if (countLine > line) { 273 | line++; 274 | } else { 275 | break; 276 | } 277 | } 278 | } 279 | let importLine = line; 280 | if (line < countLine) { 281 | let prorityInsertLine = 0; 282 | let secondInsertLine = 0; 283 | let hasComponents = false; 284 | let baseEmpty = ''; 285 | while(!/\s*<\/script>\s*/gi.test(editor.document.lineAt(line).text.trim())) { 286 | if (/\s*components\s*:\s*{.*}.*/gi.test(editor.document.lineAt(line).text.trim())) { 287 | let text = editor.document.lineAt(line).text; 288 | let preText = text.replace(/\s*}.*$/, ''); 289 | let insertPos = preText.length; 290 | editor.edit((editBuilder) => { 291 | importString = importString.replace(/\\/gi, '/'); 292 | editBuilder.insert(new Position(importLine, 0), importString); 293 | editBuilder.insert(new Position(line, insertPos), ', ' + name); 294 | }); 295 | break; 296 | } 297 | if (hasComponents && /\s*},?\s*$/gi.test(editor.document.lineAt(line).text.trim())) { 298 | let text = editor.document.lineAt(line - 1).text; 299 | let insertPos = text.indexOf(text.trim()); 300 | let empty = ''; 301 | for (let i = 0; i < insertPos; i++) { 302 | empty += ' '; 303 | } 304 | editor.edit((editBuilder) => { 305 | importString = importString.replace(/\\/gi, '/'); 306 | editBuilder.insert(new Position(importLine, 0), importString); 307 | editBuilder.insert(new Position(line - 1, editor.document.lineAt(line - 1).text.length), ',\n' + empty + name); 308 | }); 309 | break; 310 | } 311 | if (/\s*components\s*:\s*{\s*$/gi.test(editor.document.lineAt(line).text.trim())) { 312 | hasComponents = true; 313 | } 314 | if (/\s*export\s*default\s*{\s*/gi.test(editor.document.lineAt(line).text.trim())) { 315 | secondInsertLine = line; 316 | } 317 | if (/\s*data\s*\(\s*\)\s*{\s*/gi.test(editor.document.lineAt(line).text.trim())) { 318 | let text = editor.document.lineAt(line).text; 319 | let insertPos = text.indexOf(text.trim()); 320 | for (let i = 0; i < insertPos; i++) { 321 | baseEmpty += ''; 322 | } 323 | prorityInsertLine = line; 324 | } 325 | if (countLine > line) { 326 | line++; 327 | } else { 328 | break; 329 | } 330 | } 331 | if (prorityInsertLine > 0) { 332 | editor.edit((editBuilder) => { 333 | importString = importString.replace(/\\/gi, '/'); 334 | editBuilder.insert(new Position(importLine - 1, 0), importString); 335 | editBuilder.insert(new Position(prorityInsertLine - 1, editor.document.lineAt(prorityInsertLine - 1).text.length), `\n\t${baseEmpty}components: { ${name} },`); 336 | }); 337 | break; 338 | } 339 | if (secondInsertLine > 0) { 340 | editor.edit((editBuilder) => { 341 | importString = importString.replace(/\\/gi, '/'); 342 | editBuilder.insert(new Position(importLine, 0), importString); 343 | editBuilder.insert(new Position(secondInsertLine, editor.document.lineAt(secondInsertLine).text.length), `\n${this.explorer.tabSize}components: { ${name} },`); 344 | }); 345 | } 346 | } 347 | 348 | break; 349 | } 350 | } 351 | } 352 | 353 | // 代码块选择 354 | public blockSelect() { 355 | let editor = window.activeTextEditor; 356 | if(!editor) { return; } 357 | 358 | let startPosition = editor.selection.start; 359 | let lineTextObj = editor.document.lineAt(startPosition.line); 360 | let lineText = lineTextObj.text; 361 | if (lineText.length > 0 && startPosition.character === 0 && lineText[startPosition.character] === '[') { 362 | this.selectJsBlock(editor, lineText.substring(startPosition.character, lineText.length), startPosition, 'array'); 363 | } else if (lineText.length > 0 && startPosition.character > 0 && lineText[startPosition.character - 1] === '[') { 364 | this.selectJsBlock(editor, lineText.substring(startPosition.character - 1, lineText.length), new Position(startPosition.line, startPosition.character - 1), 'array'); 365 | } else if (lineText.length > 0 && startPosition.character < lineText.length && lineText[startPosition.character] === '[') { 366 | this.selectJsBlock(editor, lineText.substring(startPosition.character, lineText.length), startPosition, 'array'); 367 | } else if (lineText.length > 0 && startPosition.character === 0 && lineText[startPosition.character] === '{') { 368 | this.selectJsBlock(editor, lineText.substring(startPosition.character, lineText.length), startPosition, 'json'); 369 | } else if (lineText.length > 0 && startPosition.character > 0 && lineText[startPosition.character - 1] === '{') { 370 | this.selectJsBlock(editor, lineText.substring(startPosition.character - 1, lineText.length), new Position(startPosition.line, startPosition.character - 1), 'json'); 371 | } else if (lineText.length > 0 && startPosition.character < lineText.length && lineText[startPosition.character] === '{') { 372 | this.selectJsBlock(editor, lineText.substring(startPosition.character, lineText.length), startPosition, 'json'); 373 | } else if (lineText.trim().length > 0 && lineText.trim()[0] === '<' && startPosition.character <= lineText.indexOf('<')) { 374 | lineText = lineText.substring(startPosition.character, lineText.length); 375 | this.selectHtmlBlock(editor, lineText, startPosition); 376 | } else if (lineText.trim().length > 0 && lineText.trim()[0] === '<' && startPosition.character <= lineText.indexOf('<')) { 377 | lineText = lineText.substring(startPosition.character, lineText.length); 378 | this.selectHtmlBlock(editor, lineText, startPosition); 379 | } else if (/^\s*[\sa-zA-Z:_-]*\s*\[\s*$/gi.test(lineText)) { 380 | this.selectJsBlock(editor, lineText, new Position(startPosition.line, lineText.length - lineText.replace(/\s*/, '').length), 'array'); 381 | } else if ((lineText.trim().length > 0 && /(function|if|for|while)?.+\(.*\)\s*{/gi.test(lineText) && /^\s*(function|if|for|while)?\s*$/g.test(lineText.substr(0, startPosition.character))) 382 | || (/^(\s*[\sa-zA-Z_-]*\([\sa-zA-Z_-]*\)\s*{\s*)|(\s*[\sa-zA-Z:_-]*\s*{\s*)$/gi.test(lineText)) && /^\s*(function|if|for|while)?\s*$/g.test(lineText.substr(0, startPosition.character))) { 383 | this.selectJsBlock(editor, lineText, new Position(startPosition.line, lineText.length - lineText.replace(/\s*/, '').length), 'function'); 384 | } else { 385 | // 在本行选择 386 | this.selectLineBlock(editor, lineText, startPosition); 387 | } 388 | return ; 389 | } 390 | 391 | // 选择函数块 392 | selectJsBlock(editor: any, lineText: string, startPosition: Position, type: string) { 393 | let lineCount = editor.document.lineCount; 394 | let lineCurrent = startPosition.line; 395 | let braceLeftCount = 0; 396 | let tagLeft = '{'; 397 | let tagRight = '}'; 398 | if (type === 'array') { 399 | tagLeft = '['; 400 | tagRight = ']'; 401 | } 402 | while(lineCurrent <= lineCount) { 403 | let pos: number = 0; 404 | while((lineText.indexOf(tagLeft, pos) !== -1 || lineText.indexOf(tagRight, pos) !== -1) && pos < lineText.length) { 405 | let i = -1; 406 | // 左标签 407 | if (lineText.indexOf(tagLeft, pos) !== -1) { 408 | i = lineText.indexOf(tagLeft, pos); 409 | } 410 | // 右标签 411 | if (lineText.indexOf(tagRight, pos) !== -1) { 412 | if (i === -1 || i > lineText.indexOf(tagRight, pos)) { 413 | // 左标签不存在、左右标签都存在,右标签在前 414 | --braceLeftCount; 415 | pos = lineText.indexOf(tagRight, pos) + 1; 416 | } else { 417 | ++braceLeftCount; 418 | pos = i + 1; 419 | } 420 | } else { 421 | // 存在左标签 422 | if (i !== -1) { 423 | ++braceLeftCount; 424 | pos = i + 1; 425 | } 426 | } 427 | if (braceLeftCount === 0) { 428 | break; 429 | } 430 | } 431 | 432 | if (braceLeftCount === 0) { 433 | let extra = 0; 434 | let textExtra = editor.document.lineAt(lineCurrent).text; 435 | if (lineCurrent === startPosition.line) { 436 | extra = textExtra.indexOf(lineText); 437 | } 438 | if (type === 'function' && textExtra[pos + extra - 1] === '}' && textExtra[pos + extra] === ')') { 439 | extra += 1; 440 | } 441 | editor.selection = new Selection(startPosition, new Position(lineCurrent, pos + extra)); 442 | return; 443 | } 444 | 445 | ++lineCurrent; 446 | if (lineCount >= lineCurrent) { 447 | lineText = editor.document.lineAt(lineCurrent).text; 448 | } 449 | } 450 | return; 451 | } 452 | 453 | // 选择html代码块 454 | selectHtmlBlock(editor: any, lineText: string, startPosition: Position) { 455 | const ncname = '[a-zA-Z_][\\w\\-\\.]*'; 456 | const qnameCapture = '((?:' + ncname + '\\:)?' + ncname + ')'; 457 | const startTagOpen = new RegExp('^<' + qnameCapture); 458 | const endTag = new RegExp('^(<\\/' + qnameCapture + '[^>]*>)'); 459 | const comment = /^'; 461 | const lineCount = editor.document.lineCount; 462 | let lineCurrent = startPosition.line; 463 | 464 | let isNoIncludeTag = false; 465 | let tagStack: any = null; 466 | let col = lineText.indexOf(lineText.trim()) + startPosition.character; 467 | let beginPosition = new Position(startPosition.line, startPosition.character + lineText.length - lineText.replace(/\s*(.*)/, '$1').length); 468 | lineText = lineText.trim(); 469 | let noIncludeTags = ['input', 'img']; 470 | 471 | while(lineText) { 472 | let textTagPos = lineText.indexOf('<'); 473 | if (textTagPos === 0) { 474 | let hasEndTag = false; 475 | let hasTag = false; 476 | if (comment.test(lineText)) { 477 | let commentIndex = lineText.indexOf(commentEnd); 478 | while(commentIndex === -1 && lineCurrent < lineCount) { 479 | lineText = editor.document.lineAt(++lineCurrent).text; 480 | commentIndex = lineText.indexOf(commentEnd); 481 | } 482 | lineText = lineText.substr(commentIndex + 3, lineText.length); 483 | } 484 | const endTagMatch = lineText.match(endTag); 485 | if (endTagMatch) { 486 | hasEndTag = true; 487 | if (Array.isArray(tagStack)) { 488 | let tagIndex = tagStack.length; 489 | if (tagIndex > 0) { 490 | let isTagMatch = false; 491 | while(tagIndex > 0 && !isTagMatch) { 492 | let tag = tagStack[tagIndex - 1]; 493 | if (tag === endTagMatch[2]) { 494 | isTagMatch = true; 495 | } 496 | tagStack.pop(); 497 | --tagIndex; 498 | } 499 | } 500 | } 501 | let endAdvance = lineText.indexOf(endTagMatch[1]) + endTagMatch[1].length; 502 | col += endAdvance; 503 | lineText = lineText.substr(endAdvance, lineText.length); 504 | } 505 | 506 | if (Array.isArray(tagStack) && tagStack.length === 0) { 507 | editor.selection = new Selection(beginPosition, new Position(lineCurrent, col)); 508 | break; 509 | } 510 | 511 | const startTagMatch = lineText.match(startTagOpen); 512 | if (startTagMatch) { 513 | hasTag = true; 514 | if (isNoIncludeTag) { 515 | let lineTextCur = editor.document.lineAt(lineCurrent).text; 516 | lineText = lineTextCur.substr(0, col); 517 | let indexLast = lineText.lastIndexOf('>'); 518 | while (indexLast === -1 && lineCurrent > 0) { 519 | --lineCurrent; 520 | lineText = editor.document.lineAt(lineCurrent).text; 521 | indexLast = lineText.lastIndexOf('>'); 522 | } 523 | editor.selection = new Selection(beginPosition, new Position(lineCurrent, indexLast + 2)); 524 | break; 525 | } 526 | if (Array.isArray(tagStack)) { 527 | tagStack.push(startTagMatch[1]); 528 | } else { 529 | tagStack = [startTagMatch[1]]; 530 | if (noIncludeTags.indexOf(startTagMatch[1]) !== -1) { 531 | isNoIncludeTag = true; 532 | } 533 | } 534 | const startAdvance = lineText.indexOf(startTagMatch[1]) + startTagMatch[1].length; 535 | col += startAdvance; 536 | lineText = lineText.substr(startAdvance, lineText.length); 537 | } 538 | if (lineText.indexOf('/>') !== -1 && Array.isArray(tagStack) && tagStack.length === 1) { 539 | let tagCloseIndex = lineText.indexOf('/>'); 540 | let prevText = lineText.substr(0, tagCloseIndex + 2); 541 | let tagReg = /<([\w-]+)(\s*|(\s+[\w-_:@\.]+(=("[^"]*"|'[^']*'))?)+)\s*(\/)?>/gim; 542 | if (!tagReg.test(prevText)) { 543 | tagStack.pop(); 544 | } 545 | editor.selection = new Selection(beginPosition, new Position(lineCurrent, col + tagCloseIndex + 2)); 546 | break; 547 | } 548 | if (!lineText && lineCurrent < lineCount && tagStack.length > 0) { 549 | do { 550 | ++lineCurrent; 551 | lineText = editor.document.lineAt(lineCurrent).text; 552 | } while (!lineText && lineCurrent < lineCount); 553 | col = lineText.indexOf(lineText.trim()); 554 | lineText = lineText.trim(); 555 | continue; 556 | } 557 | if (!hasTag && !hasEndTag && lineText.length > 0) { 558 | let noTagIndex = lineText.indexOf(lineText, 1); 559 | if (noTagIndex === -1) { 560 | if (lineCurrent < lineCount) { 561 | do { 562 | ++lineCurrent; 563 | lineText = editor.document.lineAt(lineCurrent).text; 564 | } while (!lineText && lineCurrent < lineCount); 565 | col = lineText.indexOf(lineText.trim()); 566 | lineText = lineText.trim(); 567 | } else { 568 | break; 569 | } 570 | } else { 571 | lineText = lineText.substr(noTagIndex, lineText.length); 572 | } 573 | } 574 | } else if (textTagPos > 0) { 575 | lineText = lineText.substr(textTagPos, lineText.length); 576 | col += textTagPos; 577 | } else if (textTagPos < 0) { 578 | if (lineCurrent < lineCount) { 579 | // 一行最前面是否有 /> 580 | if (lineText.indexOf('/>') !== -1 && Array.isArray(tagStack) && tagStack.length > 0) { 581 | let tagCloseIndex = lineText.indexOf('/>'); 582 | let prevText = lineText.substr(0, tagCloseIndex + 2); 583 | let tagReg = /<([\w-]+)(\s*|(\s+[\w-_:@\.]+(=("[^"]*"|'[^']*'))?)+)\s*(\/)?>/gim; 584 | if (!tagReg.test(prevText)) { 585 | tagStack.pop(); 586 | } 587 | if(tagStack.length === 0) { 588 | editor.selection = new Selection(beginPosition, new Position(lineCurrent, col + tagCloseIndex + 2)); 589 | break; 590 | } 591 | } 592 | do { 593 | ++lineCurrent; 594 | lineText = editor.document.lineAt(lineCurrent).text; 595 | if (lineText.replace(/\s/gi, '') === '') { 596 | lineText = ''; 597 | } 598 | } while (!lineText && lineCurrent < lineCount); 599 | col = lineText.indexOf(lineText.trim()); 600 | lineText = lineText.trim(); 601 | } else { 602 | lineText = ''; 603 | } 604 | } 605 | } 606 | } 607 | 608 | selectLineBlock(editor: TextEditor, lineText: String, startPosition: Position) { 609 | // "" '' () {}, >< 空格 610 | // 1. 遍历左侧查询结束标签 611 | let TAGS = ["\"", "'", "(", "{", "[", " ", "`", ">"]; 612 | let TAGS_CLOSE: any = { 613 | "\"": "\"", 614 | "'": "'", 615 | "(": ")", 616 | "{": "}", 617 | "[": "]", 618 | " ": " ", 619 | "`": "`", 620 | ">": "<" 621 | }; 622 | let pos = startPosition.character - 1; 623 | let endTag = '', 624 | beginPos = 0, 625 | endPos = 0, 626 | inBeginTags: any [] = [], 627 | includeTags = false; 628 | beginPos = pos; 629 | while (pos >= 0) { 630 | if (TAGS.indexOf(lineText[pos]) !== -1) { 631 | endTag = lineText[pos]; 632 | break; 633 | } 634 | --pos; 635 | } 636 | if (beginPos === pos) { 637 | includeTags = true; 638 | beginPos = pos; 639 | } else { 640 | beginPos = pos + 1; 641 | } 642 | // 存在结束标签 643 | if (endTag.length > 0) { 644 | pos = startPosition.character; 645 | if (endTag === '>') { 646 | while (pos <= lineText.length && pos >= 0) { 647 | let txt = lineText[pos]; 648 | if ((txt === TAGS_CLOSE[endTag] || txt === '>') && pos > beginPos) { 649 | break; 650 | } 651 | ++pos; 652 | } 653 | } else { 654 | while (pos <= lineText.length && pos >= 0) { 655 | let txt = lineText[pos]; 656 | if (inBeginTags.length === 0 && (txt === TAGS_CLOSE[endTag] || txt === '>') && pos > beginPos) { 657 | break; 658 | } 659 | if (inBeginTags.length > 0 && TAGS_CLOSE[inBeginTags[inBeginTags.length - 1]] === txt) { 660 | inBeginTags.pop(); 661 | } else if (TAGS.indexOf(txt) !== -1 && txt !== ' ') { 662 | inBeginTags.push(txt); 663 | } 664 | 665 | ++pos; 666 | } 667 | } 668 | } 669 | includeTags ? (endPos = pos + 1) : (endPos = pos); 670 | editor.selection = new Selection(new Position(startPosition.line, beginPos), new Position(startPosition.line, endPos)); 671 | } 672 | } -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | import ExplorerProvider from './explorer'; 3 | import FrameworkProvider from './framework'; 4 | import Assist from './assist'; 5 | import MonitorProvider from './monitor' 6 | 7 | export function activate(context: ExtensionContext) { 8 | init(context) 9 | } 10 | 11 | function init(context: ExtensionContext) { 12 | const explorer = new ExplorerProvider(context) 13 | 14 | const framework = new FrameworkProvider(explorer) 15 | framework.register() 16 | 17 | const assist = new Assist(explorer) 18 | assist.register() 19 | 20 | new MonitorProvider(explorer) 21 | } -------------------------------------------------------------------------------- /src/explorer.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, workspace, ConfigurationTarget, commands, window, StatusBarAlignment, TextDocument } from 'vscode' 2 | import { getTabSize, getWorkspaceRoot, winRootPathHandle } from './util/util' 3 | import Traverse from './util/traverse' 4 | import * as path from 'path' 5 | import * as fs from 'fs' 6 | 7 | export interface Prefix { 8 | alias: string 9 | path: string 10 | } 11 | 12 | export default class ExplorerProvider { 13 | // 全局变量 14 | public name: string = 'vue-helper' 15 | public context: ExtensionContext 16 | // 工程根目录 17 | public projectRootPath: string = '' 18 | public projectRootPathReg: RegExp 19 | public tabSize = '' 20 | public traverse: Traverse 21 | public isTs = false 22 | public prefix: Prefix = { 23 | alias: '@', 24 | path: 'src' 25 | } 26 | public vueFiles: any = [] 27 | public store = { 28 | set: (key: string, value: any) => { 29 | workspace.getConfiguration(this.name).update(key, value, ConfigurationTarget.Global); 30 | }, 31 | get: (key: string) => { 32 | return workspace.getConfiguration(this.name).get(key); 33 | } 34 | } 35 | public inits: any[] = [] 36 | 37 | public setContext(name: string, value: boolean) { 38 | commands.executeCommand('setContext', name, value); 39 | } 40 | 41 | public getActiveEditorDir(activePath: string, ) { 42 | return activePath.replace(this.projectRootPathReg, '').replace(/[\/|\\]\w*\.\w*$/gi, '') 43 | } 44 | 45 | public getActiveEditorPath(activePath: string, ) { 46 | return activePath.replace(this.projectRootPathReg, '') 47 | } 48 | 49 | public getVueRelativePath(activeEditorPath: string, vuePath: string) { 50 | let vfPath = path.relative(activeEditorPath, vuePath) 51 | vfPath = './' + vfPath 52 | return vfPath.replace(/\\/gi, '/') 53 | } 54 | 55 | constructor(context: ExtensionContext) { 56 | this.context = context 57 | this.projectRootPath = getWorkspaceRoot('') 58 | this.projectRootPathReg = new RegExp(`.*${this.projectRootPath}/`, 'gi') 59 | this.traverse = new Traverse(this, this.prefix) 60 | this.tabSize = getTabSize() 61 | const tsconfigPath = winRootPathHandle(path.join(this.projectRootPath, 'tsconfig.json')) 62 | this.isTs = fs.existsSync(tsconfigPath) 63 | 64 | const vueHelperStatusBar = window.createStatusBarItem(StatusBarAlignment.Right, -99999) 65 | vueHelperStatusBar.text = '$(extensions-view-icon) helper' 66 | // const vueHelperStatusBarTooltip = new MarkdownString('

vue-helper2

') 67 | // vueHelperStatusBarTooltip.supportHtml = true 68 | // vueHelperStatusBar.tooltip = vueHelperStatusBarTooltip 69 | vueHelperStatusBar.show() 70 | this.context.subscriptions.push(vueHelperStatusBar) 71 | 72 | this.vueFiles = this.traverse.search('.vue', '', false) 73 | const watcher = workspace.createFileSystemWatcher('**/*.vue') 74 | watcher.onDidCreate(() => { this.vueFiles = this.traverse.search('.vue', '', false) }) 75 | watcher.onDidDelete(() => { this.vueFiles = this.traverse.search('.vue', '', false) }) 76 | 77 | workspace.onDidOpenTextDocument((e: TextDocument) => { 78 | this.openDocument(e) 79 | }) 80 | } 81 | 82 | register() { 83 | } 84 | 85 | public resetInit() { 86 | this.inits.forEach(initObj => { 87 | initObj.init() 88 | }); 89 | } 90 | 91 | public addInit(obj: any) { 92 | this.inits.push(obj) 93 | } 94 | 95 | public openDocument(e: TextDocument) { 96 | // 当多工程打开时,进入文件重新获取工程路径 97 | let docPath = e.uri.path.replace(/.*:\//gi, '\/') 98 | if (!this.projectRootPath || !docPath.includes(this.projectRootPath.replace(/.*:\//gi, '\/'))) { 99 | this.projectRootPath = getWorkspaceRoot(e.uri.path) 100 | this.vueFiles = this.traverse.search('.vue', '', false) 101 | this.resetInit() 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/framework.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, CompletionContext, CompletionItem, CompletionItemProvider, CompletionList, Position, ProviderResult, TextDocument, 2 | languages, workspace, Range, window, Selection, CompletionItemKind, SnippetString, 3 | l10n, HoverProvider, Hover, TextLine, DefinitionProvider, Definition, Uri, Location } from "vscode"; 4 | import ExplorerProvider from './explorer' 5 | import * as fs from 'fs' 6 | import * as path from 'path' 7 | import { winRootPathHandle, getRelativePath, getCurrentWord, getWord } from './util/util' 8 | import { getJsTag, getTag, getAttribute, getGlobalAttribute, getDocument } from "./frameworks"; 9 | import vueSnippetsHtml from './vue/snippets-html' 10 | import vueSnippetsJs from './vue/snippets-js' 11 | const paramCamse = require('param-case') 12 | const glob = require('glob') 13 | 14 | export interface TagObject { 15 | text: string, 16 | offset: number 17 | }; 18 | 19 | export default class FrameworkProvider { 20 | public explorer: ExplorerProvider 21 | public frameworks: string[] = [] 22 | public pathAlias = { 23 | alias: '', 24 | path: '' 25 | } 26 | 27 | constructor(explorer: ExplorerProvider) { 28 | this.explorer = explorer 29 | this.init() 30 | this.explorer.addInit(this) 31 | } 32 | 33 | init() { 34 | try { 35 | if (this.explorer.projectRootPath) { 36 | this.frameworks = [] 37 | const pkg = fs.readFileSync(winRootPathHandle(path.join(this.explorer.projectRootPath, 'package.json')), 'utf-8') 38 | pkg.includes('element-plus') && this.frameworks.push('element-plus') 39 | pkg.includes('element-ui') && this.frameworks.push('element-ui') 40 | pkg.includes('ant-design-vue') && this.frameworks.push('ant-design-vue') 41 | } 42 | } catch (error) { 43 | } 44 | } 45 | 46 | register() { 47 | this.explorer.context.subscriptions.push(languages.registerCompletionItemProvider(['vue', 'javascript', 'typescript', 'html', 'wxml'], new FrameworkCompletionItemProvider(this), '', ':', '<', '"', "'", '/', '@', '(', '>', '{')) 48 | this.explorer.context.subscriptions.push(languages.registerHoverProvider(['vue', 'wxml'], new FrameworkHoverProvider(this))) 49 | this.explorer.context.subscriptions.push(languages.registerDefinitionProvider(['vue', 'javascript', 'html', 'wxml'], new vueHelperDefinitionProvider(this))) 50 | } 51 | 52 | } 53 | 54 | class FrameworkCompletionItemProvider implements CompletionItemProvider { 55 | public frameworkProvider: FrameworkProvider 56 | public attribute: any = {} 57 | public jsTag: any = {} 58 | public tag: any = {} 59 | public globalAttribute: any = {} 60 | public tagReg: RegExp = /<([\w-]+)\s+/g; 61 | public attrReg: RegExp = /(?:\(|\s*)((\w(-)?)*)=['"][^'"]*/; // 能够匹配 left-right 属性 62 | 63 | constructor(frameworkProvider: FrameworkProvider) { 64 | this.frameworkProvider = frameworkProvider 65 | this.init() 66 | this.frameworkProvider.explorer.addInit(this) 67 | } 68 | 69 | init() { 70 | this.attribute = getAttribute(this.frameworkProvider.frameworks, this.frameworkProvider.explorer.tabSize) 71 | this.tag = getTag(this.frameworkProvider.frameworks, this.frameworkProvider.explorer.tabSize) 72 | this.jsTag = getJsTag(this.frameworkProvider.frameworks, this.frameworkProvider.explorer.tabSize) 73 | this.globalAttribute = getGlobalAttribute(this.frameworkProvider.frameworks, this.frameworkProvider.explorer.tabSize) 74 | } 75 | 76 | isCloseTag(document: TextDocument, position: Position) { 77 | let txt = document.getText(new Range(new Position(position.line, 0), position)).trim(); 78 | if(!txt.endsWith('>') || /.*=("[^"]*>|'[^']*>)$/gi.test(txt) || txt.endsWith('/>')) { 79 | return false; 80 | } 81 | let txtArr = txt.match(/<([\w-]+)(\s*|(\s+[\w-_:@\.]+(=("[^"]*"|'[^']*'))?)+)\s*>/gim); 82 | if(Array.isArray(txtArr) && txtArr.length > 0) { 83 | let txtStr = txtArr[txtArr.length - 1]; 84 | return /<([\w-]+)(\s*|(\s+[\w-_:@\.]+(=("[^"]*"|'[^']*'))?)+)\s*>$/gi.test(txtStr); 85 | } 86 | return false; 87 | } 88 | 89 | // 自动补全关闭标签 90 | getCloseTagSuggestion(document: TextDocument, position: Position) { 91 | let txtInfo = document.lineAt(position.line); 92 | let txtArr = txtInfo.text.match(/<([\w-]+)(\s*|(\s+[\w-_:@\.]+(=("[^"]*"|'[^']*'))?)+)\s*>/gim); 93 | let tag = 'div'; 94 | if(txtArr) { 95 | tag = txtArr[txtArr.length - 1].replace(/<([\w-]+)(\s*|(\s+[\w-_:@\.]+(=("[^"]*"|'[^']*'))?)+)\s*>/gim, '$1'); 96 | } 97 | let exclude = ['br', 'img']; 98 | if (exclude.indexOf(tag) === -1 && window.activeTextEditor) { 99 | window.activeTextEditor.edit((editBuilder) => { 100 | editBuilder.insert(position, ''); 101 | }); 102 | let newPosition = window.activeTextEditor.selection.active.translate(0, 0); 103 | if (newPosition) { 104 | window.activeTextEditor.selection = new Selection(newPosition, newPosition); 105 | } 106 | } 107 | } 108 | 109 | // 获取本行位置前的文本 110 | getTextBeforePosition(position: Position, document: TextDocument): string { 111 | var start = new Position(position.line, 0); 112 | var range = new Range(start, position); 113 | return document.getText(range); 114 | } 115 | 116 | // 匹配标签 117 | matchTag(reg: RegExp, txt: string, line: number, document: TextDocument, position: Position): TagObject | string { 118 | let match: any; 119 | let arr: any[] = []; 120 | if (/<\/?[-\w]+[^<>]*>[\s\w]*[^<\/>]*$/.test(txt) || /[^<>]*<$/.test(txt[txt.length - 1])))) { 121 | return 'break'; 122 | } 123 | while ((match = reg.exec(txt))) { 124 | arr.push({ 125 | text: match[1], 126 | offset: document.offsetAt(new Position(line, match.index)) 127 | }); 128 | } 129 | return arr.pop(); 130 | } 131 | 132 | getPreTag(document: TextDocument, position: Position): TagObject | undefined { 133 | let line = position.line; 134 | let tag: TagObject | string; 135 | let txt = this.getTextBeforePosition(position, document); 136 | 137 | while (position.line - line < 10 && line >= 0) { 138 | if (line !== position.line) { 139 | txt = document.lineAt(line).text; 140 | } 141 | tag = this.matchTag(this.tagReg, txt, line, document, position); 142 | 143 | if (tag === 'break') {return;} 144 | if (tag) {return tag;} 145 | line--; 146 | } 147 | return; 148 | } 149 | 150 | // 获取预览属性 151 | getPreAttr(document: TextDocument, position: Position): string | undefined { 152 | let txt = this.getTextBeforePosition(position, document).replace(/"[^'"]*(\s*)[^'"]*$/, ''); 153 | let end = position.character; 154 | let start = txt.lastIndexOf(' ', end) + 1; 155 | let parsedTxt = document.getText(new Range(position.line, start, position.line, end)); 156 | 157 | return this.matchAttr(this.attrReg, parsedTxt); 158 | } 159 | 160 | // 匹配属性 161 | matchAttr(reg: RegExp, txt: string): string { 162 | let match: any; 163 | match = reg.exec(txt); 164 | return !/"[^"]*"/.test(txt) && match && match[1]; 165 | } 166 | 167 | // 属性值开始 168 | isAttrValueStart(tag: Object | string | undefined, attr: any) { 169 | return tag && attr; 170 | } 171 | 172 | // 获取属性值 173 | getAttrValues(tag: string, attr: string) { 174 | let attrValues: string[] = [] 175 | // 全局 176 | if (this.globalAttribute[attr]) { 177 | attrValues = this.globalAttribute[attr].values 178 | } 179 | 180 | if (this.attribute[tag] && this.attribute[tag][attr]) { 181 | attrValues = this.attribute[tag][attr].values 182 | } 183 | 184 | return attrValues 185 | } 186 | 187 | // 属性值建议值 188 | getAttrValueSuggestion(tag: string, attr: string): CompletionItem[] { 189 | let suggestions: CompletionItem[] = []; 190 | const values = this.getAttrValues(tag, attr); 191 | values.forEach((value: string) => { 192 | suggestions.push({ 193 | sortText: `000${value}`, 194 | label: value, 195 | detail: this.frameworkProvider.explorer.name, 196 | kind: CompletionItemKind.Value, 197 | }); 198 | }); 199 | return suggestions; 200 | } 201 | 202 | // 属性建议值 203 | getAttrSuggestion(tag: string, document: TextDocument, position: Position) { 204 | let suggestions: any[] = []; 205 | let tagAttrs = this.getTagAttrs(tag); 206 | 207 | let preText = this.getTextBeforePosition(position, document); 208 | let prefix: any = preText.replace(/['"]([^'"]*)['"]$/, '').split(/\s|\(+/).pop(); 209 | // 方法属性 210 | const type = prefix[0] === '@' ? 'method' : 'attribute'; 211 | 212 | tagAttrs.forEach((attr: any) => { 213 | if (attr.type === type) { 214 | suggestions.push(this.buildAttrSuggestion(attr)) 215 | } 216 | }); 217 | 218 | for (let attr in this.globalAttribute) { 219 | let gAttr = this.globalAttribute[attr] 220 | if (gAttr.type === type) { 221 | suggestions.push(this.buildAttrSuggestion({ 222 | name: attr, 223 | ...gAttr 224 | })) 225 | } 226 | } 227 | return suggestions; 228 | } 229 | 230 | buildAttrSuggestion(attr: any) { 231 | const completionItem = new CompletionItem(attr.name) 232 | completionItem.sortText = `000${attr.name}` 233 | completionItem.insertText = attr.name 234 | completionItem.kind = attr.type === 'method' ? CompletionItemKind.Method : CompletionItemKind.Property 235 | completionItem.detail = this.frameworkProvider.explorer.name 236 | completionItem.documentation = l10n.t(attr.description) 237 | return completionItem 238 | } 239 | 240 | // 获取标签包含的属性 241 | getTagAttrs(tag: string) { 242 | let attrs: any = [] 243 | if (this.attribute[tag]) { 244 | for (const key in this.attribute[tag]) { 245 | if (key !== '_self') { 246 | attrs.push({ 247 | name: key, 248 | ...this.attribute[tag][key] 249 | }) 250 | } 251 | } 252 | } 253 | return attrs 254 | } 255 | 256 | // 获取props属性值 257 | getPropAttr(document: TextDocument, tagName: any) { 258 | let documentText = document.getText() 259 | // 1. 找出标签所在路径 260 | let tagNameUpper = tagName.replace(/(-[a-z])/g, (_: any, c: any) => { 261 | return c ? c.toUpperCase() : ''; 262 | }).replace(/-/gi, ''); 263 | let pathReg = RegExp('import\\\s+(' + tagName + '|' + tagNameUpper + ')\\\s+from\\\s+[\'\"]([^\'\"]*)', 'g'); 264 | let pathRegArr = documentText.match(pathReg); 265 | if (pathRegArr && pathRegArr.length > 0) { 266 | let tagPath = pathRegArr[0]; 267 | tagPath = tagPath.replace(/(.*['"])/, ''); 268 | tagPath = tagPath.replace(this.frameworkProvider.pathAlias.alias, this.frameworkProvider.pathAlias.path); 269 | if (!tagPath.endsWith('.vue')) { 270 | tagPath += '.vue'; 271 | } 272 | if (tagPath.indexOf('./') > 0 || tagPath.indexOf('../') > 0) { 273 | tagPath = path.join(document.fileName, '../', tagPath); 274 | } else { 275 | tagPath = path.join(workspace.rootPath || '', tagPath); 276 | } 277 | documentText = fs.readFileSync(tagPath, 'utf8'); 278 | } else { 279 | return; 280 | } 281 | 282 | // 2. 获取标签文件中的prop属性 283 | let props: CompletionItem[] = []; 284 | let scriptIndex = documentText.indexOf(' 0 && docText.length > 0) { 299 | braceBeforeIndex = docText.indexOf('{'); 300 | braceAfterIndex = docText.indexOf('}'); 301 | if (braceBeforeIndex === -1) { 302 | docText = ''; 303 | } else if (braceBeforeIndex < braceAfterIndex) { 304 | if (propStack === 1) { 305 | propText += docText.substr(0, braceBeforeIndex); 306 | } 307 | ++propStack; 308 | docText = docText.substr(braceBeforeIndex > 0 ? braceBeforeIndex + 1 : 1, docText.length); 309 | } else { 310 | --propStack; 311 | docText = docText.substr(braceAfterIndex > 0 ? braceAfterIndex + 1 : 1, docText.length); 312 | } 313 | } 314 | let propMatch = propText.match(/\s[\w-]*:/gi); 315 | if (propMatch && propMatch.length > 0) { 316 | propMatch.forEach((propItem, propIndex) => { 317 | propItem = propItem.substr(1, propItem.length - 2); 318 | propItem = propItem.replace(/([A-Z])/g, (_, c) => { 319 | return c ? '-' + c.toLowerCase() : ''; 320 | }); 321 | props.push({ 322 | label: propItem, 323 | sortText: '0' + propIndex, 324 | insertText: new SnippetString(`:${propItem}="$0"`), 325 | kind: CompletionItemKind.Property, 326 | documentation: '' 327 | }); 328 | }); 329 | } 330 | } 331 | } 332 | let emitReg = documentText.match(/\$emit\(\s?['"](\w*)/g); 333 | if (emitReg && emitReg.length > 0) { 334 | for (let i = 0; i < emitReg.length; i++) { 335 | let emitName = emitReg[i]; 336 | emitName = emitName.replace(/(.*['"])/, ''); 337 | props.push({ 338 | label: emitName, 339 | sortText: '0' + (props.length + 1), 340 | insertText: new SnippetString(`@${emitName}="$0"`), 341 | kind: CompletionItemKind.Method, 342 | documentation: '' 343 | }); 344 | } 345 | } 346 | return props; 347 | } 348 | 349 | // 判断是否是导入 350 | isImport(document: TextDocument, position: Position) { 351 | let lineTxt = document.lineAt(position.line).text.trim(); 352 | return /^\s*import.*/.test(lineTxt); 353 | } 354 | 355 | // 导入建议 356 | importSuggestion(document: TextDocument, position: Position) { 357 | let search = document.lineAt(position.line).text.trim(); 358 | search = search.replace(/^import/, '').trim(); 359 | let suggestions: CompletionItem[] = []; 360 | if (search) { 361 | let files = this.frameworkProvider.explorer.traverse.search('', search, false); 362 | let pathAlias = this.frameworkProvider.pathAlias 363 | files.forEach(vf => { 364 | let filePath = ''; 365 | if (pathAlias.alias) { 366 | filePath = vf.path 367 | } else { 368 | filePath = getRelativePath(document.uri.path, path.join(this.frameworkProvider.explorer.projectRootPath, vf.path)) 369 | } 370 | let camelName = vf.name 371 | let insertPath = filePath 372 | if (filePath.endsWith('.ts')) { 373 | insertPath = filePath.substring(0, filePath.length - 3) 374 | } 375 | suggestions.push({ 376 | label: vf.name, 377 | sortText: `0${vf.name}`, 378 | insertText: new SnippetString(`\${1:${camelName}} from '${insertPath}'`), 379 | kind: CompletionItemKind.Reference, 380 | detail: vf.name, 381 | documentation: `import ${camelName} from ${filePath}` 382 | }); 383 | }); 384 | } 385 | return suggestions; 386 | } 387 | 388 | // vue文件只在template里面提示,已script作为标记 389 | notInTemplate(document: TextDocument, position: Position): boolean { 390 | let line = position.line; 391 | while (line) { 392 | if (/^\s*\s*$/.test(document.lineAt(line).text)) { 393 | return true; 394 | } 395 | line--; 396 | } 397 | return false; 398 | } 399 | 400 | // 编译建议标签 401 | buildTagSuggestion(tag: string, tagVal: any, id: number): CompletionItem { 402 | return { 403 | label: tag, 404 | sortText: `00${id}${tag}`, 405 | insertText: new SnippetString(tagVal), 406 | kind: CompletionItemKind.Snippet, 407 | detail: this.frameworkProvider.explorer.name, 408 | documentation: '' 409 | }; 410 | } 411 | 412 | // 获取js代码提示 413 | getTagJsSuggestion() { 414 | let suggestions: any[] = []; 415 | let id = 1; 416 | 417 | const useVueSnippets = this.frameworkProvider.explorer.store.get('use-vue-snippets') 418 | if (useVueSnippets) { 419 | const snippetsJs: any = vueSnippetsJs(this.frameworkProvider.explorer.tabSize) 420 | for (const key in snippetsJs) { 421 | const snippet = snippetsJs[key] 422 | suggestions.push({ 423 | label: key, 424 | sortText: `0${id}${key}`, 425 | insertText: new SnippetString(snippet), 426 | kind: CompletionItemKind.Snippet, 427 | detail: this.frameworkProvider.explorer.name, 428 | }) 429 | id++ 430 | } 431 | } 432 | 433 | try { 434 | for (let tag in this.jsTag) { 435 | const tagItem = this.jsTag[tag] 436 | suggestions.push({ 437 | label: tag, 438 | sortText: `00${id}${tag}`, 439 | insertText: new SnippetString(tagItem), 440 | kind: CompletionItemKind.Snippet, 441 | detail: this.frameworkProvider.explorer.name, 442 | documentation: tagItem 443 | }) 444 | id++; 445 | } 446 | } catch (_error) { 447 | } 448 | return suggestions 449 | } 450 | 451 | // 添加工程内vue组件提示 452 | addLocalComponentSuggestions() { 453 | let suggestions: CompletionItem[] = []; 454 | if (window.activeTextEditor) { 455 | let activeEditorPath = this.frameworkProvider.explorer.getActiveEditorDir(window.activeTextEditor.document.uri.path) 456 | for (let i = 0; i < this.frameworkProvider.explorer.vueFiles.length; i++) { 457 | const vf = this.frameworkProvider.explorer.vueFiles[i]; 458 | suggestions.push({ 459 | label: vf.name, 460 | sortText: `0${i}${vf.name}`, 461 | insertText: new SnippetString(`${vf.name}$0>`), 462 | kind: CompletionItemKind.Folder, 463 | detail: this.frameworkProvider.explorer.name, 464 | documentation: `import ${vf.name} from '${this.frameworkProvider.explorer.getVueRelativePath(activeEditorPath, vf.path)}'`, 465 | command: { command: `${this.frameworkProvider.explorer.name}.funcEnhance`, title: `${this.frameworkProvider.explorer.name}.funcEnhance` } 466 | }); 467 | } 468 | } 469 | return suggestions 470 | } 471 | 472 | 473 | // 标签提示项 474 | getTagSuggestion() { 475 | let suggestions: CompletionItem[] = this.addLocalComponentSuggestions(); 476 | let id = 1; 477 | 478 | const useVueSnippets = this.frameworkProvider.explorer.store.get('use-vue-snippets') 479 | if (useVueSnippets) { 480 | const snippetsHtml: any = vueSnippetsHtml(this.frameworkProvider.explorer.tabSize) 481 | for (const key in snippetsHtml) { 482 | const snippet = snippetsHtml[key] 483 | suggestions.push({ 484 | label: key, 485 | sortText: `0${id}${key}`, 486 | insertText: new SnippetString(snippet), 487 | kind: CompletionItemKind.Snippet, 488 | detail: this.frameworkProvider.explorer.name, 489 | }) 490 | id++ 491 | } 492 | } 493 | 494 | try { 495 | for (let tag in this.tag) { 496 | suggestions.push(this.buildTagSuggestion(tag, this.tag[tag], id)); 497 | id++; 498 | } 499 | } catch (_error) { 500 | } 501 | return suggestions; 502 | } 503 | 504 | getElementTagLabelSuggestion() { 505 | let suggestions: CompletionItem[] = this.addLocalComponentSuggestions(); 506 | let id = 1; 507 | 508 | try { 509 | let labels: string[] = [] 510 | for (let tag in this.attribute) { 511 | let label = tag.replace(/:.*/gi, '') 512 | if (!labels.includes(label)) { 513 | labels.push(label) 514 | suggestions.push({ 515 | label: label, 516 | sortText: `00${id}${label}`, 517 | insertText: new SnippetString(`${label}$0>`), 518 | kind: CompletionItemKind.Snippet, 519 | detail: this.frameworkProvider.explorer.name 520 | }); 521 | id++; 522 | } 523 | } 524 | } catch (_error) { 525 | } 526 | return suggestions; 527 | } 528 | 529 | provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken, _context: CompletionContext): ProviderResult> { 530 | // 关闭标签 531 | if (this.isCloseTag(document, position)) { 532 | this.getCloseTagSuggestion(document, position); 533 | return []; 534 | } 535 | 536 | // 标签、属性 537 | let tag: TagObject | string | undefined = this.getPreTag(document, position); 538 | let attr = this.getPreAttr(document, position); 539 | let word = getCurrentWord(document, position) 540 | let hasSquareQuote = document.lineAt(position.line).text.includes('<') 541 | 542 | if (tag && attr && this.isAttrValueStart(tag, attr)) { 543 | // 属性值开始 544 | return this.getAttrValueSuggestion(tag.text, attr); 545 | } else if (tag) { 546 | // 属性开始 547 | if (this.attribute[tag.text]) { 548 | // 框架属性 549 | return this.getAttrSuggestion(tag.text, document, position); 550 | } else { 551 | return this.getPropAttr(document, tag.text); 552 | } 553 | } else if (this.isImport(document, position)) { 554 | return this.importSuggestion(document, position); 555 | } else if (word[0] === 'e' || word[0] === 'a') { 556 | // 标签 557 | return this.notInTemplate(document, position) ? this.getTagJsSuggestion() : this.getTagSuggestion() 558 | } else if (word.includes('v')) { 559 | // vue相关 560 | return this.notInTemplate(document, position) ? this.getTagJsSuggestion() : this.getTagSuggestion() 561 | } else if (!tag && hasSquareQuote) { 562 | return this.notInTemplate(document, position) ? [] : this.getElementTagLabelSuggestion() 563 | } 564 | 565 | return [] 566 | } 567 | 568 | } 569 | 570 | // 文档通过 hover 形式查看 571 | class FrameworkHoverProvider implements HoverProvider { 572 | public frameworkProvider: FrameworkProvider 573 | public document: any 574 | 575 | constructor(frameworkProvider: FrameworkProvider) { 576 | this.frameworkProvider = frameworkProvider 577 | this.document = getDocument(this.frameworkProvider.frameworks, this.frameworkProvider.explorer.tabSize) 578 | } 579 | 580 | // 获取属性所属标签 581 | public getTag(document: any, position: any): String { 582 | let line = position.line; 583 | let tagName = ''; 584 | 585 | while(line > 0 && !tagName) { 586 | let lineInfo: TextLine = document.lineAt(line); 587 | let text = lineInfo.text.trim(); 588 | // 本行则获取光标位置前文本 589 | if(line === position.line) { 590 | text = text.substring(0, position.character); 591 | } 592 | let txtArr = text.match(/<[^(>/)]+/gim); 593 | if(txtArr) { 594 | for (let i = (txtArr.length - 1); i >= 0; i--) { 595 | if(txtArr[i][0] === '<' && txtArr[i][1] !== '/') { 596 | if(txtArr[i].indexOf(' ') !== -1) { 597 | tagName = txtArr[i].replace(/^<(\S*)(\s.*|\s*)/gi, '$1'); 598 | } else { 599 | tagName = txtArr[i].replace(/^<(.*)/gi, '$1'); 600 | } 601 | break; 602 | } 603 | } 604 | } 605 | --line; 606 | } 607 | return tagName; 608 | } 609 | provideHover(document: TextDocument, position: Position): ProviderResult { 610 | let word = getWord(document, position, [' ', '<', '>', '"', '\'', '.', '\\', "=", ":"]) 611 | 612 | // tag标签遍历 613 | if(this.document[word.selectText]) { 614 | return new Hover(this.document[word.selectText]); 615 | } 616 | 617 | return null 618 | } 619 | } 620 | 621 | // 跳转到定义位置 622 | export class vueHelperDefinitionProvider implements DefinitionProvider { 623 | public frameworkProvider: FrameworkProvider 624 | 625 | constructor(frameworkProvider: FrameworkProvider) { 626 | this.frameworkProvider = frameworkProvider 627 | } 628 | 629 | public VUE_ATTR: any = { 630 | props: 1, 631 | computed: 2, 632 | methods: 3, 633 | watch: 4, 634 | beforeCreate: 5, 635 | created: 6, 636 | beforeMount: 7, 637 | mounted: 8, 638 | beforeUpdate: 9, 639 | updated: 10, 640 | activated: 11, 641 | deactivated: 12, 642 | beforeDestroy: 13, 643 | destroyed: 14, 644 | directives: 15, 645 | filters: 16, 646 | components: 17, 647 | data: 18 648 | } 649 | 650 | /** 651 | * 判断是文件内跳转还是文件外跳转 652 | */ 653 | getDefinitionPosition(lineText: string) { 654 | const pathRegs = [ 655 | /import\s+.*\s+from\s+['"](.*)['"]/, 656 | /import\s*[^'"]*\(['"](.*)['"]\)[^'"]*/, 657 | /.*require\s*\([^'"]*['"](.*)['"][^'"]*\)/, 658 | /import\s+['"](.*)['"]/, 659 | /import\s*\([^'"]*(?:\/\*.*\*\/)\s*['"](.*)['"][^'"]\)*/ 660 | ]; 661 | let execResult: RegExpMatchArray | null; 662 | for (const pathReg of pathRegs) { 663 | execResult = pathReg.exec(lineText); 664 | if (execResult && execResult[1]) { 665 | const filePath = execResult[1]; 666 | return { 667 | path: filePath 668 | } 669 | } 670 | } 671 | } 672 | 673 | /** 674 | * 获取框架 675 | * @param plugin 数组则是获取框架,字符串则为获取插件 676 | */ 677 | async getPlugin(plugin: any) { 678 | return await new Promise((resolve, reject) => { 679 | fs.readFile(workspace.rootPath + path.sep + 'package.json', 'utf8', (err, data) => { 680 | if (err) reject(err) 681 | // 数组则是获取框架 682 | let ret = '' 683 | let p: any = {} 684 | try { 685 | p = JSON.parse(data) 686 | } catch(_e) { 687 | } 688 | if (Array.isArray(plugin)) { 689 | let framework = plugin 690 | for (let i = 0; i < framework.length; i++) { 691 | const frame = framework[i] 692 | if ((p.dependencies && p.dependencies[frame]) || (p.devDependencies && p.devDependencies[frame])) { 693 | ret = frame 694 | } 695 | } 696 | } else { 697 | let pluginArr = plugin.split('/') 698 | if (pluginArr.length === 1 && (p.dependencies && p.dependencies[plugin]) || (p.devDependencies && p.devDependencies[plugin])) { 699 | ret = plugin 700 | } else if (pluginArr.length > 1 && (p.dependencies && p.dependencies[pluginArr[0]]) || (p.devDependencies && p.devDependencies[pluginArr[0]])) { 701 | ret = plugin 702 | } 703 | } 704 | 705 | if (ret) { 706 | resolve(ret) 707 | } else { 708 | resolve('') 709 | } 710 | }) 711 | }) 712 | } 713 | 714 | async readDir(dir:string, selectText: string, frame: string) { 715 | return await new Promise((resolve, reject) => { 716 | fs.readdir(dir, 'utf8', (err, files) => { 717 | if (err) reject(err) 718 | if (files.indexOf(selectText.toLowerCase()) !== -1) { 719 | if (frame === 'iview') { 720 | let prePath = dir + path.sep + selectText.toLowerCase() + path.sep 721 | let vuePath = prePath + selectText.toLowerCase() + '.vue' 722 | let indexPath = prePath + 'index.js' 723 | if (fs.existsSync(vuePath)) { 724 | resolve(vuePath) 725 | } else if (fs.existsSync(indexPath)) { 726 | resolve(indexPath) 727 | } else { 728 | resolve('') 729 | } 730 | } else if (frame === 'element-ui') { 731 | let prePath = dir + path.sep + selectText.replace(/^el-/gi, '') + path.sep 732 | let mainPath = prePath + 'src' + path.sep + 'main.vue' 733 | let vuePath = prePath + 'src' + path.sep + selectText + '.vue' 734 | let indexPath = prePath + 'index.js' 735 | if (fs.existsSync(mainPath)) { 736 | resolve(mainPath) 737 | } else if (fs.existsSync(vuePath)) { 738 | resolve(vuePath) 739 | } else if (fs.existsSync(indexPath)) { 740 | resolve(indexPath) 741 | } else { 742 | resolve('') 743 | } 744 | } 745 | } else { 746 | resolve('') 747 | } 748 | }) 749 | }) 750 | } 751 | 752 | /** 753 | * 在node_modules目录下去查找 754 | * @param selectText 755 | */ 756 | async definitionPlugin(selectText: string) { 757 | // 获取框架 758 | let frame: any = await this.getPlugin(['iview', 'element-ui']) 759 | if (frame === 'iview') { 760 | return await this.readDir(workspace.rootPath + path.sep + 'node_modules' + path.sep + 'iview' + path.sep + 'src' + path.sep + 'components', paramCamse(selectText), frame) 761 | } else if (frame === 'element-ui') { 762 | return await this.readDir(workspace.rootPath + path.sep + 'node_modules' + path.sep + 'element-ui' + path.sep + 'packages', paramCamse(selectText).replace(/^el-/gi, ''), frame) 763 | } else { 764 | return '' 765 | } 766 | } 767 | 768 | /** 769 | * 获取node_modules下package.json文件中的main字段 770 | * @param path 771 | */ 772 | async getMain(rootPath:string) { 773 | return await new Promise((resolve, reject) => { 774 | fs.readFile(rootPath + 'package.json', 'utf8', (err, data) => { 775 | if (err) reject(err) 776 | let p: any = {} 777 | try { 778 | p = JSON.parse(data) 779 | } catch(_e) { 780 | } 781 | if (p.main) { 782 | resolve(p.main) 783 | } else { 784 | resolve('') 785 | } 786 | }) 787 | }) 788 | } 789 | 790 | /** 791 | * 文件外跳转 792 | * 处理方式 793 | * 1. 根据文件目录查询是否存在相应文件 794 | * 2. 通过package.json判断是否存在安装插件 795 | * @param document 796 | * @param position 797 | * @param line 798 | */ 799 | async definitionOutFile(document: TextDocument, file: any) { 800 | let filePath = file.path 801 | 802 | // 1. 根据文件目录查询是否存在相应文件 803 | let isAbsolute = false 804 | if (filePath.includes(this.frameworkProvider.explorer.prefix.alias)) { 805 | isAbsolute = true 806 | } 807 | filePath = filePath.replace(this.frameworkProvider.explorer.prefix.alias, this.frameworkProvider.explorer.prefix.path) 808 | 809 | // 文件存在后缀,则直接查找 810 | if (/(.*\/.*|[^.]+)\..*$/gi.test(filePath)) { 811 | let tempFile = '' 812 | if (isAbsolute) { 813 | tempFile = path.join(this.frameworkProvider.explorer.projectRootPath, filePath) 814 | } else { 815 | tempFile = path.join(document.uri.path || '', '../', filePath) 816 | } 817 | if (fs.existsSync(tempFile)) { 818 | return Promise.resolve(new Location(Uri.file(tempFile), new Position(0, 0))) 819 | } 820 | } else { 821 | // 添加后缀,判断文件是否存在 822 | const postfix = ['vue', 'js', 'css', 'scss', 'less'] 823 | for (let i = 0; i < postfix.length; i++) { 824 | const post = postfix[i] 825 | // 相对路径处理 826 | let tempFile = '' 827 | if (isAbsolute) { 828 | tempFile = path.join(this.frameworkProvider.explorer.projectRootPath, filePath) 829 | } else { 830 | tempFile = path.join(document.uri.path || '', '../', filePath) 831 | } 832 | if (tempFile.endsWith(path.sep)) { 833 | tempFile = tempFile + 'index.' + post 834 | if (fs.existsSync(tempFile)) { 835 | return Promise.resolve(new Location(Uri.file(tempFile), new Position(0, 0))) 836 | } 837 | } else { 838 | let indexFile = tempFile + path.sep + 'index.' + post 839 | tempFile += '.' + post 840 | tempFile = winRootPathHandle(tempFile) 841 | if (fs.existsSync(tempFile)) { 842 | return Promise.resolve(new Location(Uri.file(tempFile), new Position(0, 0))) 843 | } 844 | // index文件判断 845 | if(fs.existsSync(indexFile)) { 846 | return Promise.resolve(new Location(Uri.file(indexFile), new Position(0, 0))) 847 | } 848 | } 849 | } 850 | } 851 | 852 | // 2. 通过package.json判断是否存在安装插件, 插件可能有目录,获取最前一节作为插件进行判断 853 | let plugin = await this.getPlugin(filePath) 854 | let pluginRootPath = workspace.rootPath + path.sep + 'node_modules' + path.sep + plugin + path.sep 855 | let pluginOwn = workspace.rootPath + path.sep + 'node_modules' + path.sep + plugin + '.js' 856 | let pluginPath = pluginRootPath + 'index.js' 857 | if (fs.existsSync(pluginOwn)) { 858 | return Promise.resolve(new Location(Uri.file(pluginOwn), new Position(0, 0))) 859 | } else if (fs.existsSync(pluginPath)) { 860 | return Promise.resolve(new Location(Uri.file(pluginPath), new Position(0, 0))) 861 | } 862 | let main = await this.getMain(pluginRootPath) 863 | if (main) { 864 | return Promise.resolve(new Location(Uri.file(pluginRootPath + main), new Position(0, 0))) 865 | } 866 | 867 | return Promise.resolve(null) 868 | } 869 | 870 | /** 871 | * 文件内跳转 872 | */ 873 | async definitionInFile(document: TextDocument, position: Position) { 874 | const word = getWord(document, position, [' ', '<', '>', '"', '\'', '.', '\\', "=", ":", "@", "(", ")", "[", "]", "{", "}", ",", "!"]) 875 | 876 | // 查找字符串位置 877 | let pos = 0 878 | let begin = false 879 | let lineText = '' 880 | let braceLeftCount = 0 881 | let attr = '' 882 | // 搜索类型,主要用于判断在哪个属性中去搜索内容,目前主要用于区分是否是组件 883 | let searchType = '' 884 | // 判断选择文件搜索类型,是否是标签 885 | if (word.startText === '<') { 886 | searchType = 'components' 887 | } 888 | while(pos < document.lineCount && !/^\s*<\/script>\s*$/g.test(lineText)) { 889 | lineText = document.lineAt(++pos).text 890 | // 从script标签开始查找 891 | if(!begin) { 892 | if(/^\s*\s*$/g.test(lineText)) { 893 | begin = true 894 | } 895 | continue; 896 | } 897 | // 判断现在正在对哪个属性进行遍历 898 | let keyWord = lineText.replace(/\s*(\w*)\s*(\(\s*\)|:|(:\s*function\s*\(\s*\)))\s*{\s*/gi, '$1') 899 | // braceLeftCount <= 3 用于去除data属性中包含vue其他属性从而不能定义问题 900 | if(this.VUE_ATTR[keyWord] !== undefined && braceLeftCount === 0) { 901 | attr = keyWord 902 | braceLeftCount = 0 903 | } 904 | 905 | if (searchType === 'components') { 906 | /** 907 | * component组件跳转处理方式 908 | * 1. 文件内import,require引入判断 909 | * 2. iview, element组件判断 910 | */ 911 | // attr存在,说明已遍历过import内容 912 | let tag = word.selectText.toLowerCase().replace(/-/gi, '') 913 | if (attr) { 914 | // 全局组件 915 | for (let i = 0; i < this.frameworkProvider.explorer.vueFiles.length; i++) { 916 | const vueFile = this.frameworkProvider.explorer.vueFiles[i]; 917 | const vueFileName = vueFile.name.toLowerCase().replace(/-/gi, '') 918 | if (vueFileName === tag) { 919 | return Promise.resolve(new Location(Uri.file(path.join(this.frameworkProvider.explorer.projectRootPath, vueFile.path.replace(this.frameworkProvider.explorer.prefix.alias, this.frameworkProvider.explorer.prefix.path))), new Position(0, 0))) 920 | } 921 | } 922 | 923 | let retPath: any = await this.definitionPlugin(word.selectText) 924 | if (retPath) { 925 | return Promise.resolve(new Location(Uri.file(retPath), new Position(0, 0))) 926 | } 927 | break; 928 | } else { 929 | if (lineText.toLowerCase().includes(tag) && (lineText.trim().indexOf('import') === 0 || lineText.trim().indexOf('require') === 0)) { 930 | return this.definitionOutFile(document, this.getDefinitionPosition(lineText)) 931 | } 932 | } 933 | } else { 934 | // data属性匹配, data具有return,单独处理 935 | let braceLeftList = lineText.match(/{/gi) 936 | let braceRightList = lineText.match(/}/gi) 937 | if(attr === 'data' && braceLeftCount >= 2) { 938 | let matchName = lineText.replace(/\s*(\w+):.+/gi, '$1') 939 | if(word.selectText === matchName && braceLeftCount === 2) { 940 | return Promise.resolve(new Location(document.uri, new Position(pos, lineText.indexOf(matchName) + matchName.length))) 941 | } 942 | let braceLeft = braceLeftList ? braceLeftList.length : 0 943 | let braceRight = braceRightList ? braceRightList.length : 0 944 | braceLeftCount += braceLeft - braceRight 945 | } else if(attr) { 946 | let matchName = lineText.replace(/\s*(async\s*)?(\w*)\s*(:|\().*/gi, '$2') 947 | if(word.selectText === matchName && braceLeftCount === 1) { 948 | return Promise.resolve(new Location(document.uri, new Position(pos, lineText.indexOf(matchName) + matchName.length))) 949 | } 950 | let braceLeft = braceLeftList ? braceLeftList.length : 0 951 | let braceRight = braceRightList ? braceRightList.length : 0 952 | braceLeftCount += braceLeft - braceRight 953 | } 954 | 955 | // data取return的属性值 956 | if(attr === 'data') { 957 | if(/\s*return\s*{\s*/gi.test(lineText)) { 958 | braceLeftCount = 2 959 | } 960 | } 961 | } 962 | } 963 | 964 | // 全目录搜索看是否存在改文件 965 | let files = glob.sync(workspace.rootPath + '/!(node_modules)/**/*.vue') 966 | for (let i = 0; i < files.length; i++) { 967 | const vueFile = files[i]; 968 | let vueChangeFile = vueFile.replace(/-/gi, '').toLowerCase().replace(/\.vue$/, '') 969 | if (vueChangeFile.endsWith('/' + word.selectText.toLowerCase().replace(/-/gi, ''))) { 970 | return Promise.resolve(new Location(Uri.file(vueFile), new Position(0, 0))) 971 | } 972 | } 973 | 974 | return Promise.resolve(null); 975 | } 976 | 977 | provideDefinition(document: TextDocument, position: Position, _token: CancellationToken): ProviderResult { 978 | let docText = document.getText() 979 | // vue2跳转 980 | // 获取定义word 981 | const line = document.lineAt(position.line) 982 | // // 判断是文件内跳转还是文件外跳转 983 | let file = this.getDefinitionPosition(line.text) 984 | if (file) { 985 | return this.definitionOutFile(document, file) 986 | } else { 987 | if (!(docText.includes('lang="ts"') || this.frameworkProvider.explorer.isTs)) { 988 | return this.definitionInFile(document, position) 989 | } 990 | } 991 | return [] 992 | } 993 | } -------------------------------------------------------------------------------- /src/frameworks/element-ui/document.ts: -------------------------------------------------------------------------------- 1 | const docUrl = 'http://element.eleme.io' 2 | 3 | export default () => { 4 | return { 5 | "el-row": `[element:${docUrl}/#/zh-CN/component/layout](${docUrl}/#/zh-CN/component/layout) \n 6 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 7 | | :--- | :--- | :--- | :--- | :--- | 8 | | gutter | 栅格间隔 | number | — | 0 | 9 | | type | 布局模式,可选 flex,现代浏览器下有效 | string | — | — | 10 | | justify | flex 布局下的水平排列方式 | string | start/end/center/space-around/space-between | start | 11 | | align | flex 布局下的垂直排列方式 | string | top/middle/bottom | top | 12 | | tag | 自定义元素标签 | string | * | div |`, 13 | "el-col": `[element:${docUrl}/#/zh-CN/component/layout](${docUrl}/#/zh-CN/component/layout) \n 14 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 15 | | :--- | :--- | :--- | :--- | :--- | 16 | | span | 栅格占据的列数 | number | — | 24 | 17 | | offset | 栅格左侧的间隔格数 | number | — | 0 | 18 | | push | 栅格向右移动格数 | number | — | 0 | 19 | | pull | 栅格向左移动格数 | number | — | 0 | 20 | | xs | <768px 响应式栅格数或者栅格属性对象 | number/object (例如: {span: 4, offset: 4}) | — | — | 21 | | sm | ≥768px 响应式栅格数或者栅格属性对象 | number/object (例如: {span: 4, offset: 4}) | — | — | 22 | | md | ≥992px 响应式栅格数或者栅格属性对象 | number/object (例如: {span: 4, offset: 4}) | — | — | 23 | | lg | ≥1200px 响应式栅格数或者栅格属性对象 | number/object (例如: {span: 4, offset: 4}) | — | — | 24 | | xl | ≥1920px 响应式栅格数或者栅格属性对象 | number/object (例如: {span: 4, offset: 4}) | — | — | 25 | | tag | 自定义元素标签 | string | * | div |`, 26 | "el-container": `[element:${docUrl}/#/zh-CN/component/container](${docUrl}/#/zh-CN/component/container) \n 27 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 28 | | :--- | :--- | :--- | :--- | :--- | 29 | | direction | 子元素的排列方向 | string | horizontal / vertical | 子元素中有 el-header 或 el-footer 时为 vertical,否则为 horizontal |`, 30 | "el-header": `[element:${docUrl}/#/zh-CN/component/container](${docUrl}/#/zh-CN/component/container) \n 31 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 32 | | :--- | :--- | :--- | :--- | :--- | 33 | | height | 顶栏高度 | string | — | 60px |`, 34 | "el-aside": `[element:${docUrl}/#/zh-CN/component/container](${docUrl}/#/zh-CN/component/container) \n 35 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 36 | | :--- | :--- | :--- | :--- | :--- | 37 | | width | 侧边栏宽度 | string | — | 300px |`, 38 | "el-footer": `[element:${docUrl}/#/zh-CN/component/layout](${docUrl}/#/zh-CN/component/layout) \n 39 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 40 | | :--- | :--- | :--- | :--- | :--- | 41 | | height | 底栏高度 | string | — | 60px |`, 42 | "el-button": `[element:${docUrl}/#/zh-CN/component/button](${docUrl}/#/zh-CN/component/button) \n 43 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 44 | | :--- | :--- | :--- | :--- | :--- | 45 | | size | 尺寸 | string | medium / small / mini | — | 46 | | type | 类型 | string | primary / success / warning / danger / info / text | — | 47 | | plain | 是否朴素按钮 | boolean | — | false | 48 | | round | 是否圆角按钮 | boolean | — | false | 49 | | circle | 是否圆形按钮 | boolean | — | false | 50 | | loading | 是否加载中状态 | boolean | — | false | 51 | | disabled | 是否禁用状态 | boolean | — | false | 52 | | icon | 图标类名 | string | — | — | 53 | | autofocus | 是否默认聚焦 | boolean | — | false | 54 | | native-type | 原生 type 属性 | string | button / submit / reset | button | 55 | | 事件名称 | 说明 | 回调参数 | 56 | | :--- | :--- | :--- | 57 | | click | 点击时触发事件 | event |`, 58 | "el-radio": `[element:${docUrl}/#/zh-CN/component/radio](${docUrl}/#/zh-CN/component/radio) \n 59 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 60 | | :--- | :--- | :--- | :--- | :--- | 61 | | label | Radio 的 value | string / number / boolean | — | — | 62 | | disabled | 是否禁用 | boolean | — | false | 63 | | border | 是否显示边框 | boolean | — | false | 64 | | size | Radio 的尺寸,仅在 border 为真时有效 | string | medium / small / mini | — | 65 | | name | 原生 name 属性 | string | — | — |\n 66 | | 事件名称 | 说明 | 回调参数 | 67 | | :--- | :--- | :--- | 68 | | change | 绑定值变化时触发的事件 | 选中的 Radio label 值 |`, 69 | "el-radio-group": `[element:${docUrl}/#/zh-CN/component/radio](${docUrl}/#/zh-CN/component/radio) \n 70 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 71 | | :--- | :--- | :--- | :--- | :--- | 72 | | size | 单选框组尺寸,仅对按钮形式的 Radio 或带有边框的 Radio 有效 | string | medium / small / mini | — | 73 | | disabled | 是否禁用 | boolean | — | false | 74 | | text-color | 按钮形式的 Radio 激活时的文本颜色 | string | — | #ffffff | 75 | | fill | 按钮形式的 Radio 激活时的填充色和边框色 | string | — | #409EFF | \n 76 | | 事件名称 | 说明 | 回调参数 | 77 | | :--- | :--- | :--- | 78 | | change | 绑定值变化时触发的事件 | 选中的 Radio label 值 |`, 79 | "el-radio-button": `[element:${docUrl}/#/zh-CN/component/radio](${docUrl}/#/zh-CN/component/radio) \n 80 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 81 | | :--- | :--- | :--- | :--- | :--- | 82 | | label | Radio 的 value | string / number | — | — | 83 | | disabled | 是否禁用 | boolean | — | false | 84 | | name | 原生 name 属性 | string | — | — |`, 85 | "el-checkbox": `[element:${docUrl}/#/zh-CN/component/checkbox](${docUrl}/#/zh-CN/component/checkbox) \n 86 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 87 | | :--- | :--- | :--- | :--- | :--- | 88 | | label | 选中状态的值(只有在checkbox-group或者绑定对象类型为array时有效) | string / number / boolean | — | — | 89 | | true-label | 选中时的值 | string / number | — | — | 90 | | false-label | 没有选中时的值 | string / number | — | — | 91 | | disabled | 是否禁用 | boolean | — | false | 92 | | border | 是否显示边框 | boolean | — | false | 93 | | size | Checkbox 的尺寸,仅在 border 为真时有效 | string | medium / small / mini | — | 94 | | name | 原生 name 属性 | string | — | — | 95 | | checked | 当前是否勾选 | boolean | — | false | 96 | | indeterminate | 设置 indeterminate 状态,只负责样式控制 | boolean | — | false | \n 97 | | 事件名称 | 说明 | 回调参数 | 98 | | :--- | :--- | :--- | 99 | | change | 当绑定值变化时触发的事件 | 更新后的值 |`, 100 | "el-checkbox-group": `[element:${docUrl}/#/zh-CN/component/checkbox](${docUrl}/#/zh-CN/component/checkbox) \n 101 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 102 | | :--- | :--- | :--- | :--- | :--- | 103 | | size | 多选框组尺寸,仅对按钮形式的 Checkbox 或带有边框的 Checkbox 有效 | string | medium / small / mini | — | 104 | | disabled | 是否禁用 | boolean | — | false | 105 | | min | 可被勾选的 checkbox 的最小数量 | number | — | — | 106 | | max | 可被勾选的 checkbox 的最大数量 | number | — | — | 107 | | text-color | 按钮形式的 Checkbox 激活时的文本颜色 | string | — | #ffffff | 108 | | fill | 按钮形式的 Checkbox 激活时的填充色和边框色 | string | — | #409EFF |\n 109 | | 事件名称 | 说明 | 回调参数 | 110 | | :--- | :--- | :--- | 111 | | change | 当绑定值变化时触发的事件 | 更新后的值 |`, 112 | "el-checkbox-button": `[element:${docUrl}/#/zh-CN/component/checkbox](${docUrl}/#/zh-CN/component/checkbox) \n 113 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 114 | | :--- | :--- | :--- | :--- | :--- | 115 | | label | 选中状态的值(只有在checkbox-group或者绑定对象类型为array时有效) | string / number / boolean | — | — | 116 | | true-label | 选中时的值 | string / number | — | — | 117 | | false-label | 没有选中时的值 | string / number | — | — | 118 | | disabled | 是否禁用 | boolean | — | false | 119 | | name | 原生 name 属性 | string | — | — | 120 | | checked | 当前是否勾选 | boolean | — | false |`, 121 | "el-input":`[element:${docUrl}/#/zh-CN/component/input](${docUrl}/#/zh-CN/component/input) \n 122 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 123 | | :--- | :--- | :--- | :--- | :--- | 124 | | type | 类型 | string | text,textarea 和其他 原生 input 的 type 值 | text | 125 | | value | 绑定值 | string / number | — | — | 126 | | maxlength | 原生属性,最大输入长度 | number | — | — | 127 | | minlength | 原生属性,最小输入长度 | number | — | — | 128 | | placeholder | 输入框占位文本 | string | — | — | 129 | | clearable | 是否可清空 | boolean | — | false | 130 | | disabled | 禁用 | boolean | — | false | 131 | | size | 输入框尺寸,只在 type!=\"textarea\" 时有效 | string | medium / small / mini | — | 132 | | prefix-icon | 输入框头部图标 | string | — | — | 133 | | suffix-icon | 输入框尾部图标 | string | — | — | 134 | | rows | 输入框行数,只对 type=\"textarea\" 有效 | number | — | 2 | 135 | | autosize | 自适应内容高度,只对 type=\"textarea\" 有效,可传入对象,如,{ minRows: 2, maxRows: 6 } | boolean / object | — | false | 136 | | autocomplete | 原生属性,自动补全 | string | on, off | off | 137 | | auto-complete | 下个主版本弃用 | string | on, off | off | 138 | | name | 原生属性 | string | — | — | 139 | | readonly | 原生属性,是否只读 | boolean | — | false | 140 | | max | 原生属性,设置最大值 | — | — | — | 141 | | min | 原生属性,设置最小值 | — | — | — | 142 | | step | 原生属性,设置输入字段的合法数字间隔 | — | — | — | 143 | | resize | 控制是否能被用户缩放 | string | none, both, horizontal, vertical | — | 144 | | autofocus | 原生属性,自动获取焦点 | boolean | true, false | false | 145 | | form | 原生属性 | string | — | — | 146 | | label | 输入框关联的label文字 | string | — | — | 147 | | tabindex | 输入框的tabindex | string | - | - |\n 148 | | name | 说明 | 149 | | :--- | :--- | 150 | | prefix | 输入框头部内容,只对 type=\"text\" 有效 | 151 | | suffix | 输入框尾部内容,只对 type=\"text\" 有效 | 152 | | prepend | 输入框前置内容,只对 type=\"text\" 有效 | 153 | | append | 输入框后置内容,只对 type=\"text\" 有效 |\n 154 | | 事件名称 | 说明 | 回调参数 | 155 | | :--- | :--- | :--- | 156 | | blur | 在 Input 失去焦点时触发 | (event: Event) | 157 | | focus | 在 Input 获得焦点时触发 | (event: Event) | 158 | | change | 在 Input 值改变时触发 | (value: string | number) | 159 | | clear | 在点击由 clearable 属性生成的清空按钮时触发 | — |\n 160 | | 方法名 | 说明 | 参数 | 161 | | :--- | :--- | :--- | 162 | | focus | 使 input 获取焦点 | — | 163 | | blur | 使 input 失去焦点 | — | 164 | | select | 选中 input 中的文字 | — |`, 165 | "el-autocomplete":`[element:${docUrl}/#/zh-CN/component/input](${docUrl}/#/zh-CN/component/input) \n 166 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 167 | | :--- | :--- | :--- | :--- | :--- | 168 | | placeholder | 输入框占位文本 | string | — | — | 169 | | disabled | 禁用 | boolean | — | false | 170 | | value-key | 输入建议对象中用于显示的键名 | string | — | value | 171 | | value | 必填值,输入绑定值 | string | — | — | 172 | | debounce | 获取输入建议的去抖延时 | number | — | 300 | 173 | | placement | 菜单弹出位置 | string | top / top-start / top-end / bottom / bottom-start / bottom-end | bottom-start | 174 | | fetch-suggestions | 返回输入建议的方法,仅当你的输入建议数据 resolve 时,通过调用 callback(data:[]) 来返回它 | Function(queryString, callback) | — | — | 175 | | popper-class | Autocomplete 下拉列表的类名 | string | — | — | 176 | | trigger-on-focus | 是否在输入框 focus 时显示建议列表 | boolean | — | true | 177 | | name | 原生属性 | string | — | — | 178 | | select-when-unmatched | 在输入没有任何匹配建议的情况下,按下回车是否触发 select 事件 | boolean | — | false | 179 | | label | 输入框关联的label文字 | string | — | — | 180 | | prefix-icon | 输入框头部图标 | string | — | — | 181 | | suffix-icon | 输入框尾部图标 | string | — | — | 182 | | hide-loading | 是否隐藏远程加载时的加载图标 | boolean | — | false | 183 | | popper-append-to-body | 是否将下拉列表插入至 body 元素。在下拉列表的定位出现问题时,可将该属性设置为 false | boolean | - | true |\n 184 | | name | 说明 | 185 | | :--- | :--- | 186 | | prefix | 输入框头部内容 | 187 | | suffix | 输入框尾部内容 | 188 | | prepend | 输入框前置内容 | 189 | | append | 输入框后置内容 |\n 190 | | name | 说明 | 191 | | :--- | :--- | 192 | | — | 自定义输入建议,参数为 { item } |\n 193 | | 事件名称 | 说明 | 回调参数 | 194 | | :--- | :--- | :--- | 195 | | select | 点击选中建议项时触发 | 选中建议项 |\n 196 | | 方法名 | 说明 | 参数 | 197 | | :--- | :--- | :--- | 198 | | focus | 使 input 获取焦点 | - |`, 199 | "el-input-number": `[element:${docUrl}/#/zh-CN/component/input-number](${docUrl}/#/zh-CN/component/input-number) \n 200 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 201 | | :--- | :--- | :--- | :--- | :--- | 202 | | value | 绑定值 | number | — | — | 203 | | min | 设置计数器允许的最小值 | number | — | -Infinity | 204 | | max | 设置计数器允许的最大值 | number | — | Infinity | 205 | | step | 计数器步长 | number | — | 1 | 206 | | precision | 数值精度 | number | — | — | 207 | | size | 计数器尺寸 | string | large, small | — | 208 | | disabled | 是否禁用计数器 | boolean | — | false | 209 | | controls | 是否使用控制按钮 | boolean | — | true | 210 | | controls-position | 控制按钮位置 | string | right | - | 211 | | name | 原生属性 | string | — | — | 212 | | label | 输入框关联的label文字 | string | — | — |\n 213 | | 事件名称 | 说明 | 回调参数 | 214 | | :--- | :--- | :--- | 215 | | change | 绑定值被改变时触发 | 最后变更的值 | 216 | | blur | 在组件 Input 失去焦点时触发 | (event: Event) | 217 | | focus | 在组件 Input 获得焦点时触发 | (event: Event)\n 218 | | 方法名 | 说明 | 参数 | 219 | | :--- | :--- | :--- | 220 | | focus | 使 input 获取焦点 | - |`, 221 | "el-select": `[element:${docUrl}/#/zh-CN/component/select](${docUrl}/#/zh-CN/component/select) \n 222 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 223 | | :--- | :--- | :--- | :--- | :--- | 224 | | multiple | 是否多选 | boolean | — | false | 225 | | disabled | 是否禁用 | boolean | — | false | 226 | | value-key | 作为 value 唯一标识的键名,绑定值为对象类型时必填 | string | — | value | 227 | | size | 输入框尺寸 | string | medium/small/mini | — | 228 | | clearable | 单选时是否可以清空选项 | boolean | — | false | 229 | | collapse-tags | 多选时是否将选中值按文字的形式展示 | boolean | — | false | 230 | | multiple-limit | 多选时用户最多可以选择的项目数,为 0 则不限制 | number | — | 0 | 231 | | name | select input 的 name 属性 | string | — | — | 232 | | autocomplete | select input 的 autocomplete 属性 | string | — | off | 233 | | auto-complete | 下个主版本弃用 | string | — | off | 234 | | placeholder | 占位符 | string | — | 请选择 | 235 | | filterable | 是否可搜索 | boolean | — | false | 236 | | allow-create | 是否允许用户创建新条目,需配合 filterable 使用 | boolean | — | false | 237 | | filter-method | 自定义搜索方法 | function | — | — | 238 | | remote | 是否为远程搜索 | boolean | — | false | 239 | | remote-method | 远程搜索方法 | function | — | — | 240 | | loading | 是否正在从远程获取数据 | boolean | — | false | 241 | | loading-text | 远程加载时显示的文字 | string | — | 加载中 | 242 | | no-match-text | 搜索条件无匹配时显示的文字 | string | — | 无匹配数据 | 243 | | no-data-text | 选项为空时显示的文字 | string | — | 无数据 | 244 | | popper-class | Select 下拉框的类名 | string | — | — | 245 | | reserve-keyword | 多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词 | boolean | — | false | 246 | | default-first-option | 在输入框按下回车,选择第一个匹配项。需配合 filterable 或 remote 使用 | boolean | - | false | 247 | | popper-append-to-body | 是否将弹出框插入至 body 元素。在弹出框的定位出现问题时,可将该属性设置为 false | boolean | - | true | 248 | | automatic-dropdown | 对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单 | boolean | - | false |\n 249 | | 事件名称 | 说明 | 回调参数 | 250 | | :--- | :--- | :--- | 251 | | change | 选中值发生变化时触发 | 目前的选中值 | 252 | | visible-change | 下拉框出现/隐藏时触发 | 出现则为 true,隐藏则为 false | 253 | | remove-tag | 多选模式下移除tag时触发 | 移除的tag值 | 254 | | clear | 可清空的单选模式下用户点击清空按钮时触发 | — | 255 | | blur | 当 input 失去焦点时触发 | (event: Event) | 256 | | focus | 当 input 获得焦点时触发 | (event: Event) |\n 257 | | name | 说明 | 258 | | :--- | :--- | 259 | | — | Option 组件列表 | 260 | | prefix | Select 组件头部内容 |`, 261 | "el-option-group": `[element:${docUrl}/#/zh-CN/component/select](${docUrl}/#/zh-CN/component/select) \n 262 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 263 | | :--- | :--- | :--- | :--- | :--- | 264 | | label | 分组的组名 | string | — | — | 265 | | disabled | 是否将该分组下所有选项置为禁用 | boolean | — | false |`, 266 | "el-option": `[element:${docUrl}/#/zh-CN/component/select](${docUrl}/#/zh-CN/component/select) \n 267 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 268 | | :--- | :--- | :--- | :--- | :--- | 269 | | value | 选项的值 | string/number/object | — | — | 270 | | label | 选项的标签,若不设置则默认与 value 相同 | string/number | — | — | 271 | | disabled | 是否禁用该选项 | boolean | — | false |`, 272 | "el-cascader": `[element:${docUrl}/#/zh-CN/component/cascader](${docUrl}/#/zh-CN/component/cascader) \n 273 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 274 | | :--- | :--- | :--- | :--- | :--- | 275 | | options | 可选项数据源,键名可通过 props 属性配置 | array | — | — | 276 | | props | 配置选项,具体见下表 | object | — | — | 277 | | value | 选中项绑定值 | array | — | — | 278 | | separator | 选项分隔符 | string | — | 斜杠'/' | 279 | | popper-class | 自定义浮层类名 | string | — | — | 280 | | placeholder | 输入框占位文本 | string | — | 请选择 | 281 | | disabled | 是否禁用 | boolean | — | false | 282 | | clearable | 是否支持清空选项 | boolean | — | false | 283 | | expand-trigger | 次级菜单的展开方式 | string | click / hover | click | 284 | | show-all-levels | 输入框中是否显示选中值的完整路径 | boolean | — | true | 285 | | filterable | 是否可搜索选项 | boolean | — | — | 286 | | debounce | 搜索关键词输入的去抖延迟,毫秒 | number | — | 300 | 287 | | change-on-select | 是否允许选择任意一级的选项 | boolean | — | false | 288 | | size | 尺寸 | string | medium / small / mini | — | 289 | | before-filter | 筛选之前的钩子,参数为输入的值,若返回 false 或者返回 Promise 且被 reject,则停止筛选 | function(value) | — | — |\n 290 | | 事件名称 | 说明 | 回调参数 | 291 | | :--- | :--- | :--- | 292 | | change | 当绑定值变化时触发的事件 | 当前值 | 293 | | active-item-change | 当父级选项变化时触发的事件,仅在 change-on-select 为 false 时可用 | 各父级选项组成的数组 | 294 | | blur | 在 Cascader 失去焦点时触发 | (event: Event) | 295 | | focus | 在 Cascader 获得焦点时触发 | (event: Event) |`, 296 | "el-switch": `[element:${docUrl}/#/zh-CN/component/switch](${docUrl}/#/zh-CN/component/switch) \n 297 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 298 | | :--- | :--- | :--- | :--- | :--- | 299 | | disabled | 是否禁用 | boolean | — | false | 300 | | width | switch 的宽度(像素) | number | — | 40 | 301 | | active-icon-class | switch 打开时所显示图标的类名,设置此项会忽略 active-text | string | — | — | 302 | | inactive-icon-class | switch 关闭时所显示图标的类名,设置此项会忽略 inactive-text | string | — | — | 303 | | active-text | switch 打开时的文字描述 | string | — | — | 304 | | inactive-text | switch 关闭时的文字描述 | string | — | — | 305 | | active-value | switch 打开时的值 | boolean / string / number | — | true | 306 | | inactive-value | switch 关闭时的值 | boolean / string / number | — | false | 307 | | active-color | switch 打开时的背景色 | string | — | #409EFF | 308 | | inactive-color | switch 关闭时的背景色 | string | — | #C0CCDA | 309 | | name | switch 对应的 name 属性 | string | — | — |\n 310 | | 事件名称 | 说明 | 回调参数 | 311 | | :--- | :--- | :--- | 312 | | change | switch 状态发生变化时的回调函数 | 新状态的值 |\n 313 | | 方法名 | 说明 | 参数 | 314 | | :--- | :--- | :--- | 315 | | focus | 使 Switch 获取焦点 | - |`, 316 | "el-slider": `[element:${docUrl}/#/zh-CN/component/slider](${docUrl}/#/zh-CN/component/slider) \n 317 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 318 | | :--- | :--- | :--- | :--- | :--- | 319 | | min | 最小值 | number | — | 0 | 320 | | max | 最大值 | number | — | 100 | 321 | | disabled | 是否禁用 | boolean | — | false | 322 | | step | 步长 | number | — | 1 | 323 | | show-input | 是否显示输入框,仅在非范围选择时有效 | boolean | — | false | 324 | | show-input-controls | 在显示输入框的情况下,是否显示输入框的控制按钮 | boolean | — | true | 325 | | input-size | 输入框的尺寸 | string | large / medium / small / mini | small | 326 | | show-stops | 是否显示间断点 | boolean | — | false | 327 | | show-tooltip | 是否显示 tooltip | boolean | — | true | 328 | | format-tooltip | 格式化 tooltip message | function(value) | — | — | 329 | | range | 是否为范围选择 | boolean | — | false | 330 | | vertical | 是否竖向模式 | boolean | — | false | 331 | | height | Slider 高度,竖向模式时必填 | string | — | — | 332 | | label | 屏幕阅读器标签 | string | — | — | 333 | | debounce | 输入时的去抖延迟,毫秒,仅在show-input等于true时有效 | number | — | 300 | 334 | | tooltip-class | tooltip 的自定义类名 | string | — | — |\n 335 | | 事件名称 | 说明 | 回调参数 | 336 | | :--- | :--- | :--- | 337 | | change | 值改变时触发(使用鼠标拖曳时,只在松开鼠标后触发) | 改变后的值 |`, 338 | "el-time-picker": `[element:${docUrl}/#/zh-CN/component/time-picker](${docUrl}/#/zh-CN/component/time-picker) \n 339 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 340 | | :--- | :--- | :--- | :--- | :--- | 341 | | readonly | 完全只读 | boolean | — | false | 342 | | disabled | 禁用 | boolean | — | false | 343 | | editable | 文本框可输入 | boolean | — | true | 344 | | clearable | 是否显示清除按钮 | boolean | — | true | 345 | | size | 输入框尺寸 | string | medium / small / mini | — | 346 | | placeholder | 非范围选择时的占位内容 | string | — | — | 347 | | start-placeholder | 范围选择时开始日期的占位内容 | string | — | — | 348 | | end-placeholder | 范围选择时开始日期的占位内容 | string | — | — | 349 | | is-range | 是否为时间范围选择,仅对有效 | boolean | — | false | 350 | | arrow-control | 是否使用箭头进行时间选择,仅对有效 | boolean | — | false | 351 | | value | 绑定值 | date(TimePicker) / string(TimeSelect) | — | — | 352 | | align | 对齐方式 | string | left / center / right | left | 353 | | popper-class | TimePicker 下拉框的类名 | string | — | — | 354 | | picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | 355 | | range-separator | 选择范围时的分隔符 | string | - | '-' | 356 | | value-format | 可选,仅TimePicker时可用,绑定值的格式。不指定则绑定值为 Date 对象 | string | 见日期格式 | — | 357 | | default-value | 可选,选择器打开时默认显示的时间 | Date(TimePicker) / string(TimeSelect) | 可被new Date()解析(TimePicker) / 可选值(TimeSelect) | — | 358 | | name | 原生属性 | string | — | — | 359 | | prefix-icon | 自定义头部图标的类名 | string | — | el-icon-time | 360 | | clear-icon | 自定义清空图标的类名 | string | — | el-icon-circle-close |\n 361 | | 事件名 | 说明 | 参数 | 362 | | :--- | :--- | :--- | 363 | | change | 用户确认选定的值时触发 | 组件绑定值 | 364 | | blur | 当 input 失去焦点时触发 | 组件实例 | 365 | | focus | 当 input 获得焦点时触发 | 组件实例 |\n 366 | | 方法名 | 说明 | 参数 | 367 | | :--- | :--- | :--- | 368 | | focus | 使 input 获取焦点 | - |`, 369 | "el-date-picker": `[element:${docUrl}/#/zh-CN/component/date-picker](${docUrl}/#/zh-CN/component/date-picker) \n 370 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 371 | | :--- | :--- | :--- | :--- | :--- | 372 | | readonly | 完全只读 | boolean | — | false | 373 | | disabled | 禁用 | boolean | — | false | 374 | | editable | 文本框可输入 | boolean | — | true | 375 | | clearable | 是否显示清除按钮 | boolean | — | true | 376 | | size | 输入框尺寸 | string | large, small, mini | — | 377 | | placeholder | 非范围选择时的占位内容 | string | — | — | 378 | | start-placeholder | 范围选择时开始日期的占位内容 | string | — | — | 379 | | end-placeholder | 范围选择时结束日期的占位内容 | string | — | — | 380 | | type | 显示类型 | string | year/month/date/dates/ week/datetime/datetimerange/daterange | date | 381 | | format | 显示在输入框中的格式 | string | 见日期格式 | yyyy-MM-dd | 382 | | align | 对齐方式 | string | left, center, right | left | 383 | | popper-class | DatePicker 下拉框的类名 | string | — | — | 384 | | picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | 385 | | range-separator | 选择范围时的分隔符 | string | — | '-' | 386 | | default-value | 可选,选择器打开时默认显示的时间 | Date | 可被new Date()解析 | — | 387 | | default-time | 范围选择时选中日期所使用的当日内具体时刻 | string[] | 数组,长度为 2,每项值为字符串,形如12:00:00,第一项指定开始日期的时刻,第二项指定结束日期的时刻,不指定会使用时刻 00:00:00 | — | 388 | | value-format | 可选,绑定值的格式。不指定则绑定值为 Date 对象 | string | 见日期格式 | — | 389 | | name | 原生属性 | string | — | — | 390 | | unlink-panels | 在范围选择器里取消两个日期面板之间的联动 | boolean | — | false | 391 | | prefix-icon | 自定义头部图标的类名 | string | — | el-icon-date | 392 | | clear-icon | 自定义清空图标的类名 | string | — | el-icon-circle-close |\n 393 | | 事件名称 | 说明 | 回调参数 | 394 | | :--- | :--- | :--- | 395 | | change | 用户确认选定的值时触发 | 组件绑定值。格式与绑定值一致,可受 value-format 控制 | 396 | | blur | 当 input 失去焦点时触发 | 组件实例 | 397 | | focus | 当 input 获得焦点时触发 | 组件实例 |\n 398 | | 方法名 | 说明 | 参数 | 399 | | :--- | :--- | :--- | 400 | | focus | 使 input 获取焦点 | — |`, 401 | "el-upload": `[element:${docUrl}/#/zh-CN/component/upload](${docUrl}/#/zh-CN/component/upload) \n 402 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 403 | | :--- | :--- | :--- | :--- | :--- | 404 | | action | 必选参数,上传的地址 | string | — | — | 405 | | headers | 设置上传的请求头部 | object | — | — | 406 | | multiple | 是否支持多选文件 | boolean | — | — | 407 | | data | 上传时附带的额外参数 | object | — | — | 408 | | name | 上传的文件字段名 | string | — | file | 409 | | with-credentials | 支持发送 cookie 凭证信息 | boolean | — | false | 410 | | show-file-list | 是否显示已上传文件列表 | boolean | — | true | 411 | | drag | 是否启用拖拽上传 | boolean | — | false | 412 | | accept | 接受上传的文件类型(thumbnail-mode 模式下此参数无效) | string | — | — | 413 | | on-preview | 点击文件列表中已上传的文件时的钩子 | function(file) | — | — | 414 | | on-remove | 文件列表移除文件时的钩子 | function(file, fileList) | — | — | 415 | | on-success | 文件上传成功时的钩子 | function(response, file, fileList) | — | — | 416 | | on-error | 文件上传失败时的钩子 | function(err, file, fileList) | — | — | 417 | | on-progress | 文件上传时的钩子 | function(event, file, fileList) | — | — | 418 | | on-change | 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 | function(file, fileList) | — | — | 419 | | before-upload | 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。 | function(file) | — | — | 420 | | before-remove | 删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止上传。 | function(file, fileList) | — | — | 421 | | list-type | 文件列表的类型 | string | text/picture/picture-card | text | 422 | | auto-upload | 是否在选取文件后立即进行上传 | boolean | — | true | 423 | | file-list | 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] | array | — | [] | 424 | | http-request | 覆盖默认的上传行为,可以自定义上传的实现 | function | — | — | 425 | | disabled | 是否禁用 | boolean | — | false | 426 | | limit | 最大允许上传个数 | number | — | — | 427 | | on-exceed | 文件超出个数限制时的钩子 | function(files, fileList) | — | - |\n 428 | | name | 说明 | 429 | | :--- | :--- | 430 | | trigger | 触发文件选择框的内容 | 431 | | tip | 提示说明文字 |\n 432 | | 方法名 | 说明 | 参数 | 433 | | :--- | :--- | :--- | 434 | | clearFiles | 清空已上传的文件列表(该方法不支持在 before-upload 中调用) | — | 435 | | abort | 取消上传请求 | ( file: fileList 中的 file 对象 ) | 436 | | submit | 手动上传文件列表 | — |`, 437 | "el-rate": `[element:${docUrl}/#/zh-CN/component/rate](${docUrl}/#/zh-CN/component/rate) \n 438 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 439 | | :--- | :--- | :--- | :--- | :--- | 440 | | max | 最大分值 | number | — | 5 | 441 | | disabled | 是否为只读 | boolean | — | false | 442 | | allow-half | 是否允许半选 | boolean | — | false | 443 | | low-threshold | 低分和中等分数的界限值,值本身被划分在低分中 | number | — | 2 | 444 | | high-threshold | 高分和中等分数的界限值,值本身被划分在高分中 | number | — | 4 | 445 | | colors | icon 的颜色数组,共有 3 个元素,为 3 个分段所对应的颜色 | array | — | ['#F7BA2A', '#F7BA2A', '#F7BA2A'] | 446 | | void-color | 未选中 icon 的颜色 | string | — | #C6D1DE | 447 | | disabled-void-color | 只读时未选中 icon 的颜色 | string | — | #EFF2F7 | 448 | | icon-classes | icon 的类名数组,共有 3 个元素,为 3 个分段所对应的类名 | array | — | ['el-icon-star-on', 'el-icon-star-on','el-icon-star-on'] | 449 | | void-icon-class | 未选中 icon 的类名 | string | — | el-icon-star-off | 450 | | disabled-void-icon-class | 只读时未选中 icon 的类名 | string | — | el-icon-star-on | 451 | | show-text | 是否显示辅助文字,若为真,则会从 texts 数组中选取当前分数对应的文字内容 | boolean | — | false | 452 | | show-score | 是否显示当前分数,show-score 和 show-text 不能同时为真 | boolean | — | false | 453 | | text-color | 辅助文字的颜色 | string | — | #1F2D3D | 454 | | texts | 辅助文字数组 | array | — | ['极差', '失望', '一般', '满意', '惊喜'] | 455 | | score-template | 分数显示模板 | string | — | {value} |\n 456 | | 事件名称 | 说明 | 回调参数 | 457 | | :--- | :--- | :--- | 458 | | change | 分值改变时触发 | 改变后的分值 |`, 459 | "el-color-picker": `[element:${docUrl}/#/zh-CN/component/color-picker](${docUrl}/#/zh-CN/component/color-picker) \n 460 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 461 | | :--- | :--- | :--- | :--- | :--- | 462 | | disabled | 是否禁用 | boolean | — | false | 463 | | size | 尺寸 | string | — | medium / small / mini | 464 | | show-alpha | 是否支持透明度选择 | boolean | — | false | 465 | | color-format | 写入 v-model 的颜色的格式 | string | hsl / hsv / hex / rgb | hex(show-alpha 为 false)/ rgb(show-alpha 为 true) | 466 | | popper-class | ColorPicker 下拉框的类名 | string | — | — | 467 | | predefine | 预定义颜色 | array | — | — |\n 468 | | 事件名称 | 说明 | 回调参数 | 469 | | :--- | :--- | :--- | 470 | | change | 当绑定值变化时触发 | 当前值 | 471 | | active-change | 面板中当前显示的颜色发生改变时触发 | 当前显示的颜色值 |`, 472 | "el-transfer": `[element:${docUrl}/#/zh-CN/component/transfer](${docUrl}/#/zh-CN/component/transfer) \n 473 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 474 | | :--- | :--- | :--- | :--- | :--- | 475 | | data | Transfer 的数据源 | array[{ key, label, disabled }] | — | [ ] | 476 | | filterable | 是否可搜索 | boolean | — | false | 477 | | filter-placeholder | 搜索框占位符 | string | — | 请输入搜索内容 | 478 | | filter-method | 自定义搜索方法 | function | — | — | 479 | | target-order | 右侧列表元素的排序策略:若为 original,则保持与数据源相同的顺序;若为 push,则新加入的元素排在最后;若为 unshift,则新加入的元素排在最前 | string | original / push / unshift | original | 480 | | titles | 自定义列表标题 | array | — | ['列表 1', '列表 2'] | 481 | | button-texts | 自定义按钮文案 | array | — | [ ] | 482 | | render-content | 自定义数据项渲染函数 | function(h, option) | — | — | 483 | | format | 列表顶部勾选状态文案 | object{noChecked, hasChecked} | — | { noChecked: '\${checked}/\${total}', hasChecked: '\${checked}/\${total}' } | 484 | | props | 数据源的字段别名 | object{key, label, disabled} | — | — | 485 | | left-default-checked | 初始状态下左侧列表的已勾选项的 key 数组 | array | — | [ ] | 486 | | right-default-checked | 初始状态下右侧列表的已勾选项的 key 数组 | array | — | [ ] |\n 487 | | name | 说明 | 488 | | :--- | :--- | 489 | | left-footer | 左侧列表底部的内容 | 490 | | right-footer | 右侧列表底部的内容 |\n 491 | | 方法名 | 说明 | 参数 | 492 | | :--- | :--- | :--- | 493 | | clearQuery | 清空某个面板的搜索关键词 | 'left' / 'right',指定需要清空的面板 |\n 494 | | 事件名称 | 说明 | 回调参数 | 495 | | :--- | :--- | :--- | 496 | | change | 右侧列表元素变化时触发 | 当前值、数据移动的方向('left' / 'right')、发生移动的数据 key 数组 | 497 | | left-check-change | 左侧列表元素被用户选中 / 取消选中时触发 | 当前被选中的元素的 key 数组、选中状态发生变化的元素的 key 数组 | 498 | | right-check-change | 右侧列表元素被用户选中 / 取消选中时触发 | 当前被选中的元素的 key 数组、选中状态发生变化的元素的 key 数组 |`, 499 | "el-form": `element:${docUrl}/#/zh-CN/component/form](${docUrl}/#/zh-CN/component/form) \n 500 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 501 | | :--- | :--- | :--- | :--- | :--- | 502 | | model | 表单数据对象 | object | — | — | 503 | | rules | 表单验证规则 | object | — | — | 504 | | inline | 行内表单模式 | boolean | — | false | 505 | | label-position | 表单域标签的位置,如果值为 left 或者 right 时,则需要设置 label-width | string | right/left/top | right | 506 | | label-width | 表单域标签的宽度,作为 Form 直接子元素的 form-item 会继承该值 | string | — | — | 507 | | label-suffix | 表单域标签的后缀 | string | — | — | 508 | | hide-required-asterisk | 是否显示必填字段的标签旁边的红色星号 | boolean | — | false | 509 | | show-message | 是否显示校验错误信息 | boolean | — | true | 510 | | inline-message | 是否以行内形式展示校验信息 | boolean | — | false | 511 | | status-icon | 是否在输入框中显示校验结果反馈图标 | boolean | — | false | 512 | | validate-on-rule-change | 是否在 rules 属性改变后立即触发一次验证 | boolean | — | true | 513 | | size | 用于控制该表单内组件的尺寸 | string | medium / small / mini | — | 514 | | disabled | 是否禁用该表单内的所有组件。若设置为 true,则表单内组件上的 disabled 属性不再生效 | boolean | — | false |\n 515 | | 方法名 | 说明 | 参数 | 516 | | :--- | :--- | :--- | 517 | | validate | 对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise | Function(callback: Function(boolean, object)) | 518 | | validateField | 对部分表单字段进行校验的方法 | Function(prop: string, callback: Function(errorMessage: string)) | 519 | | resetFields | 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 | — | 520 | | clearValidate | 移除表单项的校验结果。传入待移除的表单项的 prop 属性组成的数组,如不传则移除整个表单的校验结果 | Function(props: array) |\n 521 | | 事件名称 | 说明 | 回调参数 | 522 | | :--- | :--- | :--- | 523 | | validate | 任一表单项被校验后触发 | 被校验的表单项 prop 值,校验是否通过,错误消息(如果存在) |`, 524 | "el-form-item": `[element:${docUrl}/#/zh-CN/component/form](${docUrl}/#/zh-CN/component/form) \n 525 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 526 | | :--- | :--- | :--- | :--- | :--- | 527 | | prop | 表单域 model 字段,在使用 validate、resetFields 方法的情况下,该属性是必填的 | string | 传入 Form 组件的 model 中的字段 | — | 528 | | label | 标签文本 | string | — | — | 529 | | label-width | 表单域标签的的宽度,例如 '50px' | string | — | — | 530 | | required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | — | false | 531 | | rules | 表单验证规则 | object | — | — | 532 | | error | 表单域验证错误信息, 设置该值会使表单验证状态变为error,并显示该错误信息 | string | — | — | 533 | | show-message | 是否显示校验错误信息 | boolean | — | true | 534 | | inline-message | 以行内形式展示校验信息 | boolean | — | false | 535 | | size | 用于控制该表单域下组件的尺寸 | string | medium / small / mini | - |\n 536 | | name | 说明 | 537 | | :--- | :--- | 538 | | — | Form Item 的内容 | 539 | | label | 标签文本的内容 |\n 540 | | name | 说明 | 541 | | :--- | :--- | 542 | | error | 自定义表单校验信息的显示方式,参数为 { error } |\n 543 | | 方法名 | 说明 | 参数 | 544 | | :--- | :--- | :--- | 545 | | resetField | 对该表单项进行重置,将其值重置为初始值并移除校验结果 | - | 546 | | clearValidate | 移除该表单项的校验结果 | - |`, 547 | "el-table": `[element:${docUrl}/#/zh-CN/component/table](${docUrl}/#/zh-CN/component/table) \n 548 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 549 | | :--- | :--- | :--- | :--- | :--- | 550 | | data | 显示的数据 | array | — | — | 551 | | height | Table 的高度,默认为自动高度。如果 height 为 number 类型,单位 px;如果 height 为 string 类型,则这个高度会设置为 Table 的 style.height 的值,Table 的高度会受控于外部样式。 | string/number | — | — | 552 | | max-height | Table 的最大高度 | string/number | — | — | 553 | | stripe | 是否为斑马纹 table | boolean | — | false | 554 | | border | 是否带有纵向边框 | boolean | — | false | 555 | | size | Table 的尺寸 | string | medium / small / mini | — | 556 | | fit | 列的宽度是否自撑开 | boolean | — | true | 557 | | show-header | 是否显示表头 | boolean | — | true | 558 | | highlight-current-row | 是否要高亮当前行 | boolean | — | false | 559 | | current-row-key | 当前行的 key,只写属性 | String,Number | — | — | 560 | | row-class-name | 行的 className 的回调方法,也可以使用字符串为所有行设置一个固定的 className。 | Function({row, rowIndex})/String | — | — | 561 | | row-style | 行的 style 的回调方法,也可以使用一个固定的 Object 为所有行设置一样的 Style。 | Function({row, rowIndex})/Object | — | — | 562 | | cell-class-name | 单元格的 className 的回调方法,也可以使用字符串为所有单元格设置一个固定的 className。 | Function({row, column, rowIndex, columnIndex})/String | — | — | 563 | | cell-style | 单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 | Function({row, column, rowIndex, columnIndex})/Object | — | — | 564 | | header-row-class-name | 表头行的 className 的回调方法,也可以使用字符串为所有表头行设置一个固定的 className。 | Function({row, rowIndex})/String | — | — | 565 | | header-row-style | 表头行的 style 的回调方法,也可以使用一个固定的 Object 为所有表头行设置一样的 Style。 | Function({row, rowIndex})/Object | — | — | 566 | | header-cell-class-name | 表头单元格的 className 的回调方法,也可以使用字符串为所有表头单元格设置一个固定的 className。 | Function({row, column, rowIndex, columnIndex})/String | — | — | 567 | | header-cell-style | 表头单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有表头单元格设置一样的 Style。 | Function({row, column, rowIndex, columnIndex})/Object | — | — | 568 | | row-key | 行数据的 Key,用来优化 Table 的渲染;在使用 reserve-selection 功能的情况下,该属性是必填的。类型为 String 时,支持多层访问:user.info.id,但不支持 user.info[0].id,此种情况请使用 Function。 | Function(row)/String | — | — | 569 | | empty-text | 空数据时显示的文本内容,也可以通过 slot=\"empty\" 设置 | String | — | 暂无数据 | 570 | | default-expand-all | 是否默认展开所有行,当 Table 中存在 type=\"expand\" 的 Column 的时候有效 | Boolean | — | false | 571 | | expand-row-keys | 可以通过该属性设置 Table 目前的展开行,需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。 | Array | — | | 572 | | default-sort | 默认的排序列的 prop 和顺序。它的prop属性指定默认的排序的列,order指定默认排序的顺序 | Object | order: ascending, descending | 如果只指定了prop, 没有指定order, 则默认顺序是ascending | 573 | | tooltip-effect | tooltip effect 属性 | String | dark/light | | 574 | | show-summary | 是否在表尾显示合计行 | Boolean | — | false | 575 | | sum-text | 合计行第一列的文本 | String | — | 合计 | 576 | | summary-method | 自定义的合计计算方法 | Function({ columns, data }) | — | — | 577 | | span-method | 合并行或列的计算方法 | Function({ row, column, rowIndex, columnIndex }) | — | — | 578 | | select-on-indeterminate | 在多选表格中,当仅有部分行被选中时,点击表头的多选框时的行为。若为 true,则选中所有行;若为 false,则取消选择所有行 | Boolean | — | true |\n 579 | | 事件名 | 说明 | 参数 | 580 | | :--- | :--- | :--- | 581 | | select | 当用户手动勾选数据行的 Checkbox 时触发的事件 | selection, row | 582 | | select-all | 当用户手动勾选全选 Checkbox 时触发的事件 | selection | 583 | | selection-change | 当选择项发生变化时会触发该事件 | selection | 584 | | cell-mouse-enter | 当单元格 hover 进入时会触发该事件 | row, column, cell, event | 585 | | cell-mouse-leave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event | 586 | | cell-click | 当某个单元格被点击时会触发该事件 | row, column, cell, event | 587 | | cell-dblclick | 当某个单元格被双击击时会触发该事件 | row, column, cell, event | 588 | | row-click | 当某一行被点击时会触发该事件 | row, event, column | 589 | | row-contextmenu | 当某一行被鼠标右键点击时会触发该事件 | row, event | 590 | | row-dblclick | 当某一行被双击时会触发该事件 | row, event | 591 | | header-click | 当某一列的表头被点击时会触发该事件 | column, event | 592 | | header-contextmenu | 当某一列的表头被鼠标右键点击时触发该事件 | column, event | 593 | | sort-change | 当表格的排序条件发生变化的时候会触发该事件 | { column, prop, order } | 594 | | filter-change | 当表格的筛选条件发生变化的时候会触发该事件,参数的值是一个对象,对象的 key 是 column 的 columnKey,对应的 value 为用户选择的筛选条件的数组。 | filters | 595 | | current-change | 当表格的当前行发生变化的时候会触发该事件,如果要高亮当前行,请打开表格的 highlight-current-row 属性 | currentRow, oldCurrentRow | 596 | | header-dragend | 当拖动表头改变了列的宽度的时候会触发该事件 | newWidth, oldWidth, column, event | 597 | | expand-change | 当用户对某一行展开或者关闭的时候会触发该事件 | row, expandedRows |\n 598 | | 方法名 | 说明 | 参数 | 599 | | :--- | :--- | :--- | 600 | | clearSelection | 用于多选表格,清空用户的选择 | — | 601 | | toggleRowSelection | 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中) | row, selected | 602 | | toggleAllSelection | 用于多选表格,切换所有行的选中状态 | - | 603 | | toggleRowExpansion | 用于可展开表格,切换某一行的展开状态,如果使用了第二个参数,则是设置这一行展开与否(expanded 为 true 则展开) | row, expanded | 604 | | setCurrentRow | 用于单选表格,设定某一行为选中行,如果调用时不加参数,则会取消目前高亮行的选中状态。 | row | 605 | | clearSort | 用于清空排序条件,数据会恢复成未排序的状态 | — | 606 | | clearFilter | 用于清空过滤条件,数据会恢复成未过滤的状态 | — | 607 | | doLayout | 对 Table 进行重新布局。当 Table 或其祖先元素由隐藏切换为显示时,可能需要调用此方法 | — | 608 | | sort | 手动对 Table 进行排序。参数prop属性指定排序列,order指定排序顺序。 | prop: string, order: string |\n 609 | | name | 说明 | 610 | | :--- | :--- | 611 | | append | 插入至表格最后一行之后的内容,如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。 |`, 612 | "el-table-column": `[element:${docUrl}/#/zh-CN/component/table](${docUrl}/#/zh-CN/component/table) \n 613 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 614 | | :--- | :--- | :--- | :--- | :--- | 615 | | type | 对应列的类型。 selection 显示多选框,index 显示该行的索引, expand 显示可展开的按钮 | string | selection/index/expand | - | 616 | | index | 如果设置了 type=index,可以通过传递 index 属性来自定义索引 | number, Function(index) | - | - | 617 | | column-key | column 的 key,如果需要使用 filter-change 事件,则需要此属性标识是哪个 column 的筛选条件 | string | - | - | 618 | | label | 显示的标题 | string | - | - | 619 | | prop | 对应列内容的字段名,也可以使用 property 属性 | string | - | - | 620 | | width | 对应列的宽度 | string | - | - | 621 | | min-width | 对应列的最小宽度 | string | - | - | 622 | | fixed | 列是否固定在左侧或者右侧,true 表示固定在左侧 | string, boolean | true, left, right | - | 623 | | render-header | 列标题 Label 区域渲染使用的 Function | Function(h, { column, $index }) | - | - | 624 | | sortable | 对应列是否排序,'custom'代表希望远程排序,需监听Table的sort-change事件 | boolean, string | true, false, 'custom' | false | 625 | | sort-method | 排序使用方法,仅sortable为true时有效,需返回一数字 | Function(a, b) | - | - | 626 | | sort-by | 指定排序属性,sortable为true且没有sort-method有效。sort-by可为数组 | String/Array/Function(row, index) | - | - | 627 | | sort-orders | 轮转顺序,sortable为true有效。需传入一个数组 | array | ascending/descending/null | ['ascending', 'descending', null] | 628 | | resizable | 对应列是否可以通过拖动改变宽度(需要在 el-table 上设置 border 属性为真)| boolean | - | true | 629 | | formatter | 用来格式化内容 | Function(row, column, cellValue, index) | - | - | 630 | | show-overflow-tooltip | 当内容过长被隐藏时显示 tooltip | Boolean | - | false | 631 | | align | 对齐方式 | String | left/center/right | left | 632 | | header-align | 表头对齐方式 | String | left/center/right | - | 633 | | class-name | 列的className | string | — | — | 634 | | label-class-name | 当前列标题的自定义类名 | string | — | - | 635 | | selectable | 仅对 type=selection 的列有效,类型为 Function | Function(row, index) | — | — | 636 | | reserve-selection | 仅对 type=selection 的列有效,类型为 Boolean | Boolean | — | false | 637 | | filters | 数据过滤的选项,元素需要有 text 和 value 属性 | Array[{ text, value }] | | — | 638 | | filter-placement | 过滤弹出框的定位 | String | - | — | 639 | | filter-multiple | 数据过滤的选项是否多选 | Boolean | — | true | 640 | | filter-method | 数据过滤使用的方法 | Function(value, row, column) | — | — | 641 | | filtered-value | 选中的数据过滤项 | Array | — | — | 642 | | slot name | 说明 | 643 | | :--- | :--- | 644 | | — | 自定义列的内容,参数为 { row, column, $index } | 645 | | header | 自定义表头的内容. 参数为 { column, $index } | 646 | `, 647 | "el-tag": `[element:${docUrl}/#/zh-CN/component/tag](${docUrl}/#/zh-CN/component/tag) \n 648 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 649 | | :--- | :--- | :--- | :--- | :--- | 650 | | type | 主题 | string | success/info/warning/danger | — | 651 | | closable | 是否可关闭 | boolean | — | false | 652 | | disable-transitions | 是否禁用渐变动画 | boolean | — | false | 653 | | hit | 是否有边框描边 | boolean | — | false | 654 | | color | 背景色 | string | — | — | 655 | | size | 尺寸 | string | medium / small / mini | — |\n 656 | | 事件名称 | 说明 | 回调参数 | 657 | | :--- | :--- | :--- | 658 | | close | 关闭 Tag 时触发的事件 | — |`, 659 | "el-progress": `[element:${docUrl}/#/zh-CN/component/progress](${docUrl}/#/zh-CN/component/progress) \n 660 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 661 | | :--- | :--- | :--- | :--- | :--- | 662 | | percentage | 百分比(必填) | number | 0-100 | 0 | 663 | | type | 进度条类型 | string | line/circle | line | 664 | | stroke-width | 进度条的宽度,单位 px | number | — | 6 | 665 | | text-inside | 进度条显示文字内置在进度条内(只在 type=line 时可用) | boolean | — | false | 666 | | status | 进度条当前状态 | string | success/exception | — | 667 | | color | 进度条背景色(会覆盖 status 状态颜色) | string | — | — | 668 | | width | 环形进度条画布宽度(只在 type=circle 时可用) | number | | 126 | 669 | | show-text | 是否显示进度条文字内容 | boolean | — | true |`, 670 | "el-tree": `[element:${docUrl}/#/zh-CN/component/tree](${docUrl}/#/zh-CN/component/tree) \n 671 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 672 | | :--- | :--- | :--- | :--- | :--- | 673 | | data | 展示数据 | array | — | — | 674 | | empty-text | 内容为空的时候展示的文本 | String | — | — | 675 | | node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 | String | — | — | 676 | | props | 配置选项,具体看下表 | object | — | — | 677 | | render-after-expand | 是否在第一次展开某个树节点后才渲染其子节点 | boolean | — | true | 678 | | load | 加载子树数据的方法,仅当 lazy 属性为true 时生效 | function(node, resolve) | — | — | 679 | | render-content | 树节点的内容区的渲染 Function | Function(h, { node, data, store } | — | — | 680 | | highlight-current | 是否高亮当前选中节点,默认值是 false。 | boolean | — | false | 681 | | default-expand-all | 是否默认展开所有节点 | boolean | — | false | 682 | | expand-on-click-node | 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 | boolean | — | true | 683 | | check-on-click-node | 是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点。 | boolean | — | false | 684 | | auto-expand-parent | 展开子节点的时候是否自动展开父节点 | boolean | — | true | 685 | | default-expanded-keys | 默认展开的节点的 key 的数组 | array | — | — | 686 | | show-checkbox | 节点是否可被选择 | boolean | — | false | 687 | | check-strictly | 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false | boolean | — | false | 688 | | default-checked-keys | 默认勾选的节点的 key 的数组 | array | — | — | 689 | | filter-node-method | 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏 | Function(value, data, node) | — | — | 690 | | accordion | 是否每次只打开一个同级树节点展开 | boolean | — | false | 691 | | indent | 相邻级节点间的水平缩进,单位为像素 | number | — | 16 | 692 | | lazy | 是否懒加载子节点,需与 load 方法结合使用 | boolean | — | false | 693 | | draggable | 是否开启拖拽节点功能 | boolean | — | false | 694 | | allow-drag | 判断节点能否被拖拽 | Function(node) | — | — | 695 | | allow-drop | 拖拽时判定目标节点能否被放置。type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后 | Function(draggingNode, dropNode, type) | — | — |\n 696 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 697 | | :--- | :--- | :--- | :--- | :--- | 698 | | label | 指定节点标签为节点对象的某个属性值 | string, function(data, node) | — | — | 699 | | children | 指定子树为节点对象的某个属性值 | string | — | — | 700 | | disabled | 指定节点选择框是否禁用为节点对象的某个属性值 | boolean, function(data, node) | — | — | 701 | | isLeaf | 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效 | boolean, function(data, node) | — | — |`, 702 | "el-pagination": `[element:${docUrl}/#/zh-CN/component/pagination](${docUrl}/#/zh-CN/component/pagination) \n 703 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 704 | | :--- | :--- | :--- | :--- | :--- | 705 | | small | 是否使用小型分页样式 | boolean | — | false | 706 | | background | 是否为分页按钮添加背景色 | boolean | — | false | 707 | | page-size | 每页显示条目个数,支持 .sync 修饰符 | number | — | 10 | 708 | | total | 总条目数 | number | — | — | 709 | | page-count | 总页数,total 和 page-count 设置任意一个就可以达到显示页码的功能;如果要支持 page-sizes 的更改,则需要使用 total 属性 | Number | — | — | 710 | | pager-count | 页码按钮的数量,当总页数超过该值时会折叠 | number | 大于等于 5 且小于等于 21 的奇数 | 7 | 711 | | current-page | 当前页数,支持 .sync 修饰符 | number | — | 1 | 712 | | layout | 组件布局,子组件名用逗号分隔 | String | sizes, prev, pager, next, jumper, ->, total, slot | 'prev, pager, next, jumper, ->, total' | 713 | | page-sizes | 每页显示个数选择器的选项设置 | number[] | — | [10, 20, 30, 40, 50, 100] | 714 | | popper-class | 每页显示个数选择器的下拉框类名 | string | — | — | 715 | | prev-text | 替代图标显示的上一页文字 | string | — | — | 716 | | next-text | 替代图标显示的下一页文字 | string | — | — | 717 | | disabled | 是否禁用 | boolean | — | false |`, 718 | "el-badge": `[element:${docUrl}/#/zh-CN/component/badge](${docUrl}/#/zh-CN/component/badge) \n 719 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |\n| :--- | :--- | :--- | :--- | :--- | 720 | | value | 显示值 | string, number | — | — | 721 | | max | 最大值,超过最大值会显示 '{max}+',要求 value 是 Number 类型 | number | — | — | 722 | | is-dot | 小圆点 | boolean | — | false | 723 | | hidden | 隐藏 badge | boolean | — | false | 724 | | type | 类型 | string | primary / success / warning / danger / info | — `, 725 | "el-alert": `[element:${docUrl}/#/zh-CN/component/alert](${docUrl}/#/zh-CN/component/alert) \n 726 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 727 | | :--- | :--- | :--- | :--- | :--- | 728 | | title | 标题,必选参数 | string | — | — | 729 | | type | 主题 | string | success/warning/info/error | info | 730 | | description | 辅助性文字。也可通过默认 slot 传入 | string | — | — | 731 | | closable | 是否可关闭 | boolean | — | true | 732 | | center | 文字是否居中 | boolean | — | true | 733 | | close-text | 关闭按钮自定义文本 | string | — | — | 734 | | show-icon | 是否显示图标 | boolean | — | false |\n 735 | | 事件名称 | 说明 | 回调参数 | 736 | | :--- | :--- | :--- | 737 | | close | 关闭alert时触发的事件 | — |`, 738 | "el-menu": `[element:${docUrl}/#/zh-CN/component/menu](${docUrl}/#/zh-CN/component/menu) \n 739 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 740 | | :--- | :--- | :--- | :--- | :--- | 741 | | mode | 模式 | string | horizontal / vertical | vertical | 742 | | collapse | 是否水平折叠收起菜单(仅在 mode 为 vertical 时可用) | boolean | — | false | 743 | | background-color | 菜单的背景色(仅支持 hex 格式) | string | — | #ffffff | 744 | | text-color | 菜单的文字颜色(仅支持 hex 格式) | string | — | #303133 | 745 | | active-text-color | 当前激活菜单的文字颜色(仅支持 hex 格式) | string | — | #409EFF | 746 | | default-active | 当前激活菜单的 index | string | — | — | 747 | | default-openeds | 当前打开的 sub-menu 的 index 的数组 | Array | — | — | 748 | | unique-opened | 是否只保持一个子菜单的展开 | boolean | — | false | 749 | | menu-trigger | 子菜单打开的触发方式(只在 mode 为 horizontal 时有效) | string | hover / click | hover | 750 | | router | 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 | boolean | — | false | 751 | | collapse-transition | 是否开启折叠动画 | boolean | — | true |\n 752 | | 事件名称 | 说明 | 参数 | 753 | | :--- | :--- | :--- | 754 | | open | 展开指定的 sub-menu | index: 需要打开的 sub-menu 的 index | 755 | | close | 收起指定的 sub-menu | index: 需要收起的 sub-menu 的 index |\n 756 | | 事件名称 | 说明 | 回调参数 | 757 | | :--- | :--- | :--- | 758 | | select | 菜单激活回调 | index: 选中菜单项的 index, indexPath: 选中菜单项的 index path | 759 | | open | sub-menu 展开的回调 | index: 打开的 sub-menu 的 index, indexPath: 打开的 sub-menu 的 index path | 760 | | close | sub-menu 收起的回调 | index: 收起的 sub-menu 的 index, indexPath: 收起的 sub-menu 的 index path |`, 761 | "el-submenu": `[element:${docUrl}/#/zh-CN/component/menu](${docUrl}/#/zh-CN/component/menu) \n 762 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 763 | | :--- | :--- | :--- | :--- | :--- | 764 | | index | 唯一标志 | string | — | — | 765 | | popper-class | 弹出菜单的自定义类名 | string | — | — | 766 | | show-timeout | 展开 sub-menu 的延时 | number | — | 300 | 767 | | hide-timeout | 收起 sub-menu 的延时 | number | — | 300 | 768 | | disabled | 是否禁用 | boolean | — | false | 769 | | popper-append-to-body | 是否将弹出菜单插入至 body 元素。在菜单的定位出现问题时,可尝试修改该属性 | boolean | — | 一级子菜单:true / 非一级子菜单:false |`, 770 | "el-menu-item": `[element:${docUrl}/#/zh-CN/component/menu](${docUrl}/#/zh-CN/component/menu) \n 771 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 772 | | :--- | :--- | :--- | :--- | :--- | 773 | | index | 唯一标志 | string | — | — | 774 | | route | Vue Router 路径对象 | Object | — | — | 775 | | disabled | 是否禁用 | boolean | — | false |`, 776 | "el-menu-group": `[element:${docUrl}/#/zh-CN/component/menu](${docUrl}/#/zh-CN/component/menu) \n 777 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 778 | | :--- | :--- | :--- | :--- | :--- | 779 | | title | 分组标题 | string | — | — |`, 780 | "el-tabs": `[element:${docUrl}/#/zh-CN/component/tabs](${docUrl}/#/zh-CN/component/tabs) \n 781 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 782 | | :--- | :--- | :--- | :--- | :--- | 783 | | type | 风格类型 | string | card/border-card | — | 784 | | closable | 标签是否可关闭 | boolean | — | false | 785 | | addable | 标签是否可增加 | boolean | — | false | 786 | | editable | 标签是否同时可增加和关闭 | boolean | — | false | 787 | | value | 绑定值,选中选项卡的 name | string | — | 第一个选项卡的 name | 788 | | tab-position | 选项卡所在位置 | string | top/right/bottom/left | top | 789 | | stretch | 标签的宽度是否自撑开 | boolean | - | false | 790 | | before-leave | 切换标签之前的钩子,若返回 false 或者返回 Promise 且被 reject,则阻止切换。 | Function(activeName, oldActiveName) | — | — |\n 791 | | 事件名称 | 说明 | 回调参数 | 792 | | :--- | :--- | :--- | 793 | | tab-click | tab 被选中时触发 | 被选中的标签 tab 实例 | 794 | | tab-remove | 点击 tab 移除按钮后触发 | 被删除的标签的 name | 795 | | tab-add | 点击 tabs 的新增按钮后触发 | — | 796 | | edit | 点击 tabs 的新增按钮或 tab 被关闭后触发 | (targetName, action) |`, 797 | "el-tab-pane": `[element:${docUrl}/#/zh-CN/component/tabs](${docUrl}/#/zh-CN/component/tabs) \n 798 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 799 | | :--- | :--- | :--- | :--- | :--- | 800 | | label | 选项卡标题 | string | — | — | 801 | | disabled | 是否禁用 | boolean | — | false | 802 | | name | 与选项卡 activeName 对应的标识符,表示选项卡别名 | string | — | 该选项卡在选项卡列表中的顺序值,如第一个选项卡则为'1' | 803 | | closable | 标签是否可关闭 | boolean | — | false | 804 | | lazy | 标签是否延迟渲染 | boolean | — | false |`, 805 | "el-breadcrumb": `[element:${docUrl}/#/zh-CN/component/breadcrumb](${docUrl}/#/zh-CN/component/breadcrumb) \n 806 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 807 | | :--- | :--- | :--- | :--- | :--- | 808 | | separator | 分隔符 | string | — | 斜杠'/' | 809 | | separator-class | 图标分隔符 class | string | — | - | 810 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 811 | | :--- | :--- | :--- | :--- | :--- | 812 | | to | 路由跳转对象,同 vue-router 的 to | string/object | — | — | 813 | | replace | 在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录 | boolean | — | false |`, 814 | "el-dropdown": `[element:${docUrl}/#/zh-CN/component/dropdown](${docUrl}/#/zh-CN/component/dropdown) \n 815 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 816 | | :--- | :--- | :--- | :--- | :--- | 817 | | type | 菜单按钮类型,同 Button 组件(只在split-button为 true 的情况下有效) | string | — | — | 818 | | size | 菜单尺寸,在split-button为 true 的情况下也对触发按钮生效 | string | medium / small / mini | — | 819 | | split-button | 下拉触发元素呈现为按钮组 | boolean | — | false | 820 | | placement | 菜单弹出位置 | string | top/top-start/top-end/bottom/bottom-start/bottom-end | bottom-end | 821 | | trigger | 触发下拉的行为 | string | hover, click | hover | 822 | | hide-on-click | 是否在点击菜单项后隐藏菜单 | boolean | — | true | 823 | | show-timeout | 展开下拉菜单的延时(仅在 trigger 为 hover 时有效) | number | — | 250 | 824 | | hide-timeout | 收起下拉菜单的延时(仅在 trigger 为 hover 时有效) | number | — | 150 |\n 825 | | 事件名称 | 说明 | 回调参数 | 826 | | :--- | :--- | :--- | 827 | | click | split-button 为 true 时,点击左侧按钮的回调 | — | 828 | | command | 点击菜单项触发的事件回调 | dropdown-item 的指令 | 829 | | visible-change | 下拉框出现/隐藏时触发 | 出现则为 true,隐藏则为 false |\n 830 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 831 | | :--- | :--- | :--- | :--- | :--- | 832 | | command | 指令 | string/number/object | — | — | 833 | | disabled | 禁用 | boolean | — | false | 834 | | divided | 显示分割线 | boolean | — | false |`, 835 | "el-steps": `[element:${docUrl}/#/zh-CN/component/steps](${docUrl}/#/zh-CN/component/steps) \n 836 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 837 | | :--- | :--- | :--- | :--- | :--- | 838 | | space | 每个 step 的间距,不填写将自适应间距。支持百分比。 | number / string | — | — | 839 | | direction | 显示方向 | string | vertical/horizontal | horizontal | 840 | | active | 设置当前激活步骤 | number | — | 0 | 841 | | process-status | 设置当前步骤的状态 | string | wait / process / finish / error / success | process | 842 | | finish-status | 设置结束步骤的状态 | string | wait / process / finish / error / success | finish | 843 | | align-center | 进行居中对齐 | boolean | - | false | 844 | | simple | 是否应用简洁风格 | boolean | - | false |`, 845 | "el-step": `[element:${docUrl}/#/zh-CN/component/steps](${docUrl}/#/zh-CN/component/steps) \n 846 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 847 | | :--- | :--- | :--- | :--- | :--- | 848 | | title | 标题 | string | — | — | 849 | | description | 描述性文字 | string | — | — | 850 | | icon | 图标 | 传入 icon 的 class 全名来自定义 icon,也支持 slot 方式写入 | string | — | 851 | | status | 设置当前步骤的状态,不设置则根据 steps 确定状态 | wait / process / finish / error / success | - | - |\n 852 | | name | 说明 | 853 | | :--- | :--- | 854 | | icon | 图标 | 855 | | title | 标题 | 856 | | description | 描述性文字 |`, 857 | "el-dialog": `[element:${docUrl}/#/zh-CN/component/dialog](${docUrl}/#/zh-CN/component/dialog) \n 858 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 859 | | :--- | :--- | :--- | :--- | :--- | 860 | | visible | 是否显示 Dialog,支持 .sync 修饰符 | boolean | — | false | 861 | | title | Dialog 的标题,也可通过具名 slot (见下表)传入 | string | — | — | 862 | | width | Dialog 的宽度 | string | — | 50% | 863 | | fullscreen | 是否为全屏 Dialog | boolean | — | false | 864 | | top | Dialog CSS 中的 margin-top 值 | string | — | 15vh | 865 | | modal | 是否需要遮罩层 | boolean | — | true | 866 | | modal-append-to-body | 遮罩层是否插入至 body 元素上,若为 false,则遮罩层会插入至 Dialog 的父元素上 | boolean | — | true | 867 | | append-to-body | Dialog 自身是否插入至 body 元素上。嵌套的 Dialog 必须指定该属性并赋值为 true | boolean | — | false | 868 | | lock-scroll | 是否在 Dialog 出现时将 body 滚动锁定 | boolean | — | true | 869 | | custom-class | Dialog 的自定义类名 | string | — | — | 870 | | close-on-click-modal | 是否可以通过点击 modal 关闭 Dialog | boolean | — | true | 871 | | close-on-press-escape | 是否可以通过按下 ESC 关闭 Dialog | boolean | — | true | 872 | | show-close | 是否显示关闭按钮 | boolean | — | true | 873 | | before-close | 关闭前的回调,会暂停 Dialog 的关闭 | function(done),done 用于关闭 Dialog | — | — | 874 | | center | 是否对头部和底部采用居中布局 | boolean | — | false |\n 875 | | name | 说明 | 876 | | :--- | :--- | 877 | | — | Dialog 的内容 | 878 | | title | Dialog 标题区的内容 | 879 | | footer | Dialog 按钮操作区的内容 |\n 880 | | 事件名称 | 说明 | 回调参数 | 881 | | :--- | :--- | :--- | 882 | | open | Dialog 打开的回调 | — | 883 | | opened | Dialog 打开动画结束时的回调 | — | 884 | | close | Dialog 关闭的回调 | — | 885 | | closed | Dialog 关闭动画结束时的回调 | — |`, 886 | "el-tooltip": `[element:${docUrl}/#/zh-CN/component/tooltip](${docUrl}/#/zh-CN/component/tooltip) \n 887 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 888 | | :--- | :--- | :--- | :--- | :--- | 889 | | effect | 默认提供的主题 | String | dark/light | dark | 890 | | content | 显示的内容,也可以通过 slot#content 传入 DOM | String | — | — | 891 | | placement | Tooltip 的出现位置 | String | top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end | bottom | 892 | | value(v-model) | 状态是否可见 | Boolean | — | false | 893 | | disabled | Tooltip 是否可用 | Boolean | — | false | 894 | | offset | 出现位置的偏移量 | Number | — | 0 | 895 | | transition | 定义渐变动画 | String | — | el-fade-in-linear | 896 | | visible-arrow | 是否显示 Tooltip 箭头,更多参数可见Vue-popper | Boolean | — | true | 897 | | popper-options | popper.js 的参数 | Object | 参考 popper.js 文档 | { boundariesElement: 'body', gpuAcceleration: false } | 898 | | open-delay | 延迟出现,单位毫秒 | Number | — | 0 | 899 | | manual | 手动控制模式,设置为 true 后,mouseenter 和 mouseleave 事件将不会生效 | Boolean | — | false | 900 | | popper-class | 为 Tooltip 的 popper 添加类名 | String | — | — | 901 | | enterable | 鼠标是否可进入到 tooltip 中 | Boolean | — | true | 902 | | hide-after | Tooltip 出现后自动隐藏延时,单位毫秒,为 0 则不会自动隐藏 | number | — | 0 |`, 903 | "el-popover": `[element:${docUrl}/#/zh-CN/component/popover](${docUrl}/#/zh-CN/component/popover) \n 904 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 905 | | :--- | :--- | :--- | :--- | :--- | 906 | | trigger | 触发方式 | String | click/focus/hover/manual | click | 907 | | title | 标题 | String | — | — | 908 | | content | 显示的内容,也可以通过 slot 传入 DOM | String | — | — | 909 | | width | 宽度 | String, Number | — | 最小宽度 150px | 910 | | placement | 出现位置 | String | top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end | bottom | 911 | | disabled | Popover 是否可用 | Boolean | — | false | 912 | | value(v-model) | 状态是否可见 | Boolean | — | false | 913 | | offset | 出现位置的偏移量 | Number | — | 0 | 914 | | transition | 定义渐变动画 | String | — | fade-in-linear | 915 | | visible-arrow | 是否显示 Tooltip 箭头,更多参数可见Vue-popper | Boolean | — | true | 916 | | popper-options | popper.js 的参数 | Object | 参考 popper.js 文档 | { boundariesElement: 'body', gpuAcceleration: false } | 917 | | popper-class | 为 popper 添加类名 | String | — | — | 918 | | open-delay | 触发方式为 hover 时的显示延迟,单位为毫秒 | Number | — | — | \n 919 | | 参数 | 说明 | 920 | | :--- | :--- | 921 | | — | Popover 内嵌 HTML 文本 | 922 | | reference | 触发 Popover 显示的 HTML 元素 |\n 923 | | 事件名称 | 说明 | 回调参数 | 924 | | :--- | :--- | :--- | 925 | | show | 显示时触发 | — | 926 | | after-enter | 显示动画播放完毕后触发 | — | 927 | | hide | 隐藏时触发 | — | 928 | | after-leave | 隐藏动画播放完毕后触发 | — |`, 929 | "el-card": `[element:${docUrl}/#/zh-CN/component/card](${docUrl}/#/zh-CN/component/card) \n 930 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 931 | | :--- | :--- | :--- | :--- | :--- | 932 | | header | 设置 header,也可以通过 slot#header 传入 DOM | string | — | — | 933 | | body-style | 设置 body 的样式 | object | — | { padding: '20px' } | 934 | | shadow | 设置阴影显示时机 | string | always / hover / never | always |`, 935 | "el-carousel": `[element:${docUrl}/#/zh-CN/component/carousel](${docUrl}/#/zh-CN/component/carousel) \n 936 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 937 | | :--- | :--- | :--- | :--- | :--- | 938 | | height | 走马灯的高度 | string | — | — | 939 | | initial-index | 初始状态激活的幻灯片的索引,从 0 开始 | number | — | 0 | 940 | | trigger | 指示器的触发方式 | string | click | — | 941 | | autoplay | 是否自动切换 | boolean | — | true | 942 | | interval | 自动切换的时间间隔,单位为毫秒 | number | — | 3000 | 943 | | indicator-position | 指示器的位置 | string | outside/none | — | 944 | | arrow | 切换箭头的显示时机 | string | always/hover/never | hover | 945 | | type | 走马灯的类型 | string | card | — |\n 946 | | 事件名称 | 说明 | 回调参数 | 947 | | :--- | :--- | :--- | 948 | | change | 幻灯片切换时触发 | 目前激活的幻灯片的索引,原幻灯片的索引 | 949 | | 方法名 | 说明 | 参数 | 950 | | :--- | :--- | :--- | 951 | | setActiveItem | 手动切换幻灯片 | 需要切换的幻灯片的索引,从 0 开始;或相应 el-carousel-item 的 name 属性值 | 952 | | prev | 切换至上一张幻灯片 | — | 953 | | next | 切换至下一张幻灯片 | — |`, 954 | "el-carousel-item": `[element:${docUrl}/#/zh-CN/component/carousel](${docUrl}/#/zh-CN/component/carousel) \n 955 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 956 | | :--- | :--- | :--- | :--- | :--- | 957 | | name | 幻灯片的名字,可用作 setActiveItem 的参数 | string | — | — | 958 | | label | 该幻灯片所对应指示器的文本 | string | — | — |`, 959 | "el-collapse": `[element:${docUrl}/#/zh-CN/component/collapse](${docUrl}/#/zh-CN/component/collapse) \n 960 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 961 | | :--- | :--- | :--- | :--- | :--- | 962 | | accordion | 是否手风琴模式 | boolean | — | false | 963 | | value | 当前激活的面板(如果是手风琴模式,绑定值类型需要为string,否则为array) | string/array | — | — |\n 964 | | 事件名称 | 说明 | 回调参数 | 965 | | :--- | :--- | :--- | 966 | | change | 当前激活面板改变时触发(如果是手风琴模式,参数 activeNames 类型为string,否则为array) | (activeNames: array|string) |\n 967 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 968 | | :--- | :--- | :--- | :--- | :--- | 969 | | name | 唯一标志符 | string/number | — | — | 970 | | title | 面板标题 | string | — | — |`, 971 | "el-timeline": `[element:https://element.eleme.cn/#/zh-CN/component/timeline](https://element.eleme.cn/#/zh-CN/component/timeline) \n 972 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 973 | | :--- | :--- | :--- | :--- | :--- | 974 | | reverse | 指定节点排序方向,默认为正序 | boolean | — | false |`, 975 | "el-timeline-item": `[element:https://element.eleme.cn/#/zh-CN/component/timeline](https://element.eleme.cn/#/zh-CN/component/timeline) \n 976 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 977 | | :--- | :--- | :--- | :--- | :--- | 978 | | timestamp | 时间戳 | string | — | - | 979 | | hide-timestamp | 是否隐藏时间戳 | boolean | — | false | 980 | | placement | 时间戳位置 | string | top / bottom | bottom | 981 | | type | 节点类型 | string | primary / success / warning / danger / info | - | 982 | | color | 节点颜色 | string | hsl / hsv / hex / rgb | - | 983 | | size | 节点尺寸 | string | normal / large | - | 984 | | icon | 节点图标 | string | — | - |\n 985 | | name | 说明 | 986 | | :--- | :--- | 987 | | - | Timeline-Item 的内容 | 988 | | dot | 自定义节点 |`, 989 | "el-divider": `[element:https://element.eleme.cn/#/zh-CN/component/divider](https://element.eleme.cn/#/zh-CN/component/divider) \n 990 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 991 | | :--- | :--- | :--- | :--- | :--- | 992 | | direction | 设置分割线方向 | string | horizontal / vertical | horizontal | 993 | | content-position | 设置分割线文案的位置 | string | left / right / center | center |`, 994 | "el-calendar": `[element:https://element.eleme.cn/#/zh-CN/component/calendar](https://element.eleme.cn/#/zh-CN/component/calendar) \n 995 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 996 | | :--- | :--- | :--- | :--- | :--- | 997 | | value / v-model | 绑定值 | string | Date/string/number | - | 998 | | range | 时间范围,包括开始时间与结束时间.开始时间必须是周一,结束时间必须是周日,且时间跨度不能超过两个月。 | Array | - | 999 | | first-day-of-week | 周起始日 | Number | 1 到 7 | 1 |`, 1000 | "el-image": `[element:https://element.eleme.cn/#/zh-CN/component/image](https://element.eleme.cn/#/zh-CN/component/image) \n 1001 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 1002 | | :--- | :--- | :--- | :--- | :--- | 1003 | | src | 图片源,同原生 | string | - | - | 1004 | | fit | 确定图片如何适应容器框,同原生 object-fit | string | fill / contain / cover / none / scale-down | - | 1005 | | alt | 原生 alt | string | - | - | 1006 | | referrer-policy | 原生 referrerPolicy | string | - | - | 1007 | | lazy | 是否开启懒加载 | boolean | - | false | 1008 | | scroll-container | 开启懒加载后,监听 scroll 事件的容器 | string / HTMLElement | - | 最近一个 overflow 值为 auto 或 scroll 的父元素 | 1009 | | preview-src-list | 开启图片预览功能 | Array | - | - | 1010 | | z-index | 设置图片预览的 z-index | Number | - | - |\n 1011 | | 事件名称 | 说明 | 回调函数 | 1012 | | :--- | :--- | :--- | 1013 | | load | 图片加载成功触发 | (e: Event) | 1014 | | error | 图片加载失败触发 | (e: Error) |\n 1015 | | slot名称 | 说明 | 1016 | | :--- | :--- | 1017 | | placeholder | 图片未加载的占位内容 | 1018 | | error | 加载失败的内容 |`, 1019 | "el-backtop": `[element:https://element.eleme.cn/#/zh-CN/component/backtop](https://element.eleme.cn/#/zh-CN/component/backtop) \n 1020 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 1021 | | :--- | :--- | :--- | :--- | :--- | 1022 | | target | 触发滚动的对象 | string | Date/string/number | - | 1023 | | visibility-height | 滚动高度达到此参数值才出现 | number | 200 | 1024 | | right | 控制其显示位置, 距离页面右边距 | number | 40 | 1025 | | bottom | 控制其显示位置, 距离页面底部距离 | number | 40 |\n 1026 | | 事件 | 说明 | 回调参数 | 1027 | | :--- | :--- | :--- | 1028 | | click | 点击按钮触发的事件 | 点击事件 |`, 1029 | "el-drawer": `[element:https://element.eleme.cn/#/zh-CN/component/drawer](https://element.eleme.cn/#/zh-CN/component/drawer) \n 1030 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 1031 | | :--- | :--- | :--- | :--- | :--- | 1032 | | append-to-body | Drawer 自身是否插入至 body 元素上。嵌套的 Drawer 必须指定该属性并赋值为 true | boolean | - | false | 1033 | | before-close | 关闭前的回调,会暂停 Drawer 的关闭 | function(done),done 用于关闭 Drawer | - | - | 1034 | | close-on-press-escape | 是否可以通过按下 ESC 关闭 Drawer | boolean | - | true | 1035 | | custom-class | Drawer 的自定义类名 | string | - | - | 1036 | | destroy-on-close | 控制是否在关闭 Drawer 之后将子元素全部销毁 | boolean | - | false | 1037 | | modal | 是否需要遮罩层 | boolean | - | true | 1038 | | modal-append-to-body | 遮罩层是否插入至 body 元素上,若为 false,则遮罩层会插入至 Drawer 的父元素上 | boolean | - | true | 1039 | | direction | Drawer 打开的方向 | Direction | rtl / ltr / ttb / tbb | rtl | 1040 | | show-close | 是否显示关闭按钮 | boolean | - | true | 1041 | | size | Drawer 窗体的大小, 当使用 number 类型时, 以像素为单位, 当使用 string 类型时, 请传入 'x%', 否则便会以 number 类型解释 | number / string | - | 30% | 1042 | | title | Drawer 的标题,也可通过具名 slot (见下表)传入 | string | - | - | 1043 | | visible | 是否显示 Drawer,支持 .sync 修饰符 | boolean | - | false | 1044 | | wrapperClosable | 点击遮罩层是否可以关闭 Drawer | boolean | - | true |\n 1045 | | slot名称 | 说明 | 1046 | | :--- | :--- | 1047 | | - | Drawer 的内容 | 1048 | | title | Drawer 标题区的内容 |\n 1049 | | 事件 | 说明 | 回调参数 | 1050 | | :--- | :--- | :--- | 1051 | | open | Drawer 打开的回调 | - | 1052 | | opened | Drawer 打开动画结束时的回调 | - | 1053 | | close | Drawer 关闭的回调 | - | 1054 | | closed | Drawer 关闭动画结束时的回调 | - |`, 1055 | } 1056 | } -------------------------------------------------------------------------------- /src/frameworks/element-ui/globalAttribute.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | return { 3 | "v-loading": { 4 | type: 'attribute', 5 | values: ["string"], 6 | description: 'el-loading' 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/frameworks/element-ui/index.ts: -------------------------------------------------------------------------------- 1 | import jsTag from './jsTag' 2 | import tag from './tag' 3 | import globalAttribute from './globalAttribute' 4 | import attribute from './attribute' 5 | import document from './document' 6 | 7 | export default { 8 | tag, 9 | jsTag, 10 | globalAttribute, 11 | attribute, 12 | document 13 | } -------------------------------------------------------------------------------- /src/frameworks/element-ui/jsTag.ts: -------------------------------------------------------------------------------- 1 | export default (tabSize: string) => { 2 | return { 3 | "el-alert": `this.$alert('这是一段内容', '标题名称', { 4 | ${tabSize}confirmButtonText: '确定', 5 | ${tabSize}callback: action => { 6 | ${tabSize}${tabSize} 7 | ${tabSize}} 8 | })`, 9 | "el-conform": `this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { 10 | ${tabSize}confirmButtonText: '确定', 11 | ${tabSize}cancelButtonText: '取消', 12 | ${tabSize}type: 'warning' 13 | }).then(() => { 14 | ${tabSize}this.$message({ 15 | ${tabSize}${tabSize}type: 'success', 16 | ${tabSize}${tabSize}message: '删除成功!' 17 | ${tabSize}}) 18 | }).catch(() => { 19 | ${tabSize}this.$message({ 20 | ${tabSize}${tabSize}type: 'info', 21 | ${tabSize}${tabSize}message: '已取消删除' 22 | ${tabSize}}) 23 | })`, 24 | "el-form:clear": `this.$refs['\${1:form}'].clearValidate()`, 25 | "el-form:valid": `this.$refs['\${1:formName}'].validate((valid) => { 26 | ${tabSize}if (valid) { 27 | ${tabSize}${tabSize}$2 28 | ${tabSize}} else { 29 | ${tabSize}${tabSize}return false 30 | ${tabSize}} 31 | })`, 32 | "el-message": `this.$message({ 33 | ${tabSize}message: '恭喜你,这是一条成功消息', 34 | ${tabSize}type: 'success' 35 | })`, 36 | "el-message:close": `this.$message({ 37 | ${tabSize}message: '恭喜你,这是一条成功消息', 38 | ${tabSize}showClose: true, 39 | ${tabSize}type: 'success' 40 | })`, 41 | "el-notify": `this.$notify({ 42 | ${tabSize}title: '标题名称', 43 | ${tabSize}message: h('i', {style: 'color: teal'}, 'notify') 44 | })`, 45 | "el-notify:noclose": `this.$notify({ 46 | ${tabSize}title: '提示', 47 | ${tabSize}message: '不会自动关闭的消息', 48 | ${tabSize}duration: 0 49 | })`, 50 | "el-notify:success": `this.$notify({ 51 | ${tabSize}title: '成功', 52 | ${tabSize}message: '这是一条成功的提示消息', 53 | ${tabSize}type: 'success' 54 | })`, 55 | "el-prompt": `this.$prompt('请输入邮箱', '提示', { 56 | ${tabSize}confirmButtonText: '确定', 57 | ${tabSize}cancelButtonText: '取消', 58 | ${tabSize}inputPattern: '', 59 | ${tabSize}inputErrorMessage: '' 60 | }).then(({ value }) => { 61 | ${tabSize} 62 | }).catch(() => { 63 | ${tabSize} 64 | });`, 65 | "el-rules:array": `{ type: 'array', required: true, message: '请至少选择一个', trigger: 'change' }`, 66 | "el-rules:date": `{ type: 'date', required: true, message: '请选择日期', trigger: 'change' }`, 67 | "el-rules:minMax": `{require: true, min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }`, 68 | "el-rules:required": `{required: true, message:'请输入', trigger: 'blur'}`, 69 | "el-rules:self": `{ validator: validatePass, trigger: 'blur' }`, 70 | "el-rules:selfmethod": `var validatePass = (rule, value, callback) => { 71 | ${tabSize}if (value === '') { 72 | ${tabSize}${tabSize}callback(new Error('')); 73 | ${tabSize}} else { 74 | ${tabSize}${tabSize}callback(); 75 | ${tabSize}} 76 | }`, 77 | "el-pagination": `handleCurrentChange (pageNum) { 78 | ${tabSize}this.pageNum = pageNum 79 | ${tabSize}this.fetchList() 80 | }, 81 | handleSizeChange(pageSize) { 82 | ${tabSize}this.pageSize = pageSize 83 | ${tabSize}this.fetchList() 84 | }`, 85 | "reg-phone": `/^[1][3,4,5,7,8][0-9]{9}$/`, 86 | "reg-email": `/^[A-Za-zd]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/`, 87 | } 88 | } -------------------------------------------------------------------------------- /src/frameworks/element-ui/tag.ts: -------------------------------------------------------------------------------- 1 | export default (tabSize: string) => { 2 | return { 3 | "el-col": `$2`, 4 | "el-checkbox": `$2`, 5 | "el-checkbox-group": ` 6 | ${tabSize}$3 7 | `, 8 | "el-checkbox-button": `$2`, 9 | "el-input": `$3`, 10 | "el-option": `$3`, 11 | "el-option-group": ` 12 | $5 13 | `, 14 | "el-cascader": `$3`, 15 | "el-color-picker": `$2`, 16 | "el-form-item": `$2`, 17 | "el-alert": `$3`, 18 | "el-submenu": ` 19 | ${tabSize}$3$4 20 | `, 21 | "el-menu-item-group": ` 22 | ${tabSize}$2$3 23 | `, 24 | "el-menu-item": ``, 25 | "el-tab-pane": `$1`, 26 | "el-breadcrumb-item":``, 27 | "el-dropdown-menu": ``, 28 | "el-step": `$2`, 29 | "el-carousel": ` 30 | ${tabSize}$1 31 | `, 32 | "el-carousel-item": `$1`, 33 | "el-collapse-item": `$3`, 34 | "el-timeline-item": `$1`, 35 | "el-divider": `$1`, 36 | "el-alert:icon": ` 40 | `, 41 | "el-autocomplete": ` 42 | `, 43 | "el-badge": ``, 44 | "el-badge:button": ` 45 | ${tabSize}$2 46 | `, 47 | "el-badge:dot": `$1`, 48 | "el-breadcrumb": ` 49 | ${tabSize} 50 | ${tabSize} 51 | `, 52 | "el-button": `$1`, 53 | "el-button-group": ` 54 | ${tabSize}$1 55 | ${tabSize}$2 56 | `, 57 | "el-card": ` 58 | ${tabSize}
$1
59 | ${tabSize}
60 |
`, 61 | "el-carousel:card": ` 62 | ${tabSize} 63 | ${tabSize}${tabSize}$1 64 | ${tabSize} 65 | `, 66 | "el-cascadar": ` 70 | `, 71 | "el-collapse": ` 72 | ${tabSize} 73 | ${tabSize}${tabSize}
$5
74 | ${tabSize}
75 |
`, 76 | "el-date-picker": ``, 77 | "el-date-picker:datetime": ``, 78 | "el-dialog": ` 83 | ${tabSize}
$4
84 | ${tabSize}
85 | ${tabSize}${tabSize}取 消 86 | ${tabSize}${tabSize}确 定 87 | ${tabSize}
88 |
`, 89 | "el-dialog:form": ` 94 | ${tabSize} 95 | ${tabSize}${tabSize} 96 | ${tabSize}${tabSize}${tabSize} 97 | ${tabSize}${tabSize} 98 | ${tabSize} 99 | ${tabSize}
100 | ${tabSize}${tabSize}取 消 101 | ${tabSize}${tabSize}确 定 102 | ${tabSize}
103 |
`, 104 | "el-dropdown": ` 105 | ${tabSize} 106 | ${tabSize}${tabSize}下拉菜单 107 | ${tabSize} 108 | ${tabSize} 109 | ${tabSize}${tabSize}黄金糕 110 | ${tabSize} 111 | `, 112 | "el-form": ` 113 | ${tabSize}$4 114 | `, 115 | "el-form:inline": ` 116 | ${tabSize}$4 117 | `, 118 | "el-form-item:input": ` 119 | ${tabSize} 120 | `, 121 | "el-form-item:checkbox": ` 122 | ${tabSize} 123 | ${tabSize}${tabSize} 124 | ${tabSize} 125 | `, 126 | "el-form-item:radio": ` 127 | ${tabSize} 128 | ${tabSize}${tabSize} 129 | ${tabSize} 130 | `, 131 | "el-form-item:select": ` 132 | ${tabSize} 133 | ${tabSize}${tabSize} 134 | ${tabSize} 135 | `, 136 | "el-form-item:switch": ` 137 | 138 | `, 139 | "el-form-item:textarea": ` 140 | 141 | `, 142 | "el-input-number": ``, 143 | "el-input:textarea": ``, 144 | "el-menu": ` 145 | ${tabSize}$4 146 | `, 147 | "el-menu:submenu": ` 148 | ${tabSize} 149 | ${tabSize} 150 | ${tabSize}${tabSize} 151 | ${tabSize}${tabSize} 152 | ${tabSize} 153 | `, 154 | "el-pagination": ` 157 | `, 158 | "el-pagination:full": ` 166 | `, 167 | "el-popover": ` 174 | `, 175 | "el-popover:insert": ` 180 | ${tabSize}
181 |
`, 182 | "el-progress": ``, 183 | "el-progress:circle": ``, 184 | "el-progress:inside": ``, 185 | "el-radio": `$2`, 186 | "el-radio-button": ``, 187 | "el-radio-group": ` 188 | ${tabSize}$3 189 | `, 190 | "el-rate": ``, 191 | "el-rate:color": ` 194 | `, 195 | "el-rate:half": ` 201 | `, 202 | "el-row": ` 203 | ${tabSize}$3 204 | `, 205 | "el-select": ` 206 | ${tabSize} 211 | ${tabSize} 212 | `, 213 | "el-slider": ``, 214 | "el-steps": ` 215 | ${tabSize} 216 | `, 217 | "el-steps:simple": ` 218 | ${tabSize} 219 | `, 220 | "el-steps:icon": ` 221 | ${tabSize} 222 | `, 223 | "el-switch": ``, 224 | "el-tabs": ` 225 | ${tabSize}$5 226 | `, 227 | "el-tabs:card": ` 228 | ${tabSize}$5 229 | `, 230 | "el-table": ` 233 | ${tabSize} 237 | ${tabSize} 238 | `, 239 | "el-table-column": ` 244 | `, 245 | "el-table-column:left": ` 250 | `, 251 | "el-table-column:slot": ``, 254 | "el-tag": `$2`, 255 | "el-tag:close": `$2`, 256 | "el-time-picker": ` 260 | `, 261 | "el-time-select": ` 265 | `, 266 | "el-tooltip": ` 267 | ${tabSize}$3 268 | `, 269 | "el-tooltip:content": ` 270 |
$3
271 | ${tabSize}$4 272 |
`, 273 | "el-transfer": ` 276 | `, 277 | "el-transfer:search": ` 280 | ${tabSize}filterable 281 | ${tabSize}:filter-method="" 282 | ${tabSize}filter-placeholder=""> 283 | `, 284 | "el-tree": ` 288 | `, 289 | "el-tree:select": ` 294 | `, 295 | "el-upload": ` 304 | ${tabSize}点击上传 305 | ${tabSize}
只能上传jpg/png文件,且不超过500kb
306 |
`, 307 | "el-upload:avatar": ` 312 | ${tabSize} 313 | ${tabSize} 314 | `, 315 | "el-timeline": ` 316 | ${tabSize} 320 | ${tabSize}${tabSize}{{activity.content}} 321 | ${tabSize} 322 | `, 323 | "el-calendar": ``, 324 | "el-image": `$4`, 328 | "el-backtop": `$2`, 329 | "el-infinite-scroll": `
    330 | ${tabSize}
  • {{ i }}
  • 331 |
`, 332 | "el-drawer": ` 337 | ${tabSize}\${5:我来啦!} 338 | ` 339 | } 340 | } -------------------------------------------------------------------------------- /src/frameworks/index.ts: -------------------------------------------------------------------------------- 1 | import elementUI from './element-ui/index' 2 | 3 | const framework: any = { 4 | 'element-ui': elementUI 5 | } 6 | 7 | export function getTag(frameworks: string[], tabSize: string) { 8 | let ret = {} 9 | frameworks.forEach((frameworkName) => { 10 | if (framework[frameworkName]) { 11 | ret = Object.assign(ret, framework[frameworkName].tag(tabSize)) 12 | } 13 | }) 14 | return ret 15 | } 16 | 17 | export function getJsTag(frameworks: string[], tabSize: string) { 18 | let ret = {} 19 | frameworks.forEach((frameworkName) => { 20 | if (framework[frameworkName]) { 21 | ret = Object.assign(ret, framework[frameworkName].jsTag(tabSize)) 22 | } 23 | }) 24 | return ret 25 | } 26 | 27 | export function getAttribute(frameworks: string[], tabSize: string) { 28 | let ret = {} 29 | frameworks.forEach((frameworkName) => { 30 | if (framework[frameworkName]) { 31 | ret = Object.assign(ret, framework[frameworkName].attribute(tabSize)) 32 | } 33 | }) 34 | return ret 35 | } 36 | 37 | export function getGlobalAttribute(frameworks: string[], tabSize: string) { 38 | let ret = {} 39 | frameworks.forEach((frameworkName) => { 40 | if (framework[frameworkName]) { 41 | ret = Object.assign(ret, framework[frameworkName].globalAttribute(tabSize)) 42 | } 43 | }) 44 | return ret 45 | } 46 | 47 | export function getDocument(frameworks: string[], tabSize: string) { 48 | let ret = {} 49 | frameworks.forEach((frameworkName) => { 50 | if (framework[frameworkName]) { 51 | ret = Object.assign(ret, framework[frameworkName].document(tabSize)) 52 | } 53 | }) 54 | return ret 55 | } -------------------------------------------------------------------------------- /src/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 6 | -------------------------------------------------------------------------------- /src/monitor.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import ExplorerProvider from "./explorer"; 3 | import * as fs from 'fs' 4 | import * as path from 'path' 5 | import { v4 as uuid } from "uuid"; 6 | 7 | interface User { 8 | id: string 9 | active: string 10 | } 11 | 12 | export default class MonitorProvider { 13 | private url = 'https://int.miaixyt.com' 14 | private userPath = '' 15 | private user: User = { 16 | id: '', 17 | active: '' 18 | } 19 | public explorer: ExplorerProvider 20 | 21 | constructor(explorer: ExplorerProvider) { 22 | this.explorer = explorer 23 | try { 24 | this.userPath = path.join(this.explorer.context.extensionPath, 'asset/user.json') 25 | const user = fs.readFileSync(this.userPath, 'utf-8') 26 | let today = new Date().getDate() 27 | if (user) { 28 | this.user = JSON.parse(user) 29 | } 30 | let canSend = false 31 | if (!this.user.id) { 32 | this.user.id = uuid() 33 | this.user.active = today.toString() 34 | fs.writeFileSync(this.userPath, JSON.stringify(this.user), 'utf-8') 35 | canSend = true 36 | } else { 37 | if (parseInt(this.user.active) !== today) { 38 | canSend = true 39 | this.user.active = today.toString() 40 | fs.writeFileSync(this.userPath, JSON.stringify(this.user), 'utf-8') 41 | } 42 | } 43 | if (canSend) { 44 | this.active() 45 | } 46 | } catch (_error: any) { 47 | } 48 | } 49 | 50 | active() { 51 | axios.post(this.url + '/api/sm/addArticleReadLog', { 52 | device_id: this.user.id, 53 | platform: 'IDE' 54 | }) 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export function activate(context: vscode.ExtensionContext) { 4 | console.log('vue-helper activate', context.extension.id) 5 | } -------------------------------------------------------------------------------- /src/util/traverse.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as path from 'path' 3 | import ExplorerProvider, { Prefix } from '../explorer' 4 | 5 | export default class Traverse { 6 | public explorer: ExplorerProvider 7 | public prefix: Prefix 8 | public constructor(explorer: ExplorerProvider, prefix: Prefix) { 9 | this.explorer = explorer 10 | this.prefix = prefix 11 | } 12 | 13 | // 遍历组件 14 | search(poster: any, searchName: any, usePrefix: boolean) { 15 | let files: any[] = []; 16 | let cond: Function; 17 | let componentPath = '' 18 | let prefix = usePrefix ? this.prefix : {} 19 | // if (this.explorer.config.rootPath) { 20 | // componentPath = this.explorer.config.rootPath.component 21 | // let pathAlias = this.explorer.config.rootPath.root.split('=') 22 | // if (pathAlias.length === 2) { 23 | // prefix = { 24 | // alias: pathAlias[0], 25 | // path: pathAlias[1], 26 | // } 27 | // } 28 | // } 29 | if (!this.explorer.projectRootPath) { 30 | // 未打开工程 31 | return [] 32 | } 33 | 34 | if (componentPath && Array.isArray(componentPath) && componentPath.length > 0) { 35 | cond = function (rootPath: any) { 36 | return componentPath.indexOf(rootPath) !== -1; 37 | }; 38 | } else { 39 | let ignore: string[] = ['node_modules', 'dist', 'build']; 40 | cond = function (rootPath: any) { 41 | return !(rootPath.charAt(0) === '.' || ignore.indexOf(rootPath) !== -1); 42 | }; 43 | } 44 | let rootPathes = fs.readdirSync(this.explorer.projectRootPath || ''); 45 | 46 | for (let i = 0; i < rootPathes.length; i++) { 47 | const rootPath = rootPathes[i]; 48 | if (cond(rootPath)) { 49 | let stat = fs.statSync(path.join(this.explorer.projectRootPath || '', rootPath)); 50 | if (stat.isDirectory()) { 51 | this.traverseHandle(rootPath, files, prefix, poster, searchName); 52 | } else { 53 | this.traverseAdd(rootPath, rootPath, files, prefix, poster, searchName); 54 | } 55 | } 56 | } 57 | return files; 58 | } 59 | 60 | // 遍历添加 61 | traverseAdd(rootPath: string, dir: string, files: any[], prefix: any, poster: string, search: string) { 62 | if (rootPath.endsWith(poster)) { 63 | let posterReg = new RegExp('-?(.*)' + (poster ? poster : '\\.\\w*') + '$', 'gi'); 64 | let name = rootPath; 65 | name = name.replace(posterReg, '$1'); 66 | if (!search || (search && dir.includes(search))) { 67 | files.push({ 68 | name: name, 69 | path: dir.replace(new RegExp(`^${prefix.path}`, 'gi'), prefix.alias).replace(/\\/gi, '/') 70 | }); 71 | if (name === 'index') { 72 | name = dir.replace(/\\/gi, '/').replace(/.*\/(\w*)\/\w*.\w*/gi, '$1') 73 | files.push({ 74 | name: name, 75 | path: dir.replace(new RegExp(`^${prefix.path}`, 'gi'), prefix.alias).replace(/\\/gi, '/') 76 | }) 77 | } 78 | } 79 | } 80 | } 81 | 82 | // 遍历处理 83 | traverseHandle(postPath: string, files: any [], prefix: any, poster: string, search: string) { 84 | let fileDirs = fs.readdirSync(path.join(this.explorer.projectRootPath || '', postPath)); 85 | for (let i = 0; i < fileDirs.length; i++) { 86 | const rootPath = fileDirs[i]; 87 | if (!(rootPath.charAt(0) === '.')) { 88 | let dir = path.join(postPath, rootPath); 89 | let stat = fs.statSync(path.join(this.explorer.projectRootPath || '', dir)); 90 | if (stat.isDirectory()) { 91 | this.traverseHandle(dir, files, prefix, poster, search); 92 | } else { 93 | this.traverseAdd(rootPath, dir, files, prefix, poster, search); 94 | } 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /src/util/util.ts: -------------------------------------------------------------------------------- 1 | import { workspace, TextDocument, Position } from 'vscode' 2 | import * as os from 'os' 3 | import * as path from 'path' 4 | 5 | // windows根路径处理 6 | export function winRootPathHandle(pagePath: string) { 7 | if (!pagePath) { 8 | return '' 9 | } 10 | if (os.platform().includes("win") && pagePath.length > 0 && (pagePath[0] === "\\" || pagePath[0] === "/")) { 11 | return pagePath.substr(1, pagePath.length); 12 | } else { 13 | return pagePath; 14 | } 15 | } 16 | 17 | export function getWorkspaceRoot(documentUrl: string) { 18 | let url: string = ''; 19 | if (workspace.workspaceFolders?.length === 1) { 20 | return winRootPathHandle(workspace.workspaceFolders[0].uri.path) 21 | } 22 | workspace.workspaceFolders?.forEach((workspaceFolder) => { 23 | // windows盘去除 24 | if(documentUrl.includes(workspaceFolder.uri.path.replace(/.*:\//gi, '\/'))) { 25 | url = workspaceFolder.uri.path 26 | } 27 | }) 28 | return winRootPathHandle(url) 29 | } 30 | 31 | export function getTabSize() { 32 | const tabSize = workspace.getConfiguration('editor').tabSize; 33 | let space = ''; 34 | for (let i = 0; i < tabSize; i++) { 35 | space += ' '; 36 | } 37 | if (space.length === 0) { 38 | space = ' ' 39 | } 40 | return space 41 | } 42 | 43 | export function getCurrentWord(document: TextDocument, position: Position) { 44 | let i = position.character - 1; 45 | const text = document.lineAt(position.line).text; 46 | while (i >= 0 && ' \t\n\r\v":{[,'.indexOf(text.charAt(i)) === -1) { 47 | i--; 48 | } 49 | return text.substring(i + 1, position.character); 50 | } 51 | 52 | export function getRelativePath(src: string, dist: string) { 53 | let vfPath = path.relative(winRootPathHandle(src), dist) 54 | vfPath = vfPath.replace(/\\/gi, '/') 55 | if (vfPath.startsWith('../')) { 56 | vfPath = vfPath.substr(1, vfPath.length) 57 | } 58 | return vfPath 59 | } 60 | 61 | export function getWord(document: TextDocument, position: Position, textSplite: string[]) { 62 | const line = document.lineAt(position.line); 63 | // 通过前后字符串拼接成选择文本 64 | let posIndex = position.character; 65 | let textSingle = line.text.substring(posIndex, posIndex + 1); 66 | let selectText = ''; 67 | // 前向获取符合要求的字符串 68 | while(textSplite.indexOf(textSingle) === -1 && posIndex <= line.text.length) { 69 | selectText += textSingle; 70 | ++posIndex 71 | textSingle = line.text.substring(posIndex, posIndex + 1) 72 | } 73 | // 往后获取符合要求的字符串 74 | posIndex = position.character - 1; 75 | textSingle = line.text.substring(posIndex, posIndex + 1); 76 | while(textSplite.indexOf(textSingle) === -1 && posIndex > 0) { 77 | selectText = textSingle + selectText; 78 | --posIndex 79 | textSingle = line.text.substring(posIndex, posIndex + 1); 80 | } 81 | textSingle = line.text.substring(posIndex, posIndex + 1); 82 | return { 83 | selectText, 84 | startText: textSingle 85 | } 86 | } -------------------------------------------------------------------------------- /src/vue/snippets-html.ts: -------------------------------------------------------------------------------- 1 | export default (tabSize: string) => { 2 | return { 3 | "vcomponent": `$0`, 4 | "vka": ` 5 | ${tabSize}$2 6 | $0`, 7 | "vtransition": ` 8 | ${tabSize}$2 9 | $0`, 10 | "vtg": ` 11 | ${tabSize}$2 12 | `, 13 | "vrl": `$2$0`, 14 | "vrlt": `$2$0`, 15 | "vrv": `$1$0`, 16 | } 17 | } -------------------------------------------------------------------------------- /src/vue/snippets-js.ts: -------------------------------------------------------------------------------- 1 | export default (tabSize: string) => { 2 | return { 3 | "vnew": `new Vue({ 4 | ${tabSize}$1 5 | })`, 6 | "vgsilent": `Vue.config.silent = \${1:true}`, 7 | "vgeh": `Vue.config.errorHandler = function (err, vm, info) { 8 | ${tabSize}\${1:// handle error} 9 | }`, 10 | "vgwh": `Vue.config.warnHandler = function (msg, vm, trace) { 11 | ${tabSize}\${1:// handle warn} 12 | }`, 13 | "vgextend": `Vue.extend({ 14 | ${tabSize}template:\${1:template} 15 | })`, 16 | "vgnt": `Vue.nextTick({ 17 | ${tabSize}$1 18 | })`, 19 | "vgset": `Vue.set(\${2:target}, \${3:key}, \${4:value})`, 20 | "vgdelete": `Vue.delete(\${2:target}, \${3:key})`, 21 | "vgdirective": `Vue.directive(\${2:id}\${3:, [definition]})`, 22 | "vgfilter": `Vue.filter(\${1:id}\${2:, [definition]})`, 23 | "vgcomponent": `Vue.component(\${1:id}\${2:, [definition]})$0`, 24 | "vguse": `Vue.use(\${1:plugin})$0`, 25 | "vgmixin": `Vue.mixin({\${1:mixin}})`, 26 | "vgcompile": `Vue.compile(\${1:template})`, 27 | "vdata": `data() { 28 | ${tabSize}return { 29 | ${tabSize}${tabSize}$1 30 | ${tabSize}} 31 | },$0`, 32 | "vmounted": `mounted () { 33 | ${tabSize}$1 34 | }`, 35 | "vbm": `beforeMount () { 36 | ${tabSize}$1 37 | }`, 38 | "vcreated": `created () { 39 | ${tabSize}$1 40 | }`, 41 | "vbc": `beforeCreate () { 42 | ${tabSize}$1 43 | }`, 44 | "vupdated": `updated () { 45 | ${tabSize}$1 46 | }`, 47 | "vbu": `beforeUpdate () { 48 | ${tabSize}$1 49 | }`, 50 | "vactivated": `activated () { 51 | ${tabSize}$1 52 | }`, 53 | "vdeactivated": `deactivated () { 54 | ${tabSize}$1 55 | }`, 56 | "vbd": `beforeDestroy () { 57 | ${tabSize}$1 58 | }`, 59 | "vdestroyed": `destroyed () { 60 | ${tabSize}$1 61 | }`, 62 | "vprops": `props: { 63 | ${tabSize}$1 64 | }`, 65 | "vpd": `propsData: { 66 | ${tabSize}$1 67 | }`, 68 | "vcomputed": `computed: { 69 | ${tabSize}$1 70 | }`, 71 | "vmethods": `methods: { 72 | ${tabSize}$1 73 | }`, 74 | "vwatch": `watch: { 75 | ${tabSize}$1 76 | }`, 77 | "vwo": `\${1:key}: { 78 | ${tabSize}deep: \${2:true}, 79 | ${tabSize}immediate: \${3:true}, 80 | ${tabSize}handler: function (\${4:val}, \${5:oldVal}) { 81 | ${tabSize}${tabSize}$6 82 | ${tabSize}} 83 | },$0`, 84 | "vdirectives": `directives: { 85 | ${tabSize}$1 86 | }`, 87 | "vfilters": `filters: { 88 | ${tabSize}$1 89 | }`, 90 | "vcomponents": `components: { 91 | ${tabSize}$1 92 | }`, 93 | "vmixins": `mixins:[$1],`, 94 | "vprovide": `provide: { 95 | ${tabSize}$1 96 | }`, 97 | "vinject": `inject: [$1],`, 98 | "vmodel": `model: { 99 | ${tabSize}prop: $1, 100 | ${tabSize}event: $2 101 | }`, 102 | "vrender": `render(h) { 103 | ${tabSize}$1 104 | },`, 105 | "vel": `\${1|this,vm|}.$el$2`, 106 | "voptions": `\${1|this,vm|}.$options$2`, 107 | "vparent": `\${1|this,vm|}.$parent$2`, 108 | "vroot": `\${1|this,vm|}.$root$2`, 109 | "vchildren": `\${1|this,vm|}.$children$2`, 110 | "vslots": `\${1|this,vm|}.$slots$2`, 111 | "vss": `\${1|this,vm|}.$scopedSlots.default({ 112 | ${tabSize}$2 113 | })`, 114 | "vrefs": `\${1|this,vm|}.$refs$2`, 115 | "vis": `\${1|this,vm|}.$isServer$2`, 116 | "vattrs": `\${1|this,vm|}.$attrs$2`, 117 | "vlisteners": `\${1|this,vm|}.$listeners$2`, 118 | "vset": `\${1|this,vm|}.$set(\${2:target}, \${3:key}, \${4:value})`, 119 | "vdelete": `\${1|this,vm|}.$delete(\${2:target}, \${3:key})`, 120 | "von": `\${1|this,vm|}.$on('\${2:event}', \${3:callback})$4`, 121 | "vonce": `\${1|this,vm|}.$once('\${2:event}', \${3:callback})$4`, 122 | "voff": `\${1|this,vm|}.$off('\${2:event}', \${3:callback})$4`, 123 | "vemit": `\${1|this,vm|}.$emit('\${2:event}'\${3:, args})$4`, 124 | "vmount": `\${1|this,vm|}.$mount('$2')`, 125 | "vfu": `\${1|this,vm|}.$forceUpdate()`, 126 | "vdestroy": `\${1|this,vm|}.$destroy()$2`, 127 | "vnt": `\${1|this,vm|}.$nextTick(() => { 128 | ${tabSize}$2 129 | })`, 130 | } 131 | } -------------------------------------------------------------------------------- /tsconfig.build-ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [ 5 | ] 6 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "outDir": "out", 6 | "rootDir": "src", 7 | }, 8 | "include": [ 9 | "src", 10 | "src/**/*.json" 11 | ], 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ], 16 | "references": [ 17 | ] 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2016", 4 | "lib": [ 5 | "WebWorker", 6 | "ES2021", 7 | ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "sourceMap": true, 11 | "composite": true, 12 | "declaration": true, 13 | "strict": true, 14 | "alwaysStrict": false, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "baseUrl": "./", 20 | "paths": { 21 | }, 22 | "noEmit": true, 23 | }, 24 | "include": [ 25 | "*/*/src", 26 | "*/*/tests", 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | ], 31 | } --------------------------------------------------------------------------------