Vue3 Typescript PC网页+桌面应用瑞士军刀模板
~ Everything You Need For a Modern Web-based App ~
发布一个基于Vue3和Element plus组件库的PC网页和桌面应用模板,整合了大部分常见开发框架和工具库。
项目地址:Vue3快速启动聚合模板
操作手册
-
克隆项目,并使用VS Code打开;
-
按提示安装工作区推荐扩展;
-
运行
npm install
安装项目依赖; -
运行
npm run vite:dev
开启开发服务器; -
网页应用项目,浏览器访问 http://localhost:5173/,
桌面应用项目,运行
npm run electron:start
启动electron开发模式; -
需要新的npm包依赖时,使用-D参数安装到开发依赖,而不是生产依赖,
因为vite和electron打包会自动导入所需依赖到生产环境。
-
修改完成后,运行
npm run lint:fix
和npm run prettier
对代码进行检查和格式化; -
网页应用部署为全自动CI脚本,无需人工操作,
electron应用打包运行
npm run electron:make
;
基础框架
Vue3
- 基于Vue3 组合式API
<script setup>
单文件组件,并使用Typescript语言进行开发; - 最显著的特点是变量和方法定义不再局限于Vue组件实例的this绑定,可以直接在
<template>
中引用,无需再填写配置项; - 响应式变量的语法有所变化,要使用
ref
方法封装,整体风格更接近于React; - 不再将工具模块绑定到Vue组件实例,转而使用
use*
方法,或直接import引入; - 更多细节参阅 script setup 文档 。
Typescript
- Vue3主工程首选开发语言Typescript,同时保留必要时使用Javascript的自由;
- 外围辅助功能和配置首选Javascript,如Electron主进程;
- Typescript类型系统可在运行前发现错误,同时可以增强编辑器提示。
Tailwind
- 基于工具类名的样式库,使用体验类似Bootstrap,但由于是动态生成css,功能更强;
- 首选使用Tailwind实现页面布局和样式,自定义类名手写css仅作为补充手段,非必要不使用;
- 更多细节参阅本文档的样式系统章节。
Element plus
- 主力UI组件库,直接使用其中组件可满足大部分交互功能需要;
- 关于Element主题样式的自定义,参阅本文档的样式系统章节。
Pinia
- 模块化的状态存储,用于存储客户端相关信息;
- 可加密,可持久化。
Vue-Router
- vue官方的路由系统插件,直接使用;
- 具体参阅本文档的路由结构章节。
Electron
- 桌面应用部分由Electron提供支持,主要作为页面运行环境;
- 负责处理本地功能,调度外部程序。
Koa
- 全异步的Web服务框架,为项目提供内置前端部署服务器支持;
- 桌面应用模式下koa由electron加载,并通过本地服务的方式访问,可避免使用本地文件协议,同时为本地离线部署提供支持。
样式系统
Tailwind
- Tailwind作为首选手段,用于所有除element主题样式以外的元素;
- element主题样式修改作为第二手段;
- 手动编写css作为最终手段,非必要不使用;
- 同时支持css,scss,less,stylus四种样式语言,手写场合首选使用scss;
- VSCode安装
Tailwind CSS IntelliSense
插件,在任意html元素的class属性中获得tailwind编辑器提示(class为空时需要按空格
或Ctrl+空格
触发);
Element plus 主题样式修改
必要时可以修改Element组件的内部主题样式,三种方式:
覆盖el内部scss变量
-
于
element-theme.scss
文件中,可利用scss的forward指令对element主题scss变量进行动态覆盖:@forward 'element-plus/theme-chalk/src/common/var.scss' with ( $colors: ( 'primary': ( 'base': rgb(101, 203, 101), ), ) );
-
一般用于组件尺寸和颜色的修改;
-
这个方法总是有效,首推使用;
-
这也是element plus官方推荐的主题自定义方案;
-
唯一的缺点是要解读element的主题变量。
覆盖el的css全局变量
-
于
App.vue
的style标签,在:root
(即html)元素中定义--el-*
的css变量; -
用于某些需要动态控制样式,但el对应scss变量不在全局的场合,如受
<el-header>
组件尺寸影响的带滚动条的容器高度::root { --electron-window-titlebar-height: 30px; --el-header-height: 60px; --el-footer-height: 60px; } #app-container { height: calc(100% - var(--electron-window-titlebar-height)); } .el-message { margin-top: calc( 15px + var(--el-header-height) + var(--electron-window-titlebar-height) ); }
-
不总是有效并且难以管理,仅作为特殊需求下的补充手段。
覆盖.el-*
类名
-
于
App.vue
的style标签,直接覆盖element组件最终页面元素的类名; -
用于不受变量控制的组件样式,或需要复杂动态计算的场合:
.el-container { height: 100%; width: 100%; } .el-scrollbar { width: 100%; } .el-scrollbar__view { width: 100%; } .el-scrollbar__bar { display: block !important; } .el-message { margin-top: calc(15px + var(--el-header-height)); }
-
由于加载顺序问题,生效情况完全不确定,经常需要使用
!important
覆盖; -
最终手段的最终手段,不推荐使用。
全局样式
所有全局样式于App.vue
中加载,不使用scoped
属性限制作用范围,分为以下3部分:
style.css
- 定义元素通用样式,如css reset;
- 同时也是tailwind的加载入口,这部分是自动的,无需干预;
style-electron-titlebar-fix.css
-
用于修复桌面应用模式下的标题栏高度造成的滚动条和消息框错位,
-
根据页面所处环境是浏览器还是electron动态加载;
App.vue的<style>
标签
- 定义页面主容器样式;
- 全局主容器css变量;
- .el-*类名element主题样式的覆盖。
组件内部样式
- 如有必要,每个组件都可在内部
<style scoped>
标签定义自己需要的样式; - 必须使用
scoped
属性将样式作用范围限制在当前组件; - 可以在组件范围内个别覆盖element的css变量和
el-*
类名。
路由系统
路由系统较为复杂,以下详细描述。
结构定义
- 根据Vue router子路由(children)的特性,借鉴安卓应用的页面结构,采用逐级嵌套的组织方法;
- 通过
<router-view>
组件动态加载下一层组件,不可跨级引用; - 但,任何层级都可以直接使用element组件和
components
目录中的通用组件,也就是通用组件没有层级; - 如果功能足够简单,每一层实际内容也可以是独立的页面,不包含子级路由,如
Landing.vue
活动组件。
层级划分
具体共分为4个层级:
-
第0层:应用主容器组件
App.vue
是应用的主容器,包括全局根路由,全局国际化配置,全局样式;
-
第1层:活动组件Activities
- 所有活动组件放置于
activities
目录; - 分别对应一个足够抽象的,大的业务页面,比如登录活动,主屏活动;
- 活动组件主要作为视图组件的容器,一般不包括自己专用的子组件;
- 活动组件本身的功能应该足够简单,复杂的功能要下放到下一层的视图组件实现;
- 所有活动组件放置于
-
第2层:视图组件Views
- 所有活动组件放置于
views
目录; - 视图组件是业务功能主要实现地点,是基本功能单位;
- 视图组件可以包括自己的专用子功能组件;
- 所有活动组件放置于
-
第3层:视图组件的子视图组件和子组件
- 子视图组件和子组件要放置在
views
目录中,与父视图组件同名的目录中,如视图组件名为views/Home.vue
,则子组件应放在views/Home/
; - 子组件目录的结构按需设置,不做统一设计,但不应过于复杂;如果实际出现了非常复杂的情况,则应考虑重新拆分业务;
- 子视图组件和子组件要放置在
定义规则
- 路由结构于
routers/routes.ts
中按照以上层级对应定义; - 除重定向路由、不同层级间组件同名可以使用相对路径,所有路由的
path
属性都设为完整的绝对路径,并与其所属层级对应; routes.ts
中导出的定义可直接用于导航菜单的动态渲染,其中show
属性用于控制路由是否在菜单显示,icon
属性用于控制菜单项的图标(注意,这里只能使用MDI Webfont图标class名);
使用方式
-
使用方式共有3种:
-
通过
<router-link>
组件; -
通过useRouter和useRoute方法:
const router = useRouter(); const back = () => { router.push('/'); }; const route = useRoute(); // 打印路由查询参数 console.log(route.query);
-
直接在
<template>
中使用$router变量:<el-menu-item @click="$router.push('/')">返回</el-menu-item>
-
存储系统
-
客户端本地数据存储由Pinia支持,结合
pinia-plugin-persistedstate
持久化插件和secure-ls
加密本地存储来实现模块化的加密的持久存储; -
所有存储模块于
stores
目录定义; -
通过存储模块导出的
use*Store
方法使用:mport { useGlobalStore } from '../stores'; const store = useGlobalStore(); const name = computed({ get: () => store.name, set: v => { store.name = v; }, });
网络请求
-
使用axios来发送网络请求;
-
相关配置和封装位于
networks
目录; -
模版提供两种封装方法:
-
useHttpRequest
,直接将axios原模块封装为Vue3的inject对象,可以直接使用,发起原始请求:// 这里的req等同于axios模块 const req = useHttpRequest(); // 需手动catch处理axios的异常 try { let res = await req.get('https://api.open-meteo.com/v1/forecast', { params: { latitude: '39.91', longitude: '116.40', daily: 'weathercode,temperature_2m_max,temperature_2m_min', timezone: 'auto', }, }); if (res.status === 200) { // 请求成功,处理数据 } else { // 请求出错,处理接口返回的错误; } } catch (err) { // 请求出错,处理axios执行异常,一般为网络错误或跨域错误 }
-
createApi
和useApi
,构造Api调用模块,可以绑定根地址,增加请求和响应预处理器,createApi
方法的配置项含义详见对应注释:import { createApi, useApi } from './api'; const apiWeather = createApi({ name: 'weather', baseURL: 'https://api.open-meteo.com/v1', parser: data => { if (data.error) { throw new Error(data.reason); } return data; }, }); const useWeatherApi = () => useApi('weather'); export { apiWeather, useWeatherApi };
构造好的模块需要在
main.ts
注册:import { request, apiWeather } from './networks'; app.use(request); app.use(apiWeather);
使用方法:
const api = useWeatherApi(); let query = { latitude: '39.91', longitude: '116.40', daily: 'weathercode,temperature_2m_max,temperature_2m_min', timezone: 'auto', }; let res = await api.get('/forecast', query); if (res) { // 请求成功,处理数据 } else { // 请求失败时res为undefined,并会自动弹出错误提示 }
-
图表
-
图表使用Echarts,并使用Vue-echarts封装组件:
<div class="w-[50%] h-[30vh]"> <v-chart :option="opt" autoresize></v-chart> </div>
-
注意,Echarts新版组件全部为动态加载,需要将用到的组件在
plugins/v-echarts
中手动加载:import { SVGRenderer } from 'echarts/renderers'; import { LineChart, BarChart } from 'echarts/charts'; import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, } from 'echarts/components'; use([ // CanvasRenderer, SVGRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, ]);
-
VSCode安装
Echarts Enhanced Completion
插件,并在配置项对象前添加/** @type EChartsOption */
类型注释,可以获得丰富的配置项文档提示:/** @type EChartsOption */ const opt = ref({ title: { text: 'Echarts Demo', textStyle: { color: '#332277', }, }, tooltip: { show: true, }, xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], }, yAxis: { type: 'value', }, series: [ { data: [100, 230, 224, 218, 135, 147, 260], type: 'bar', }, ], });
图标库
Material Design Icons
-
MDI作为主力图标库,使用其中的webfont版本:
<span class="mdi mdi-microsoft-windows"></span>
-
MDI是目前免费图标库中图标最全的,已经可以满足绝大部分需要。
Iconify
-
使用Unplugin-icons自动按需导入Iconify的图标组件作为补充手段;
-
可以调用所有常见图标框架的图标,缺点是不支持动态组件渲染,SVG图标与文字混排也不方便;
-
使用方法为直接在
<template>
中添加组件,写法为<i-库名-图标名>
或<i-库名:图标名>
:<i-fa-home /> <i-ep:refresh-right class="animate-spin" />
- 对应的名称在Iconify图标库页面查阅,例如element plus库的向右刷新图标。
外部SVG图标
- 如果库中实在没有合适的图标,则考虑使用外部SVG图标;
- 直接在
<template>
中添加SVG元素,或将SVG文件置于assets
目录中,再于元素的src属性等引用; - 推荐阿里巴巴图标库和SVG Repo。
Element Plus 自带图标系统
-
由于需要单独安装并手动引入,配置较麻烦,不推荐使用;
-
但可以给MDI和Iconify图标外层嵌套
<el-icon>
组件来获得与element一致的样式和编程接口,尤其是在菜单项中等与element其他组件合用的场合:<el-menu-item @click="$router.push('/')"> <el-icon> <span class="mdi mdi-exit-to-app"></span> </el-icon> <span>退出</span> </el-menu-item> <el-button type="primary" class="mt-2 transition-all hover:scale-125" @click="back" > 返回首页 <el-icon class="el-icon--right"><i-icon-park-solid:home /></el-icon> </el-button>
工具库
Vueuse三大核心库
- 即@vueuse/core,@vueuse/head,@vueuse/math;
- 对标lodash,针对Vue3的组合式语法的超级综合工具集,例如连接蓝牙,事件限流,数值四舍五入,数组操作,元素拖放等;
- 主要优势为返回值全部默认为ref类型,无需再手动构造ref或手动
.value=
响应式赋值。
动画效果
-
简单的交互动画可直接使用tailwind的transition类、动画类名或element自带动画类名:
<el-button type="primary" class="mt-2 transition-all hover:scale-125" @click="back" > 返回首页 <el-icon class="el-icon--right"><i-icon-park-solid:home /></el-icon> </el-button> <i-ep:refresh-right class="animate-spin" />
配合Vue3自带
<transition>
组件实现路由转场等组件动态效果:<router-view v-slot="{ Component, route }"> <!-- tailwind配合vue内置transition组件实现页面切换动画 --> <transition mode="out-in" appear enter-active-class="transition duration-700 ease-in-out" leave-active-class="transition duration-700 ease-in-out" enter-from-class="opacity-50 translate-x-[2%]" leave-to-class="opacity-100" > <component :is="Component" :key="route.path" /> </transition> </router-view>
-
复杂动画使用@vueuse/motion的v-motion指令:
<blockquote>演示@vueuse/motion指令动画,可以做复杂的进入效果</blockquote> <h1 v-motion :initial="{ opacity: 0, y: -100, scale: 0.8 }" :enter="{ opacity: 1, y: 0, scale: 1 }" :hovered="{ scale: 1.2 }" class="" > <i class="mdi mdi-information"></i><span>文档页</span> </h1>
或直接使用预设动画指令:
<img v-motion-roll-left src="/vite.svg" class="logo" alt="Vite logo" />
声音效果
使用@vueuse/sound播放声音效果:
import sound from '../assets/sound.wav';
const { play } = useSound(sound);
const titleIconClicked = () => {
play();
};
Electron加强
通过@vueuse/electron使用IpcRenderer模块与electron主进程通讯:
const send = useIpcRenderer().send;
send('windowClose');
日期处理Day.js
对标momentjs,处理日期计算和日期格式。
更多工具可按需添加到开发依赖。
开发环境
编辑器
- VSCode
开发辅助
-
Vite
快速热更新的开发服务器和编译打包,webpack继任者;
-
Eslint
代码静态检查;
-
PostCSS
css预处理,Tailwind动态编译需要;
-
Prettier
代码格式化;
-
Unplugin-auto-import
-
自动按需导入vue,pinia,vue-router,本模版包含的vueuse模块的所有方法和element plus自带的方法,可以直接调用,无需手动import;
-
直接在
.ts
,.js
或组件的<script>
标签中输入方法的开头字母,可以获得编辑器提示;
-
-
Unplugin-vue-components
- 自动按需导入element plus组件,
components
目录下的通用组件,可以直接在<template>
中使用,无需手动import; - 在
<template>
中,输入<
和组件的开头字母,可以获得编辑器提示;
- 自动按需导入element plus组件,
-
Unplugin-icons
自动按需导入iconify图标组件,用法在图标库章节已经介绍。
编辑器插件
所有本文档提及的VSCode插件均已列入工作区推荐,可以按提示批量安装。大部分插件属于基础服务,无需手工操作。下面仅重点介绍几个关键插件及其使用方法,更多信息请参阅项目的About文档视图页面链接列表。
-
Volar
Vue3 官方的 IDE 开发支持插件,Vetur 的继任者(需要卸载或者禁用 Vetur)。
-
TypeScript Vue 插件 (Volar)
Volar 的 TS 支持版本,如果开启接管模式则不用安装。
-
Vite
Vite打包工具的官方支持插件,可设置自动启动开发服务器等。
-
Vue VSCode Snippets
Vue的代码片段模版。
-
html tag wrapper
在标签外嵌套
<div>
标签的快捷操作。选中要被嵌套的标签,可以是多行,按Ctrl+I
;配合另一个插件
Auto Rename Tag
和代码格式化,使用非常方便。 -
Auto Rename Tag
自动重命名成对的html标签和组件。修改开始和关闭标签中的一个,自动更新另一个。
-
Color the tag name(タグに色つけ太郎)
给不同的html标签和组件染上不同颜色,同名的标签颜色总是一样的,模版复杂的时候也非常清晰,配合
Highlight Matching Tag
,大幅度提高定位页面元素的效率。 -
Highlight Matching Tag
自动高亮选中的html标签和组件,“选中”可以是选中元素名,选中元素的属性,光标停留在元素内部任意位置,对当前正在编辑的页面元素有个清晰的提示。
-
Surround
在选中代码外围嵌套常用结构的快捷操作,包括
if/else
,try/catch
,多行注释等,支持多种语言。选中要被嵌套的代码,按Ctrl+Shift+T
或直接右键选择Surround With
。 -
Tailwind CSS IntelliSense
Tailwind官方VSCode支持插件,使用方法已有介绍。
-
npm Intellisense
编写npm包导入时提供编辑器提示。
-
Material Design Icons Intellisense
在文件管理器栏增加MDI图标库的浏览器,并可在编辑器中显示图标的预览。
-
Echarts Enhanced Completion
echarts配置项提示增强,用法已经介绍。
-
Auto Import
在项目范围自动搜索可导入的对象并增加到编辑器提示,自动添加import语句。作为对Vite的unplugin系列自动导入插件的补充。
其他
配置 Volar 接管模式
默认情况下,TypeScript 无法处理 .vue
导入的类型信息,因此我们将 tsc
CLI 替换为 vue-tsc
以进行类型检查。 在编辑器中,我们需要 TypeScript Vue Plugin (Volar) 让 TypeScript 语言服务识别 .vue
类型。
某些情况下独立的 TypeScript 插件性能不够好,可以通过以下步骤启用接管模式:
1.禁用内置的 TypeScript 扩展
- 从 VSCode 的命令面板运行
Extensions: Show Built-in Extensions
- 找到
TypeScript and JavaScript Language Features
,右击选择Disable (Workspace)
2.通过从命令面板运行“Developer: Reload Window”重新加载 VSCode 窗口。
npm脚本
vite:dev | 启动vite开发服务器 |
vite:build | 用vite编译vue工程 |
vite:preview | 启动vite预览服务器,预览编译产物 |
electron:start | 启动electron开发模式,需先启动vite开发服务器才可正常工作 |
electron:package | 打包electron应用,相当于绿色版软件 |
electron:make | 生成electron应用安装包 |
web:build | 为网页应用部署打包,会清理开发依赖,供CI流程使用,本地开发无需关注 |
web:serve | 启动网页应用部署服务,本地开发无需关注 |
lint | 使用eslint对项目进行语法和代码风格检查 |
lint:fix | 使用eslint对项目进行语法和代码风格检查,并尝试自动修复错误 |
prettier | 使用prettier对代码进行格式化 |
文件结构
. | |
├── builtin-server | 内置前端部署服务 |
├── dist | vite build产物 |
├── out | electron打包产物 |
├── public | 静态资源,部署后可外部访问的资源放置于此,如favicon |
├── src | vue工程源代码 |
│ ├── activities | 路由结构第1层:活动组件 |
│ ├── assets | 工程内部资源,部署后仅供项目内部使用的资源 |
│ ├── components | 工程内部通用组件库,业务无关的组件放置于此,按作用分类 |
│ │ ├── Container | 容器组件 |
│ │ ├── Data | 数据展示组件 |
│ │ ├── Demo | 演示用组件,正式项目可删除 |
│ │ ├── Desktop | 桌面应用特有组件,一般为electron相关 |
│ │ ├── Interaction | 交互组件 |
│ │ └── Navigation | 导航组件 |
│ ├── networks | 网络请求封装模块 |
│ │ ├── api-weather.ts | 天气api封装,演示用,正式项目可删除 |
│ │ ├── api.ts | axios-api底层封装 |
│ │ ├── axios-setup.ts | axios默认配置 |
│ │ ├── http-request.ts | axios裸封装,不带预处理功能 |
│ │ └── index.ts | 模块导出汇总入口,下不赘述 |
│ ├── plugins | 功能性插件模块 |
│ │ ├── pinia-persisted.ts | pinia持久化状态存储 |
│ │ ├── v-echarts.ts | echarts组件加载 |
│ │ └── index.ts | |
│ ├── routers | 路由模块 |
│ │ ├── routes.ts | 路由结构定义 |
│ │ └── index.ts | |
│ ├── stores | 状态存储模块 |
│ │ ├── global.ts | 持久化全局存储 |
│ │ ├── secured.ts | 持久加密存储 |
│ │ └── index.ts | |
│ ├── views | 路由结构第2层:视图组件 |
│ │ ├── Home | 路由结构第3层:(Home)视图组件的子级内容 |
│ │ ├── Home.vue | Home视图组件 |
│ │ ├── About.vue | About视图组件 |
│ │ └── Settings.vue | Settings视图组件 |
│ ├── auto-imports.d.ts | 自动导入方法类型定义,自动生成,无需关注 |
│ ├── components.d.ts | 自动导入组件类型定义,自动生成,无需关注 |
│ ├── element-theme.scss | element主题变量自定义样式文件 |
│ ├── App.vue | 应用主体容器组件 |
│ ├── main.ts | 应用加载主入口 |
│ ├── style-electron-titlebar-fix.css | 针对electron应用标题栏高度的样式修正 |
│ ├── style.css | 全局元素样式,也是tailwind的加载入口 |
│ └── vite-env.d.ts | vite使用的环境变量定义,自动生成,无需关注 |
├── README.md | 本文件 |
├── forge.config.js | electron打包的相关配置 |
├── index.html | vue3的主页面,特殊情况才需要编辑 |
├── index.js | electron的主入口 |
├── package-lock.json | npm锁 |
├── package.json | npm配置 |
├── postcss.config.js | postcss配置,主要用来加载tailwind插件 |
├── preload.js | 已弃用 |
├── serve.js | 内置前端部署服务加载入口 |
├── tailwind.config.js | tailwind配置 |
├── tsconfig.json | typescript主配置 |
├── tsconfig.node.json | typescript vite额外配置 |
├── vite.config.ts | vite主配置 |
└── vite.vueuse.import.ts | vueuse额外的自动导入脚本 |