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.
--------------------------------------------------------------------------------
/src/templates/page-vue2.ts:
--------------------------------------------------------------------------------
1 | const content = `\
2 | components: {},
3 | data() {
4 | return {}
5 | },
6 | computed: {},
7 | methods: {},
8 | watch: {},
9 |
10 | // 页面周期函数--监听页面加载
11 | onLoad() {},
12 | // 页面周期函数--监听页面初次渲染完成
13 | onReady() {},
14 | // 页面周期函数--监听页面显示(not-nvue)
15 | onShow() {},
16 | // 页面周期函数--监听页面隐藏
17 | onHide() {},
18 | // 页面周期函数--监听页面卸载
19 | onUnload() {},
20 | // 页面处理函数--监听用户下拉动作
21 | // onPullDownRefresh() { uni.stopPullDownRefresh(); },
22 | // 页面处理函数--监听用户上拉触底
23 | // onReachBottom() {},
24 | // 页面处理函数--监听页面滚动(not-nvue)
25 | // onPageScroll(event) {},
26 | // 页面处理函数--用户点击右上角分享
27 | // onShareAppMessage(options) {},\
28 | `
29 |
30 | const template = `\
31 |
32 | <%- options.name %>
33 |
34 |
35 |
45 |
46 | \
47 | `
48 |
49 | export default template
50 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": [
14 | "--extensionDevelopmentPath=${workspaceFolder}"
15 | ],
16 | "outFiles": [
17 | "${workspaceFolder}/out/**/*.js"
18 | ],
19 | "preLaunchTask": "${defaultBuildTask}"
20 | },
21 | {
22 | "name": "Extension Tests",
23 | "type": "extensionHost",
24 | "request": "launch",
25 | "runtimeExecutable": "${execPath}",
26 | "args": [
27 | "--extensionDevelopmentPath=${workspaceFolder}",
28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
29 | ],
30 | "outFiles": [
31 | "${workspaceFolder}/out/test/**/*.js"
32 | ],
33 | "preLaunchTask": "${defaultBuildTask}"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "uni-create-view" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## [2.0.9]
8 | - 更新 logo 图标
9 |
10 | ## [2.0.8]
11 | - 调整项目目录、新增 .eslintrc 配置
12 | - 使用 pnpm 替换 yarn 工具
13 | - 添加 LICENSE 协议
14 | - 调整、优化插件默认配置
15 | - README 文档补充优化
16 |
17 | ## [2.0.6]
18 | - 删除页面无用的属性[#13]
19 | - 修复错误的模板语法[#12]
20 | - template 模板 data 更改
21 |
22 | ## [2.0.4]
23 | - 保留支持 `composition-api(vue2)`,文档优化
24 |
25 | ## [2.0.2]
26 | - 修复 window 环境下路径 `\\` 问题
27 |
28 | ## [2.0.1]
29 | - 新增文档内容
30 |
31 | # [2.0.0]
32 | - 支持组件页面 style scoped 选项
33 | - 重构主逻辑
34 | - 修复无法写入 pages.json
35 | - 支持 setup 语法
36 | - 支持 vue3 模版
37 | - 支持自定义名称 index 或者使用文件夹名称
38 | - 模版逻辑优化,使用 ejs 实现
39 | - 优化查询上层文件逻辑
40 |
41 | ## [1.3.6]
42 | - 新增 composition-api 选项
43 | - 优化模板判断逻辑
44 |
45 | ## [1.3.5]
46 | - 新增输入页面名称使用空格分割输入内容时, 左侧为页面文件名称, 右侧为"navigationBarTitleText"名称
47 |
48 | ## [1.3.4]
49 | - 修复右键后无反应 bug
50 |
51 | ## [1.3.2]
52 | - 支持 pages.json 中添加注释
53 |
54 | ## [1.3.1]
55 | - 支持单文件创建
56 |
57 | ## [1.3.0]
58 | - 新增 nvue 生命周期兼容性提示
59 | - 支持深度目录创建页面
60 | - 支持创建分包页面
61 | - 清除多余语法
62 |
63 | ## [1.2.7]
64 | - 更换 `computed` 排版顺序
65 | - 添加 `onPullDownRefresh` 默认释放上拉刷新 `uni.stopPullDownRefresh()`
66 |
--------------------------------------------------------------------------------
/src/command.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { generate } from './generate'
3 | import { getConfiguration, logger } from './utils'
4 |
5 | export interface CreateCommandOptions {
6 | /** 命令名称 */
7 | name: string
8 | /** 命令 */
9 | command: string
10 | /** 配置 */
11 | options?: { component?: boolean; subcontract?: boolean }
12 | }
13 |
14 | export function createCommand(options: CreateCommandOptions) {
15 | return vscode.commands.registerCommand(options.command, async (uri) => {
16 | const componentText = `输入${options.name}名称`
17 | const pageText = `${componentText},空格分隔字段(navigationBarTitleText)`
18 | const input = await vscode.window.showInputBox({ prompt: options.name === '页面' ? pageText : componentText })
19 |
20 | if (!input) {
21 | logger('error', `${options.name}名称不能为空!`)
22 | throw new Error(`${options.name}名称不能为空!`)
23 | }
24 | const { message, status } = await generate({
25 | names: { view: input.split(' ')[0], page: input.split(' ')[1] || '' },
26 | nameType: getConfiguration('create-uniapp-view.name'),
27 | path: uri.fsPath,
28 | component: options.options?.component,
29 | subcontract: options.options?.subcontract,
30 | typescript: getConfiguration('create-uniapp-view.typescript'),
31 | styleType: getConfiguration('create-uniapp-view.style'),
32 | directory: getConfiguration('create-uniapp-view.directory'),
33 | template: getConfiguration('create-uniapp-view.template'),
34 | setup: getConfiguration('create-uniapp-view.setup'),
35 | scoped: getConfiguration('create-uniapp-view.scoped'),
36 | })
37 |
38 | logger(status, message)
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | UniApp Create View for VS Code
8 |
9 |
10 | 在 VS Code 右键目录文件夹快速创建页面与组建,创建视图页面时将自动添加 `pages.json` 中!
11 |
12 |
13 |
14 |
15 | Visual Studio |
16 | Github Page |
17 | Hairy's Blog
18 |
19 |
20 |
21 |
22 | ## Features
23 |
24 | - 📁 创建页面、分包页面,自动查找根目录下 `pages.json` 文件并写入
25 | - 📦 可深度目录创建,写入 `pages.json` 后仍可保留注释
26 | - ✨ 可配置 `vue(2|3)|composition-api|setup` 组件、页面模板
27 | - 👕 可配置 `scss|less|stylus|sass` 预编辑器类型
28 | - 🦾 `typescript` 为默认开发语言(可在设置中关闭)
29 |
30 |
31 |
32 | > 使用 `composition-api(vue2)` 模版,建议配合 [uni-composition-api](https://github.com/TuiMao233/uni-composition-api) 使用
33 |
34 | ## 基本使用(page、component)
35 |
36 | 右键打开菜单选择创建类型,可选择创建组件、页面、分包页面,空格分割视图名称与页面名称(navigationBarTitleText)
37 |
38 |
39 |
40 |
41 |
42 | ## 深度目录
43 |
44 | `^1.3.0` 新增扩展能力,无特殊需求还是建议使用单文件模式。
45 |
46 | 
47 |
48 | ## 分包页面
49 |
50 | `^1.3.0` 新增功能,用于创建分包页面,并自动添加至 `subPackages` 字段中。
51 |
52 | > 注意:`cli` 创建的项目需要在`package.json`中添加参数 `--minimize`,具体参考官方文档:[dcloud.io](https://uniapp.dcloud.io/collocation/pages?id=subpackages)
53 |
54 | 
55 |
56 | ## License
57 |
58 | [MIT](./LICENSE) License © 2021-PRESENT [Hairyf](https://github.com/hairyf)
--------------------------------------------------------------------------------
/src/template.ts:
--------------------------------------------------------------------------------
1 | import * as ejs from 'ejs'
2 | import cv2 from './templates/component-vue2'
3 | import cv3 from './templates/component-vue3'
4 | import pv2 from './templates/page-vue2'
5 | import pv3 from './templates/page-vue3'
6 | import pv2c from './templates/page-composition'
7 | import cv2c from './templates/component-composition'
8 |
9 | const ALL_TEMPLATES = {
10 | ['vue2' as string]: { page: pv2, component: cv2 },
11 | ['vue3' as string]: { page: pv3, component: cv3 },
12 | ['composition-api(vue2)' as string]: { page: pv2c, component: cv2c },
13 | }
14 |
15 | export interface CreateViewTemplateOptions {
16 | template?: string
17 | name?: string
18 | typescript?: boolean
19 | styleType?: string
20 | component?: boolean
21 | setup?: string
22 | scoped?: boolean
23 | }
24 |
25 | export function createViewTemplate(options: CreateViewTemplateOptions) {
26 | const templates = ALL_TEMPLATES[options.template || 'vue2']
27 | const template = templates[options.component ? 'component' : 'page']
28 |
29 | const handle = (attrs: (string | boolean | undefined)[]) => {
30 | const _v = attrs.filter(Boolean).join(' ').trim()
31 | return _v ? ` ${_v}` : ''
32 | }
33 |
34 | const scriptAttrs = handle([
35 | options.typescript && 'lang="ts"',
36 | options.template === 'vue3' && options.setup && 'setup',
37 | ])
38 |
39 | const styleAttrs = handle([
40 | options.styleType !== 'css' && `lang="${options.styleType}"`,
41 | options.scoped && 'scoped',
42 | ])
43 |
44 | const data = {
45 | name: options.name,
46 | setup: options.setup,
47 | typescript: options.typescript,
48 | scriptAttrs,
49 | styleAttrs,
50 | }
51 | return ejs.render(template, { options: data })
52 | }
53 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import path = require('path')
2 | import slash = require('slash')
3 | import * as vscode from 'vscode'
4 | import * as fs from 'fs-extra'
5 |
6 | export type SearchFileResult = Promise<{ path: string; data: string } | null | undefined>
7 |
8 | export function logger(type: string, message = '') {
9 | switch (type) {
10 | case 'success':
11 | return vscode.window.showInformationMessage(`Success: ${message}`)
12 | case 'warning':
13 | return vscode.window.showWarningMessage(`Warning: ${message}`)
14 | case 'error':
15 | return vscode.window.showErrorMessage(`Failed: ${message}`)
16 | }
17 | }
18 |
19 | export function getConfiguration(section: string) {
20 | return vscode.workspace.getConfiguration().get(section)
21 | }
22 |
23 | export function isDirectory(path: string) {
24 | try {
25 | return fs.statSync(path).isDirectory()
26 | }
27 | catch (error) {
28 | return false
29 | }
30 | }
31 |
32 | export function isFileAccess(path: string) {
33 | return new Promise((resolve) => {
34 | fs.access(path, (error: any) => {
35 | if (error)
36 | resolve(false)
37 | else resolve(true)
38 | })
39 | })
40 | }
41 |
42 | export function upwardSearchFile(currentPath: string, fileName: string): SearchFileResult {
43 | const recursion = async (appPath: string): Promise => {
44 | const recursPath = slash(path.resolve(appPath, fileName))
45 | // 递归出口: 路径是根路径, 停止递归
46 | if (recursPath.split('/').length < 1)
47 | return null
48 |
49 | if (await isFileAccess(recursPath || '/')) {
50 | const stat = fs.lstatSync(recursPath)
51 | const data = stat.isFile() ? fs.readFileSync(recursPath, 'utf-8') : ''
52 | return { path: recursPath, data }
53 | }
54 | else {
55 | return recursion(path.resolve(appPath, '../'))
56 | }
57 | }
58 |
59 | return recursion(currentPath)
60 | }
61 |
--------------------------------------------------------------------------------
/src/generate.ts:
--------------------------------------------------------------------------------
1 | import path = require('path')
2 | import * as fs from 'fs-extra'
3 | import * as JSONC from 'comment-json'
4 | import slash = require('slash')
5 | import { createViewTemplate } from './template'
6 | import { isDirectory, upwardSearchFile } from './utils'
7 |
8 | export interface GenerateOptions {
9 | /** 创建路径 */
10 | path: string
11 |
12 | nameType: string
13 | /** 多个名称内容 */
14 | names: {
15 | /** 视图名称(组件/文件夹/文件) */
16 | view: string
17 | /** 页面名称(navigationBarTitleText) */
18 | page: string
19 | }
20 |
21 | /** 创建是否包含文件夹 */
22 | directory?: boolean
23 |
24 | /** options */
25 | typescript?: boolean
26 | styleType?: string
27 | component?: boolean
28 | template?: string
29 | setup?: string
30 | scoped?: boolean
31 | subcontract?: boolean
32 | }
33 | export interface GenerateResult {
34 | status: string
35 | message: string
36 | }
37 |
38 | export async function generate(options: GenerateOptions): Promise {
39 | const names = options.names
40 | const directoryPath = path.resolve(options.path, names.view)
41 |
42 | // #region 判断路径是否存在 / 符合创建环境
43 | if (!isDirectory(options.path))
44 | return { status: 'error', message: '创建错误, 该路径不是文件夹' }
45 |
46 | if (options.directory) {
47 | if (!isDirectory(directoryPath))
48 | fs.ensureDir(directoryPath)
49 | else return { status: 'error', message: '创建错误, 该文件夹已存在!' }
50 | }
51 | // #endregion
52 |
53 | // #region 生成模版
54 | const isIndex = options.nameType === 'index'
55 | const filePath = options.directory ? `${names.view}/${isIndex ? 'index' : names.view}.vue` : `${names.view}.vue`
56 | const template = createViewTemplate({ name: names.view, ...options })
57 | fs.writeFileSync(path.resolve(options.path, filePath), template, { flag: 'w' })
58 | // #endregion
59 |
60 | // 组件则跳过
61 | if (options.component)
62 | return { status: 'success', message: '创建组件成功!' }
63 |
64 | // 写入 pages.json
65 | const status = await writePagesJson(options)
66 | if (status)
67 | return status
68 |
69 | return { status: 'success', message: '创建页面成功!' }
70 | }
71 |
72 | export async function writePagesJson(options: GenerateOptions) {
73 | const names = options.names
74 | options.path = slash(options.path)
75 |
76 | const pagesJsonFile = await upwardSearchFile(options.path, 'pages.json')
77 | if (!pagesJsonFile)
78 | return { status: 'warning', message: '创建页面成功! 但pages.json未找到' }
79 |
80 | // 获取基于项目目录下的 pages 文件和根目录
81 | const pagesSplit = pagesJsonFile.path.split('pages.json')
82 |
83 | const isIndex = options.nameType === 'index'
84 | const rootPath = options.path.replace(pagesSplit[0], '')
85 | const filePath = options.directory ? `${names.view}/${isIndex ? 'index' : names.view}` : `${names.view}`
86 |
87 | // 读取 pages.json, 准备 page 信息
88 | const pagesJson = JSONC.parse(pagesJsonFile.data) as Record
89 | const page = { path: filePath, style: { navigationBarTitleText: names.page || names.view } }
90 |
91 | // 如果是分包页面
92 | if (options.subcontract) {
93 | pagesJson.subPackages = pagesJson.subPackages || []
94 | const findRoot = pagesJson.subPackages.find((v: any) => v.root === rootPath)
95 | const root = findRoot || { root: rootPath, pages: [] }
96 | root.pages.push(page)
97 | if (!findRoot)
98 | pagesJson.subPackages.push(root)
99 | }
100 | else {
101 | pagesJson.pages = pagesJson.pages || []
102 | page.path = slash(path.join(rootPath, page.path))
103 | pagesJson.pages.push(page)
104 | }
105 |
106 | const newPagesJson = JSONC.stringify(pagesJson, null, '\t')
107 | fs.writeFileSync(pagesJsonFile.path, newPagesJson)
108 | }
109 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "publisher": "mrmaoddxxaa",
3 | "name": "create-uniapp-view",
4 | "displayName": "uni-create-view",
5 | "version": "2.1.0",
6 | "description": "快速创建 uniapp 视图与组件!",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/uni-helper/uni-create-view",
10 | "directory": "."
11 | },
12 | "bugs": {
13 | "url": "https://github.com/uni-helper/uni-create-view"
14 | },
15 | "keywords": [
16 | "uniapp",
17 | "page",
18 | "components",
19 | "uni-app",
20 | "uni"
21 | ],
22 | "prettier": "@hairy/eslint/prettier.js",
23 | "categories": [
24 | "Other"
25 | ],
26 | "main": "./out/extension.js",
27 | "icon": "public/logo.png",
28 | "engines": {
29 | "vscode": "^1.47.0"
30 | },
31 | "activationEvents": [
32 | "onCommand:create-uniapp-view.createPage",
33 | "onCommand:create-uniapp-view.createComponent"
34 | ],
35 | "contributes": {
36 | "configuration": {
37 | "title": "create-uniapp-view 创建 page、component 时的配置项",
38 | "properties": {
39 | "create-uniapp-view.typescript": {
40 | "type": "boolean",
41 | "default": true,
42 | "description": "创建视图时是否选择 TypeScript 为默认语言"
43 | },
44 | "create-uniapp-view.directory": {
45 | "type": "boolean",
46 | "default": false,
47 | "description": "创建视图时是否创建同名文件夹"
48 | },
49 | "create-uniapp-view.name": {
50 | "type": [
51 | "string"
52 | ],
53 | "default": "index",
54 | "enum": [
55 | "index",
56 | "与文件夹同名"
57 | ],
58 | "description": "创建文件夹中生成的文件名"
59 | },
60 | "create-uniapp-view.style": {
61 | "type": [
62 | "string"
63 | ],
64 | "default": "css",
65 | "enum": [
66 | "css",
67 | "scss",
68 | "less",
69 | "stylus",
70 | "sass"
71 | ],
72 | "description": "创建视图时 CSS 预处理器的类型"
73 | },
74 | "create-uniapp-view.scoped": {
75 | "type": "boolean",
76 | "default": true,
77 | "description": "创建模版时,是否使用