Vue3 多端统一开发框架 - 快速构建跨平台应用,省时省力"。
- 现代技术栈:Vite 5.2 + Vue 3.5 + TypeScript 5.3 + Pinia 2.1
- 多端适配:一套代码,同时支持 H5 和 PC 端
- UI 组件库:Element Plus 2.7 (PC 端) + Vant 4.9 (移动端)
- 自动导入:组件和 API 自动导入,无需手动 import
- 主题系统:支持多主题切换,内置暗黑模式
- 权限控制:完整的登录认证和权限管理
- 状态管理:基于 Pinia 的状态管理,支持持久化
- 请求封装:Axios 请求封装,支持常规请求和流式请求
- 安全防护:内置请求签名和设备指纹识别
- 图标系统:SVG 图标和 Iconfront 图标组件封装
- 自定义 Hooks:常用功能封装为 Hooks
- 构建优化:自动代码分割、压缩和优化
- Cursor 规则:提供详细的项目规范文档,支持 Cursor AI 开发助手智能理解项目架构和开发规范
src/
├── apis/ # API 接口
│ ├── modules/ # 按模块组织的API
│ │ ├── user.ts # 用户相关API
│ │ └── script.ts # 脚本相关API
│ ├── types/ # API类型定义
│ └── index.ts # 统一导出
├── assets/ # 静态资源
│ └── icons/ # 图标资源
├── components/ # 公共组件
│ ├── SvgIcon/ # SVG图标组件
│ ├── InconFent/ # Iconfront图标组件
│ └── ThemeSwitch/ # 主题切换组件
├── hooks/ # 自定义Hooks
│ ├── useNProgress.ts # 进度条Hook
│ └── useThemeTransition.ts # 主题切换过渡动画Hook
├── router/ # 路由配置
│ ├── home/ # 首页路由模块
│ ├── login/ # 登录路由模块
│ └── index.ts # 路由主文件
├── stores/ # 状态管理
│ ├── theme.ts # 主题状态
│ ├── token.ts # Token管理
│ ├── user.ts # 用户信息
│ └── login.ts # 登录状态
├── styles/ # 全局样式
│ ├── themes/ # 主题相关
│ │ └── variables.scss # 主题变量定义
│ ├── transitions/ # 过渡动画
│ │ └── theme.scss # 主题切换动画
│ └── mixins/ # 样式混入
├── utils/ # 工具函数
│ ├── http/ # HTTP请求封装
│ ├── device.ts # 设备信息
│ └── index.ts # 工具统一导出
└── views/ # 页面组件
├── login/ # 登录页面
├── home/ # 首页
├── settings/ # 设置页面
└── error/ # 错误页面
项目内置了七大核心规范,确保开发过程中的一致性和高质量代码输出:
项目基于现代前端技术栈构建,核心技术包括:
- Vue 3.5.x、TypeScript 5.3.x、Vite 5.2.x
- 状态管理:Pinia 2.1.x (含持久化)
- UI 组件库:Element Plus 2.7.x (PC 端)、Vant 4.9.x (移动端)
- 工具库:Axios、Lodash-es、TypeScript-MD5 等
项目采用模块化、分层次的目录结构,主要包括:
apis
: API 接口层,按业务模块组织components
: 全局公共组件hooks
: 自定义 Hooksrouter
: 路由配置,模块化管理stores
: Pinia 状态管理styles
: 全局样式和主题系统utils
: 工具函数views
: 页面组件
API 请求采用统一封装,包含权限认证、请求签名、错误处理等机制:
- 请求流程:参数准备 → 权限验证 → 请求签名 → 设备指纹 → 请求发送 → 响应处理
- API 定义规范:按业务模块分组、类型严格定义、命名规范统一
- 支持常规请求和流式请求两种方式
采用 JSDoc 风格的注释规范:
- 函数和方法:包含描述、参数说明、返回值说明、示例
- 类和接口:包含描述、属性说明
- 复杂业务逻辑:分步骤说明
- 特殊处理或边界条件:详细说明原因和处理方式
项目封装了多个核心 Hooks 和工具方法:
- 用户认证与权限:
useUserStore
、useTokenStore
- 主题管理:
useThemeStore
、useThemeTransition
- HTTP 请求:标准请求和流式请求封装
- 进度条:
useNProgress
- 安全工具:设备指纹、请求签名
添加新功能模块的标准流程:
- 创建 API 模块:定义接口、参数和响应类型
- 创建视图组件:页面组件及私有组件
- 创建路由配置:定义路由、权限和元信息
- 创建状态管理:定义 State、Getters 和 Actions
基于 Airbnb JavaScript 风格指南,结合 Vue 官方风格指南:
- 变量与引用:优先使用 const,需要重新赋值时使用 let
- 函数:优先使用箭头函数,使用参数默认值
- 组件:使用 PascalCase 命名,详细定义 Props
- TypeScript:严格类型定义,提高代码可读性
- 命名规范:描述性命名,遵循命名约定
pnpm install
# PC端开发
pnpm dev
# H5端开发
pnpm dev:h5
# PC端生产环境
pnpm pro
# H5端生产环境
pnpm proH5
# PC端构建(开发环境)
pnpm build:dev
# H5端构建(开发环境)
pnpm build:dev:h5
# PC端构建(生产环境)
pnpm build:prod
# H5端构建(生产环境)
pnpm build:prod:h5
项目内置了强大的主题系统,支持多主题切换和平滑过渡动画。
主题变量定义在 src/styles/themes/variables.scss
中:
// 基础变量定义
$themes: (
light: (
// 亮色主题
--primary-color: #409eff,
--bg-color: #ffffff,
--text-primary: #303133,
// 更多变量...
),
dark: (
// 暗色主题
--primary-color: #409eff,
--bg-color: #141414,
--text-primary: #ffffff,
// 更多变量...
),
// 可以添加更多自定义主题...
red:
(
--primary-color: #f56c6c,
// 红色主题的其他变量...
),
);
在页面中引入 ThemeSwitch
组件:
<template>
<div class="container">
<!-- 主题切换按钮 -->
<ThemeSwitch />
<!-- 页面内容 -->
</div>
</template>
<script setup lang="ts">
import ThemeSwitch from "@/components/ThemeSwitch/index.vue";
import { useThemeStore } from "@/stores/theme";
// 获取主题状态管理实例
const themeStore = useThemeStore();
// 在组件挂载时初始化主题
onMounted(() => {
themeStore.initTheme();
});
</script>
在父容器中应用主题变量和过渡动画:
<template>
<div class="app-container">
<!-- 页面内容 -->
</div>
</template>
<style lang="scss">
// 导入主题过渡动画样式
@import "@/styles/transitions/theme.scss";
.app-container {
// 使用主题混入
@include useTheme;
// 使用主题变量
background: var(--bg-color);
color: var(--text-primary);
}
</style>
通过 useThemeStore
可以手动控制主题:
import { useThemeStore } from "@/stores/theme";
const themeStore = useThemeStore();
// 切换到暗色主题
themeStore.setTheme("dark");
// 切换到亮色主题
themeStore.setTheme("light");
// 切换到自定义主题
themeStore.setTheme("red");
// 切换暗黑/亮色模式
themeStore.toggleDarkMode();
// 获取当前主题
console.log(themeStore.currentTheme);
// 检查是否为暗黑模式
console.log(themeStore.isDarkMode);
项目使用 View Transitions API 实现平滑的主题切换动画,通过 useThemeTransition
Hook 封装:
import { useThemeTransition } from "@/hooks/useThemeTransition";
const { handleThemeChange } = useThemeTransition();
// 切换主题并应用过渡动画
// 参数1: 目标主题
// 参数2: 触发元素 (可选,用于动画起点)
// 参数3: 动画选项 (可选)
handleThemeChange("dark", triggerElement, {
duration: 500,
easing: "cubic-bezier(0.4, 0, 0.2, 1)",
});
要添加新主题,只需在 src/styles/themes/variables.scss
中的 $themes
变量中添加新的主题配置:
$themes: (
light: (
/* 亮色主题变量 */
),
dark: (
/* 暗色主题变量 */
),
// 添加新主题
purple:
(
--primary-color: #722ed1,
--bg-color: #f9f0ff,
--text-primary: #333333,
// 其他变量...
),
);
在 src/apis/modules
目录下创建模块文件:
// src/apis/modules/product.ts
import axiosInstance from "@/utils/http";
import type { RequestData } from "../types/common";
interface ProductParams {
id: string;
name: string;
}
// 获取产品列表
export const getProducts = (params: ProductParams) => {
const requestData: RequestData<ProductParams> = {
parameter: params,
};
return axiosInstance.post("/api/products", requestData);
};
在 src/apis/index.ts
中导出新模块:
// src/apis/index.ts
export * as userApi from "./modules/user";
export * as scriptApi from "./modules/script";
export * as productApi from "./modules/product"; // 新增
在组件中使用 API:
// 方式1: 直接导入
import { getProducts } from "@/apis/modules/product";
// 方式2: 通过统一导出使用
import { productApi } from "@/apis";
const fetchData = async () => {
try {
// 方式1
const res1 = await getProducts({ id: "1", name: "test" });
// 方式2
const res2 = await productApi.getProducts({ id: "1", name: "test" });
console.log(res1, res2);
} catch (error) {
console.error(error);
}
};
在 src/stores
目录下创建新的 store:
// src/stores/product.ts
import { defineStore } from "pinia";
interface Product {
id: string;
name: string;
price: number;
}
interface ProductState {
products: Product[];
loading: boolean;
}
export const useProductStore = defineStore("productStore", {
state: (): ProductState => ({
products: [],
loading: false,
}),
getters: {
totalProducts: (state) => state.products.length,
totalPrice: (state) =>
state.products.reduce((sum, product) => sum + product.price, 0),
},
actions: {
setProducts(products: Product[]) {
this.products = products;
},
addProduct(product: Product) {
this.products.push(product);
},
async fetchProducts() {
this.loading = true;
try {
// 使用API获取数据
const { data } = await productApi.getProducts({});
this.setProducts(data);
} catch (error) {
console.error(error);
} finally {
this.loading = false;
}
},
},
// 持久化配置
persist: true,
});
在组件中使用 store:
import { useProductStore } from "@/stores/product";
// 在setup中使用
const productStore = useProductStore();
// 读取状态
console.log(productStore.products);
console.log(productStore.totalProducts); // getter
// 修改状态
productStore.setProducts([...]);
productStore.addProduct({ id: "1", name: "Product 1", price: 99 });
// 调用异步action
productStore.fetchProducts();
在 src/views
目录下创建页面组件:
<!-- src/views/product/index.vue -->
<template>
<div class="product-container">
<h1>产品列表</h1>
<div v-if="loading">加载中...</div>
<ul v-else>
<li v-for="item in products" :key="item.id">
{{ item.name }} - {{ item.price }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { productApi } from "@/apis";
const products = ref([]);
const loading = ref(false);
const fetchProducts = async () => {
loading.value = true;
try {
const { data } = await productApi.getProducts({});
products.value = data;
} catch (error) {
console.error(error);
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchProducts();
});
</script>
在 src/router
目录下创建路由模块:
// src/router/product/productRouter.ts
const productRouter = [
{
path: "/products",
name: "Products",
component: () => import("@/views/product/index.vue"),
meta: {
requiresAuth: true,
title: "产品列表",
},
},
{
path: "/products/:id",
name: "ProductDetail",
component: () => import("@/views/product/detail.vue"),
meta: {
requiresAuth: true,
title: "产品详情",
},
},
];
export default productRouter;
在主路由文件中引入并注册路由模块:
// src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import homeRouter from "./home/homeRouter";
import loginRouter from "./login/loginRouter";
import productRouter from "./product/productRouter"; // 新增
const routes = [
// 其他路由...
...homeRouter,
...loginRouter,
...productRouter, // 注册产品路由
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
项目支持多环境配置,可以在根目录创建不同的环境变量文件:
.env
- 所有环境通用的变量.env.development
- 开发环境变量.env.production
- 生产环境变量.env.developmentH5
- H5 开发环境变量.env.productionH5
- H5 生产环境变量
示例配置:
# .env.development
VITE_NODE_ENV=development
VITE_API_BASE_URL=/api
VITE_TEST_URL=http://dev-api.example.com
VITE_PRO_URL=http://api.example.com
在代码中使用环境变量:
// 使用环境变量
const apiUrl = import.meta.env.VITE_API_BASE_URL;
console.log(`当前环境: ${import.meta.env.VITE_NODE_ENV}`);
项目的 vite.config.ts
提供了丰富的功能配置:
无需手动导入 Vue API 和 UI 组件库:
// 自动导入Vue API、Element Plus和Vant组件
AutoImport({
imports: ["vue"], // 自动导入Vue API
resolvers: [ElementPlusResolver(), VantResolver()],
});
// 自动导入组件
Components({
resolvers: [ElementPlusResolver(), VantResolver()],
});
自动为每个 SCSS 文件注入全局变量和混入:
css: {
preprocessorOptions: {
scss: {
additionalData: `
@import "@/styles/themes/variables.scss";
@import "@/styles/mixins/theme.scss";
@import "@/styles/mixins/common.scss";
`;
}
}
}
自动加载和优化 SVG 图标:
createSvgIconsPlugin({
iconDirs: [path.join(__dirname, "src/assets/icons/svg")],
symbolId: "icon-[name]",
});
智能代码分割和压缩:
build: {
minify: "terser",
rollupOptions: {
output: {
manualChunks(id) {
// 不同依赖库提取到独立的文件
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
}
}
}
简化导入路径:
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@c': path.resolve(__dirname, 'src/components'),
'@u': path.resolve(__dirname, 'src/utils')
}
}
- 组件命名: 使用 PascalCase 命名组件
- 目录结构: 复杂组件使用目录包含多个文件
- Props 类型: 始终为 props 定义类型
- 样式隔离: 使用 scoped 或 module 确保样式隔离
<!-- 推荐的组件结构 -->
<template>
<div class="product-card">
<!-- 组件内容 -->
</div>
</template>
<script setup lang="ts">
// Props定义
interface Props {
product: {
id: string;
name: string;
price: number;
};
showDetails?: boolean;
}
// 默认值
const props = withDefaults(defineProps<Props>(), {
showDetails: false,
});
// 事件
const emit = defineEmits<{
(e: "select", id: string): void;
(e: "delete", id: string): void;
}>();
</script>
<style lang="scss" scoped>
.product-card {
@include flex(column, flex-start, stretch);
// 使用主题变量
color: var(--text-primary);
background-color: var(--bg-color);
}
</style>
- 模块化: 按业务领域组织 API
- 类型安全: 为请求和响应定义类型
- 注释: 使用 JSDoc 注释 API 功能和参数
- 错误处理: 统一处理 API 错误
- Store 拆分: 按功能模块拆分 Store
- 类型定义: 为 State、Getters 和 Actions 定义类型
- 持久化: 根据需要配置持久化选项
- Action 复用: 在 Action 中复用其他 Action
- ZsTs119
- Email: zsts@foxmail.com
- GitHub: https://github.com/ZsTs119