mirror of
https://github.com/sunniejs/vue-h5-template.git
synced 2026-05-21 10:18:13 +08:00
feat: 优化整体代码
This commit is contained in:
parent
b336529d66
commit
bfca8a8858
@ -16,6 +16,7 @@
|
||||
"ShallowRef": true,
|
||||
"Slot": true,
|
||||
"Slots": true,
|
||||
"Snackbar": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"acceptHMRUpdate": true,
|
||||
@ -71,6 +72,7 @@
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"showToast": true,
|
||||
"storeToRefs": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
|
||||
151
README.md
151
README.md
@ -1,40 +1,147 @@
|
||||
# vue-h5-template
|
||||
<div align="center">
|
||||
<h1>Vue H5 Template</h1>
|
||||
<p>基于 Vue 3 + Vite 7 + TypeScript + 多 UI 组件库 + Pinia + viewport 适配方案,构建移动端快速开发脚手架</p>
|
||||
|
||||
基于 vue3 + vite + (nutui or varlet or vant) + ts + sass + viewport 适配方案 + axios 封装,构建手机端模板脚手架
|
||||
<p>
|
||||
<img src="https://img.shields.io/github/license/sunniejs/vue-h5-template" alt="license" />
|
||||
<img src="https://img.shields.io/github/stars/sunniejs/vue-h5-template?style=social" alt="stars" />
|
||||
<img src="https://img.shields.io/github/forks/sunniejs/vue-h5-template?style=social" alt="forks" />
|
||||
</p>
|
||||
|
||||
如果你对 `ts` 没有要求,想用纯 `js` 进行开发, 可以[点这里](https://github.com/sunniejs/vue-h5-template/tree/vue-h5-template-lite)来获取vue-h5-template-lite
|
||||
<p>
|
||||
<a href="https://sunniejs.github.io/vue-h5-template/">在线文档</a> ·
|
||||
<a href="https://github.com/sunniejs/vue-h5-template/issues">问题反馈</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
如果你不熟悉 vue3,想继续使用 vue2 开发的,可以[点这里](https://github.com/sunniejs/vue-h5-template/tree/vue2-h5-template)来获取 vue2-h5-template
|
||||
---
|
||||
|
||||
如果你想使用 monorepo 架构进行开发,可以[点这里](https://github.com/fonghehe/vue-h5-template)来获取 monorepo 版本
|
||||
## 特性
|
||||
|
||||
详细的说明文档请[点击](https://sunniejs.github.io/vue-h5-template/)查看
|
||||
- **Vue 3.5** + **Vite 7** + **TypeScript 5.9** — 最新技术栈
|
||||
- **多 UI 组件库** — 同时支持 Vant、NutUI、Varlet,按需自动引入
|
||||
- **Pinia 状态管理** — 配合 `pinia-plugin-persistedstate` 实现持久化
|
||||
- **viewport(vw)适配** — 基于 `cnjm-postcss-px-to-viewport`,自动处理 UI 库 375/750 设计稿差异
|
||||
- **Axios + useFetch 双请求方案** — 支持传统 Axios 和 `@vueuse/core` 的 `createFetch`
|
||||
- **vue-i18n 多语言** — 按需懒加载语言包
|
||||
- **文件路由** — 基于 `vite-plugin-pages` 自动生成路由
|
||||
- **丰富的 Vite 插件** — Mock、Eruda 调试、PWA、QRCode、图片压缩、gzip 压缩、打包分析等
|
||||
- **代码规范** — ESLint flat config + Prettier + Stylelint + Husky + lint-staged
|
||||
- **Docker 部署** — 内置 Dockerfile + Nginx 配置
|
||||
|
||||
如果对你有帮助送我一颗珍贵的小星星(づ ̄ 3  ̄)づ ╭❤ ~
|
||||
## 环境要求
|
||||
|
||||
# 关于我
|
||||
| 工具 | 版本 |
|
||||
| ------- | ---------- |
|
||||
| Node.js | >= 20.10.0 |
|
||||
| pnpm | >= 9.12.0 |
|
||||
|
||||
扫描添加下方的微信并备注加交流群(已超过 200 人,只能邀请),交流学习,及时获取代码最新动态。
|
||||
## 快速开始
|
||||
|
||||
<p>
|
||||
<img src="https://cdn.jsdelivr.net/gh/fonghehe/picture/personal/account.jpg" width="256">
|
||||
```bash
|
||||
# 拉取项目
|
||||
git clone https://github.com/sunniejs/vue-h5-template.git
|
||||
|
||||
# 进入项目目录
|
||||
cd vue-h5-template
|
||||
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
|
||||
# 启动项目
|
||||
pnpm dev
|
||||
|
||||
# 打包
|
||||
pnpm build
|
||||
|
||||
# 预览打包结果
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
├── build/ # Vite 构建相关配置
|
||||
│ ├── vite/plugins/ # Vite 插件配置(auto-import、component、mock、eruda 等)
|
||||
│ └── utils.ts # 构建工具函数
|
||||
├── mock/ # Mock 数据
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # 接口管理
|
||||
│ ├── assets/ # 项目资源(字体、图片等)
|
||||
│ ├── layout/ # 布局组件
|
||||
│ ├── locales/ # 国际化语言包
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── store/ # Pinia 状态管理
|
||||
│ ├── styles/ # 全局样式 & SCSS 变量
|
||||
│ ├── utils/ # 工具函数(Axios 封装、useFetch 封装)
|
||||
│ ├── views/ # 页面组件
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.ts # 入口文件
|
||||
├── types/ # TypeScript 类型声明
|
||||
├── .env.* # 多环境变量配置
|
||||
├── vite.config.mts # Vite 配置
|
||||
├── postcss.config.js # PostCSS 配置(viewport 适配)
|
||||
├── Dockerfile # Docker 部署配置
|
||||
└── nginx.conf # Nginx 配置
|
||||
```
|
||||
|
||||
## 集成的 Vite 插件
|
||||
|
||||
| 插件 | 说明 |
|
||||
| ------------------------------- | -------------------- |
|
||||
| `unplugin-auto-import` | 按需自动引入 API |
|
||||
| `unplugin-vue-components` | 按需自动引入组件 |
|
||||
| `vite-plugin-pages` | 文件系统路由 |
|
||||
| `vite-plugin-mock` | 本地 Mock 数据 |
|
||||
| `@zhaojjiang/vite-plugin-eruda` | 移动端调试工具 |
|
||||
| `vite-plugin-svg-icons` | SVG 图标 |
|
||||
| `vite-plugin-compression` | Gzip 压缩 |
|
||||
| `vite-plugin-imagemin` | 图片压缩 |
|
||||
| `vite-plugin-pwa` | PWA 支持 |
|
||||
| `vite-plugin-qrcode` | 开发时二维码 |
|
||||
| `vite-plugin-restart` | 配置文件修改自动重启 |
|
||||
| `vite-plugin-progress` | 构建进度条 |
|
||||
| `@vitejs/plugin-basic-ssl` | 本地 HTTPS |
|
||||
| `rollup-plugin-visualizer` | 打包分析 |
|
||||
|
||||
## 其他版本
|
||||
|
||||
- **vue-h5-template-lite**(纯 JS 版)— [点击查看](https://github.com/sunniejs/vue-h5-template/tree/vue-h5-template-lite)
|
||||
- **vue2-h5-template**(Vue 2 版)— [点击查看](https://github.com/sunniejs/vue-h5-template/tree/vue2-h5-template)
|
||||
- **monorepo 版**(Monorepo 架构)— [点击查看](https://github.com/fonghehe/vue-h5-template)
|
||||
|
||||
## 文档
|
||||
|
||||
详细的使用文档请查看 [在线文档](https://sunniejs.github.io/vue-h5-template/)
|
||||
|
||||
如果对你有帮助,欢迎 Star 支持 ⭐
|
||||
|
||||
## 关于我
|
||||
|
||||
扫描添加下方的微信并备注加交流群,交流学习,及时获取代码最新动态。
|
||||
|
||||
<p>
|
||||
<img src="https://cdn.jsdelivr.net/gh/fonghehe/picture/personal/account.jpg" width="256" />
|
||||
</p>
|
||||
|
||||
如果你觉得该项目有给你带来帮助,方便了你的日常开发,可以请作者喝一杯 ☕ 支持持续的迭代
|
||||
如果你觉得该项目有给你带来帮助,可以请作者喝一杯 ☕ 支持持续的迭代
|
||||
|
||||
<table >
|
||||
<table>
|
||||
<tr align="center">
|
||||
<td>WechatPay</td>
|
||||
<td>AliPay</td>
|
||||
<td>WechatPay</td>
|
||||
<td>AliPay</td>
|
||||
</tr>
|
||||
<tr style="text-align:center">
|
||||
<td> <img src="https://cdn.jsdelivr.net/gh/fonghehe/picture/contribute/wechatPay.jpeg" width="256" /></td>
|
||||
<td>
|
||||
<img src="https://cdn.jsdelivr.net/gh/fonghehe/picture/contribute/aliPay.jpeg" width="256" />
|
||||
</td>
|
||||
<tr align="center">
|
||||
<td><img src="https://cdn.jsdelivr.net/gh/fonghehe/picture/contribute/wechatPay.jpeg" width="256" /></td>
|
||||
<td><img src="https://cdn.jsdelivr.net/gh/fonghehe/picture/contribute/aliPay.jpeg" width="256" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
# Star History
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#tobe-fe-dalao/fast-vue3&Timeline)
|
||||
[](https://star-history.com/#sunniejs/vue-h5-template&Timeline)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./License)
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
/** API request prefix — matches the proxy key in vite server config */
|
||||
export const API_BASE_URL = '/api';
|
||||
|
||||
/** Mock API prefix — used by vite-plugin-mock */
|
||||
export const MOCK_API_BASE_URL = '/mock-api';
|
||||
|
||||
/** Real backend URL for proxy — set to your actual backend address, e.g. 'http://localhost:8080' */
|
||||
export const API_TARGET_URL = '';
|
||||
|
||||
/** Mock server URL for proxy — leave empty when using vite-plugin-mock locally */
|
||||
export const MOCK_API_TARGET_URL = '';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
@ -8,7 +8,10 @@
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"
|
||||
/>
|
||||
<meta name="format-detection" content="telephone=no, email=no, date=no, address=no" />
|
||||
<title>Vite App</title>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<title>Vue H5 Template</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -15,7 +15,7 @@ export default [
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
data: { name: 'Evan', age: 26 },
|
||||
data: { name: 'Evan', age: 26, token: 'mock-token-123456' },
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@ -23,7 +23,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="BasicLayoutPage">
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Home, Horizontal, My, Location } from '@nutui/icons-vue';
|
||||
|
||||
const tabItem = [
|
||||
@ -41,15 +40,18 @@
|
||||
|
||||
const showBorder = ref(true);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
watch(
|
||||
() => router,
|
||||
() => {
|
||||
const judgeRoute = tabItem.some((item) => item.key === router.currentRoute.value.path.replace('/', ''));
|
||||
activeTab.value = tabItem.findIndex((item) => item.key === router.currentRoute.value.path.replace('/', ''));
|
||||
() => route.path,
|
||||
(path) => {
|
||||
const currentKey = path.replace('/', '');
|
||||
const judgeRoute = tabItem.some((item) => item.key === currentKey);
|
||||
activeTab.value = tabItem.findIndex((item) => item.key === currentKey);
|
||||
tabbarVisible.value = judgeRoute;
|
||||
showBorder.value = judgeRoute;
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const tabSwitch = (_item: any, index: number) => {
|
||||
|
||||
@ -21,15 +21,12 @@ export const useUserStore = defineStore('user', {
|
||||
this.info = info ?? '';
|
||||
},
|
||||
async login() {
|
||||
try {
|
||||
const res = await loginPassword(); // 调用登录接口
|
||||
this.setInfo(res); // 设置用户信息
|
||||
this.token = res.token; // 假设返回的 res 包含 token
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.error('Login failed', error);
|
||||
throw error;
|
||||
const res = await loginPassword();
|
||||
this.setInfo(res);
|
||||
if (res?.token) {
|
||||
this.token = res.token;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
// CSS custom properties (light theme defaults)
|
||||
:root {
|
||||
--color-text: #213547;
|
||||
--color-text-secondary: #666;
|
||||
--color-background: #fff;
|
||||
--color-background-soft: #f5f5f5;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-text: rgb(255 255 255 / 87%);
|
||||
--color-text-secondary: rgb(255 255 255 / 60%);
|
||||
--color-background: #1a1a1a;
|
||||
--color-background-soft: #2c2c2c;
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,20 @@
|
||||
import axios from 'axios';
|
||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import { showToast } from 'vant';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const service: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '',
|
||||
withCredentials: false,
|
||||
timeout: 10000,
|
||||
timeout: 15000,
|
||||
});
|
||||
|
||||
service.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
const userStore = useUserStore();
|
||||
if (userStore.token) {
|
||||
config.headers.Authorization = `Bearer ${userStore.token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
@ -21,16 +26,21 @@ service.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const res = response.data;
|
||||
if (res.code !== 200) {
|
||||
showToast(res.msg);
|
||||
return Promise.reject(res.msg || 'Error');
|
||||
} else {
|
||||
return res.data;
|
||||
showToast(res.msg || 'Error');
|
||||
return Promise.reject(new Error(res.msg || 'Error'));
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
console.log('err' + error);
|
||||
showToast(error.message);
|
||||
return Promise.reject(error.message);
|
||||
const status = error.response?.status;
|
||||
if (status === 401) {
|
||||
const userStore = useUserStore();
|
||||
userStore.$reset();
|
||||
window.location.hash = '#/login';
|
||||
}
|
||||
const message = (error.response?.data as any)?.msg || error.message || 'Network Error';
|
||||
showToast(message);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { createFetch } from '@vueuse/core';
|
||||
import { useCookies } from '@vueuse/integrations/useCookies';
|
||||
import { showNotify } from 'vant';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const useFetchApi = createFetch({
|
||||
baseUrl: '',
|
||||
options: {
|
||||
async beforeFetch({ options }) {
|
||||
const myToken = useCookies().get((import.meta.env.VITE_TOKEN_KEY as string) || 'Authorization') || '';
|
||||
const userStore = useUserStore();
|
||||
const token = userStore.token || '';
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${myToken}`,
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
return { options };
|
||||
},
|
||||
|
||||
@ -35,17 +35,20 @@
|
||||
import { detailsData } from '../data';
|
||||
import { Dshop, Dongdong, Cart } from '@nutui/icons-vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const page = ref(1);
|
||||
|
||||
const details = reactive<any>({ data: {} });
|
||||
|
||||
watch(
|
||||
() => router,
|
||||
(val) => {
|
||||
details.data = detailsData.find((_item, index) => index === parseInt(val.currentRoute.value.query.id as string));
|
||||
() => route.query.id,
|
||||
(id) => {
|
||||
const index = Number(id);
|
||||
if (!Number.isNaN(index)) {
|
||||
details.data = detailsData.find((_item, i) => i === index) ?? {};
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user