---
name: vue3-mobile-starter
description: 快速搭建 Vue3 移动端应用（TS、Router、Pinia、SCSS、Vant、Vite、Axios、px-to-viewport）。当用户创建新的 Vue3 移动端项目时调用。
---

# 技能详细说明

用于快速初始化 Vue3 移动端项目骨架，内置 TypeScript、Vue Router、Pinia、SCSS、Vant、Vite、Axios、postcss-px-to-viewport-8-plugin。遵循本项目规范输出文件模板与执行步骤，确保以 pnpm 安装与运行。

## 1. 核心规范

- 技术栈：Vue 3 + TypeScript + Vite + Pinia + Vue Router + Axios + SCSS + Vant
- 构建工具：Vite（包含 PostCSS 配置，使用 postcss-px-to-viewport-8-plugin）
- 包管理器：pnpm（必须使用 pnpm 安装与运行脚本）
- 移动端适配：vw 转换（viewportWidth 推荐 375），附加完整可调参数
- 目录结构：清晰分层（router、store、styles、utils、views、components）
- 代码要求：函数需具备 JSDoc 注释，包含参数、返回值、异常；变量需添加用途注释

## 2. 代码结构模板

以下模板可直接复制到新工程中使用。按需调整命名与路径。

### 2.1 package.json（片段）

说明：确保使用 pnpm 脚本与常用命令。

```json
{
  "name": "vue3-mobile-starter",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview --port 5173"
  },
  "dependencies": {
    "axios": "^1.6.8",
    "pinia": "^2.1.7",
    "vant": "^4.8.0",
    "vue": "^3.4.0",
    "vue-router": "^4.3.0"
  },
  "devDependencies": {
    "autoprefixer": "^10.4.18",
    "postcss": "^8.4.35",
    "postcss-px-to-viewport-8-plugin": "^1.2.0",
    "sass": "^1.71.1",
    "typescript": "^5.3.3",
    "vite": "^5.1.0",
    "@types/node": "^20.11.25"
  }
}
```

### 2.2 tsconfig.json（基础）

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "preserve",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
```

### 2.3 vite.config.ts（含 PostCSS 与别名）

```ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "node:path";
import pxToViewport from "postcss-px-to-viewport-8-plugin";

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/variables.scss" as *;`,
      },
    },
    postcss: {
      plugins: [
        pxToViewport({
          unitToConvert: "px",
          viewportWidth: 375,
          unitPrecision: 6,
          propList: ["*"],
          viewportUnit: "vw",
          fontViewportUnit: "vw",
          selectorBlackList: [".ignore", ".hairlines"],
          minPixelValue: 1,
          mediaQuery: false,
          replace: true,
          exclude: [],
          landscape: false,
        }),
      ],
    },
  },
  server: {
    port: 5173,
    open: true,
  },
});
```

### 2.4 postcss.config.js（可选，若不在 Vite 中内联）

```js
/* eslint-disable @typescript-eslint/no-var-requires */
const pxToViewport = require("postcss-px-to-viewport-8-plugin");

module.exports = {
  plugins: [
    pxToViewport({
      unitToConvert: "px",
      viewportWidth: 375,
      unitPrecision: 6,
      propList: ["*"],
      viewportUnit: "vw",
      fontViewportUnit: "vw",
      selectorBlackList: [".ignore", ".hairlines"],
      minPixelValue: 1,
      mediaQuery: false,
      replace: true,
      exclude: [],
      landscape: false,
    }),
  ],
};
```

### 2.5 index.html（视口）

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
    />
    <link rel="icon" href="/favicon.ico" />
    <title>Vue3 Mobile</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
```

### 2.6 src/main.ts（应用入口）

```ts
import { createApp } from "vue";
import { createPinia } from "pinia";
import { router } from "@/router";
import App from "./App.vue";
import "vant/lib/index.css";
import "@/styles/index.scss";
import { Button } from "vant";

// app: 应用实例
const app = createApp(App);

// store: 全局状态管理
const store = createPinia();

/**
 * 初始化应用
 * @throws Error 初始化过程中抛出的未捕获异常
 * @returns void 无返回值
 */
function bootstrap(): void {
  // 注册 Pinia
  app.use(store);
  // 注册路由
  app.use(router);
  // 注册基础 Vant 组件（示例）
  app.use(Button);
  // 挂载应用
  app.mount("#app");
}

bootstrap();
```

### 2.7 src/router/index.ts（路由）

```ts
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";

// routes: 路由表
const routes: RouteRecordRaw[] = [
  {
    path: "/",
    name: "home",
    component: () => import("@/views/Home.vue"),
  },
];

/**
 * 创建并导出路由实例
 * @returns 路由实例
 * @throws Error 当路由初始化失败时抛出
 */
export const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
});
```

### 2.8 src/store/index.ts（Pinia）

```ts
import { createPinia } from "pinia";

/**
 * 创建 Pinia 实例（在 main.ts 中使用）
 * @returns Pinia 实例
 */
export function setupStore() {
  // pinia: 全局状态实例
  const pinia = createPinia();
  return pinia;
}
```

### 2.9 src/store/modules/app.ts（示例 Store）

```ts
import { defineStore } from "pinia";

// AppState: 应用状态类型
interface AppState {
  // title: 应用标题
  title: string;
}

/**
 * 应用级 Store
 * @param 无
 * @returns Store 实例
 * @throws Error 无特别异常
 */
export const useAppStore = defineStore("app", {
  state: (): AppState => ({
    title: "Vue3 Mobile",
  }),
  actions: {
    /**
     * 设置标题
     * @param newTitle 新标题
     * @returns void 无返回值
     * @throws Error 当 newTitle 非法时可能抛出
     */
    setTitle(newTitle: string): void {
      this.title = newTitle;
    },
  },
});
```

### 2.10 src/utils/request.ts（Axios 封装）

```ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";

// BASE_TIMEOUT: 基础超时时间（毫秒）
const BASE_TIMEOUT = 15000;

/**
 * 创建 Axios 实例
 * @returns Axios 实例
 * @throws Error 创建实例失败
 */
function createService(): AxiosInstance {
  // service: Axios 实例
  const service = axios.create({
    baseURL: import.meta.env.VITE_API_BASE_URL || "/",
    timeout: BASE_TIMEOUT,
  });

  // 请求拦截
  service.interceptors.request.use(
    (config: AxiosRequestConfig) => {
      // token: 鉴权令牌
      const token = "";
      if (token) {
        config.headers = config.headers || {};
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    },
    (error) => {
      return Promise.reject(error);
    },
  );

  // 响应拦截
  service.interceptors.response.use(
    (response: AxiosResponse) => {
      return response.data;
    },
    (error) => {
      return Promise.reject(error);
    },
  );

  return service;
}

// http: 全局复用的 Axios 实例
export const http = createService();

/**
 * 通用请求方法
 * @template T 返回数据类型
 * @param config Axios 请求配置
 * @returns Promise<T> 响应数据
 * @throws Error 网络错误或业务异常
 */
export function request<T = unknown>(config: AxiosRequestConfig): Promise<T> {
  return http.request<any, T>(config);
}
```

### 2.11 src/styles/variables.scss（变量示例）

```scss
// $primary-color: 主题色
$primary-color: #1989fa;
```

### 2.12 src/styles/index.scss（全局样式）

```scss
/* 基础重置与通用样式 */
html,
body,
#app {
  height: 100%;
}
body {
  margin: 0;
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #fff;
}

.text-primary {
  color: $primary-color;
}
```

### 2.13 src/views/Home.vue（示例页面）

```vue
<template>
  <section class="home">
    <van-button type="primary" @click="handleClick">{{ title }}</van-button>
  </section>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { useAppStore } from "@/store/modules/app";

// store: 应用 Store
const store = useAppStore();

// title: 计算后的标题
const title = computed(() => store.title);

/**
 * 点击事件处理
 * @returns void 无返回值
 * @throws Error 无特别异常
 */
function handleClick(): void {
  store.setTitle("Hello Mobile");
}
</script>

<style lang="scss" scoped>
.home {
  padding: 16px;
}
</style>
```

### 2.14 src/App.vue（应用根组件）

```vue
<template>
  <router-view />
  <!-- 可在此放置全局层级组件，例如全局提示 -->
  <div class="app-footer">Vue3 Mobile Starter</div>
  <router-link to="/" style="display: none">home</router-link>
  <!-- router-link 仅为确保路由依赖在构建期被保留，可移除 -->
  <!-- 上述注释说明仅为模板指引，可按需删除 -->
</template>

<script setup lang="ts">
// 无特别逻辑
</script>

<style lang="scss">
.app-footer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 12px 0;
  text-align: center;
  color: #666;
  font-size: 12px;
}
</style>
```

### 2.15 环境变量（.env.development 示例）

```env
VITE_API_BASE_URL=/api
```

## 3. 注意事项

1. 统一使用 pnpm：安装与运行脚本均使用 pnpm（避免 npm/yarn）。
2. Vant 引入：示例仅展示按钮组件按需注册，实际项目请按需引入其他组件。
3. px-to-viewport：推荐 viewportWidth=375；若视觉稿为 750，可设置 750，并据此调整设计规范。
4. 字体与 1px 细线：如需对特定选择器禁用转换，使用 `.ignore` 前缀或设置 selectorBlackList。
5. Axios 封装：根据后端返回结构调整响应拦截与错误处理逻辑；谨慎处理 token。
6. 样式组织：建议使用 SCSS 模块与变量管理主题；按需覆盖 Vant 样式变量。

## 4. 执行步骤

1. 创建模板工程（Vue + TS）：
   - pnpm create vite my-app --template vue-ts
2. 进入目录并安装依赖：
   - cd my-app
   - pnpm add vue-router pinia axios vant
   - pnpm add -D sass postcss autoprefixer postcss-px-to-viewport-8-plugin
3. 配置 Vite 与 PostCSS：
   - 复制 2.3 的 vite.config.ts（或使用 2.4 的 postcss.config.js）
4. 建立目录与文件：
   - src/router、src/store、src/utils、src/styles、src/views
   - 复制 2.6～2.15 示例文件
5. 运行与预览：
   - pnpm dev
   - pnpm build && pnpm preview
6. 可选优化：
   - 引入按需自动导入（unplugin-auto-import/unplugin-vue-components）进一步简化 Vant 使用
   - 接入 ESLint/Prettier/Husky 保障代码质量

当用户需要从零创建新的 Vue3 移动端项目且要求集成 TypeScript、Vue Router、Pinia、SCSS、Vant、Vite、Axios 与 postcss-px-to-viewport-8-plugin 时，调用本技能以生成模板与步骤清单。
