零基础玩转 FastAPI_09:前端基础工程构建

阶段 09:前端基础工程构建

合辑 :FastAPI × Vue3 JoyFit 系统阶段开发计划 | 端 :前端 | 阶段 :9 / 12 | 前置阶段 :阶段 08:后端测试与接口文档

大家好,我是小马不起床。

目录

  1. 一、阶段目标
  2. 二、本阶段开发顺序
  3. 三、最终目录结构
  4. 四、本阶段文件清单
  5. [五、步骤 1:创建 Vue3 前端项目](#五、步骤 1:创建 Vue3 前端项目)
  6. [六、步骤 2:安装前端业务依赖](#六、步骤 2:安装前端业务依赖)
  7. [七、步骤 3:配置 package.json](#七、步骤 3:配置 package.json)
  8. [八、步骤 4:配置 Vite](#八、步骤 4:配置 Vite)
  9. [九、步骤 5:配置开发环境变量](#九、步骤 5:配置开发环境变量)
  10. [十、步骤 6:配置前端入口](#十、步骤 6:配置前端入口)
  11. [十一、步骤 7:配置根组件](#十一、步骤 7:配置根组件)
  12. [十二、步骤 8:创建基础路由](#十二、步骤 8:创建基础路由)
  13. [十三、步骤 9:创建后端响应类型](#十三、步骤 9:创建后端响应类型)
  14. [十四、步骤 10:创建 Axios 请求封装](#十四、步骤 10:创建 Axios 请求封装)
  15. [十五、步骤 11:创建全局样式](#十五、步骤 11:创建全局样式)
  16. [十六、步骤 12:创建基础布局](#十六、步骤 12:创建基础布局)
  17. [十七、步骤 13:创建首页骨架](#十七、步骤 13:创建首页骨架)
  18. [十八、步骤 14:创建 404 页面](#十八、步骤 14:创建 404 页面)
  19. [十九、步骤 15:启动前端服务](#十九、步骤 15:启动前端服务)
  20. 二十、和后端联通的基础验证
  21. 二十一、本阶段验收清单
  22. 二十二、常见问题
  23. 二十三、进入下一阶段的条件
  24. 二十四、问题记录与修复方案

说明:文中尖括号 <> 内容为占位符,使用时按实际环境替换。

一、阶段目标

本阶段目标是在后端接口基本稳定、OpenAPI 可以导出的基础上,创建 Vue3 前端工程,并完成前端基础设施。

完成本阶段后,前端应具备以下能力:

  1. 使用 Vite 创建 Vue3 + TypeScript 工程。
  2. 安装 Vue Router、Pinia、Axios、Element Plus。
  3. 配置开发环境 API 代理。
  4. 创建全局请求封装。
  5. 创建基础路由。
  6. 创建基础布局。
  7. 创建前端首页骨架。
  8. 可以通过 npm run dev 启动前端服务。

本阶段仅搭建前端基础工程,不实现登录表单与权限守卫;登录权限在下一阶段实现。

二、本阶段开发顺序

text 复制代码
1. 使用 Vite 创建 frontend 项目
2. 安装前端依赖
3. 配置 Vite 代理
4. 配置环境变量
5. 配置 main.ts
6. 配置 App.vue
7. 创建路由
8. 创建 Axios 请求封装
9. 创建基础布局
10. 创建首页和 404 页面
11. 启动前端服务验证

三、最终目录结构

完成本阶段后,前端核心目录如下:

text 复制代码
frontend/
  docs/
  public/
  src/
    api/
      http.ts
      types.ts
    layouts/
      BasicLayout.vue
    router/
      index.ts
    styles/
      global.css
    views/
      HomeView.vue
      NotFoundView.vue
    App.vue
    main.ts
  .env.development
  index.html
  package.json
  vite.config.ts

四、本阶段文件清单

序号 相对路径 类型 说明
1 frontend/package.json 修改 前端依赖与脚本
2 frontend/vite.config.ts 修改 Vite 插件、别名、开发代理
3 frontend/.env.development 新增 开发环境变量
4 frontend/src/main.ts 修改 Vue 入口、注册 Pinia、Router、Element Plus
5 frontend/src/App.vue 修改 根组件
6 frontend/src/router/index.ts 新增 基础路由
7 frontend/src/api/types.ts 新增 后端统一响应类型
8 frontend/src/api/http.ts 新增 Axios 实例与请求封装
9 frontend/src/styles/global.css 新增 全局样式
10 frontend/src/layouts/BasicLayout.vue 新增 基础布局
11 frontend/src/views/HomeView.vue 新增 首页骨架
12 frontend/src/views/NotFoundView.vue 新增 404 页面

五、步骤 1:创建 Vue3 前端项目

执行位置

确认当前目录为:

text 复制代码
FastAPI_Vue3_JoyFit

即与 backend 同级。

创建命令

bash 复制代码
npm create vite@latest frontend -- --template vue-ts

进入前端目录:

bash 复制代码
cd frontend

安装 Vite 默认依赖:

bash 复制代码
npm install

注意事项

  1. 项目名固定为 frontend。
  2. 模板选择 vue-ts,即 Vue3 + TypeScript。
  3. 若提示是否安装 create-vite,输入 y。

六、步骤 2:安装前端业务依赖

执行位置

text 复制代码
FastAPI_Vue3_JoyFit/frontend

安装命令

bash 复制代码
npm install vue-router@4 pinia axios element-plus @element-plus/icons-vue

依赖说明

依赖 作用
vue-router 前端路由(Vue Router)
pinia 全局状态管理(Pinia)
axios HTTP 请求库(Axios)
element-plus 后台管理 UI 组件库(Element Plus)
@element-plus/icons-vue Element Plus 图标

七、步骤 3:配置 package.json

相对路径

text 复制代码
frontend/package.json

完整代码

json 复制代码
{
  "name": "joyfit-frontend",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite --host 0.0.0.0",
    "build": "vue-tsc -b && vite build",
    "preview": "vite preview --host 0.0.0.0"
  },
  "dependencies": {
    "@element-plus/icons-vue": "latest",
    "axios": "latest",
    "element-plus": "latest",
    "pinia": "latest",
    "vue": "latest",
    "vue-router": "latest"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "latest",
    "typescript": "latest",
    "vite": "latest",
    "vue-tsc": "latest"
  }
}

注意事项

  1. 通过 npm create vite 与 npm install 生成的版本号可能不是 latest,属正常情况。
  2. 无需为对齐版本号而手动修改版本。
  3. 关键在于依赖项与 scripts 字段完整。

八、步骤 4:配置 Vite

相对路径

text 复制代码
frontend/vite.config.ts

完整代码

typescript 复制代码
import { fileURLToPath, URL } from 'node:url'

import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  server: {
    host: '0.0.0.0',
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:8000',
        changeOrigin: true,
      },
    },
  },
})

注意事项

  1. 前端访问 /api 时,Vite 会代理到后端 http://127.0.0.1:8000。
  2. 开发阶段因此不需要直接处理浏览器跨域(CORS, Cross-Origin Resource Sharing)。
  3. 后端仍保留 CORS 配置,方便接口文档(Swagger, OpenAPI)与其他工具访问。

九、步骤 5:配置开发环境变量

相对路径

text 复制代码
frontend/.env.development

完整代码

text 复制代码
VITE_APP_TITLE=JoyFit
VITE_API_BASE_URL=/api

注意事项

  1. Vite 前端环境变量必须以 VITE_ 开头。
  2. 开发环境使用 /api,由 Vite 代理到后端。
  3. 生产环境可再创建 .env.production。

十、步骤 6:配置前端入口

相对路径

text 复制代码
frontend/src/main.ts

完整代码

typescript 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

import App from './App.vue'
import router from './router'
import './styles/global.css'

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(ElementPlus)

app.mount('#app')

注意事项

  1. Pinia 在本阶段先注册,下一阶段的登录状态会使用。
  2. router 在本阶段先注册,下一阶段会增加登录页与路由守卫(Navigation Guard)。
  3. ElementPlus 全局注册,便于直接使用组件。

十一、步骤 7:配置根组件

相对路径

text 复制代码
frontend/src/App.vue

完整代码

vue 复制代码
<template>
  <RouterView />
</template>

注意事项

  1. 根组件仅负责渲染路由出口。
  2. 布局放入 layouts/BasicLayout.vue,避免根组件过重。

十二、步骤 8:创建基础路由

相对路径

text 复制代码
frontend/src/router/index.ts

完整代码

typescript 复制代码
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    component: () => import('../layouts/BasicLayout.vue'),
    children: [
      {
        path: '',
        name: 'home',
        component: () => import('../views/HomeView.vue'),
        meta: {
          title: '首页',
        },
      },
    ],
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'not-found',
    component: () => import('../views/NotFoundView.vue'),
    meta: {
      title: '页面不存在',
    },
  },
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
})

router.afterEach((to) => {
  const title = to.meta.title ? `${String(to.meta.title)} - JoyFit` : 'JoyFit'
  document.title = title
})

export default router

注意事项

  1. 当前仅配置首页与 404。
  2. 登录页、后台页面、用户端页面在后续阶段逐步加入。
  3. BasicLayout 作为主布局,后续后台菜单挂载于此。

十三、步骤 9:创建后端响应类型

相对路径

text 复制代码
frontend/src/api/types.ts

完整代码

typescript 复制代码
export interface ApiResponse<T = unknown> {
  code: number
  message: string
  data: T
}

export interface PageResult<T> {
  items: T[]
  total: number
  page: number
  page_size: number
  pages: number
}

注意事项

  1. 后端统一返回 code、message、data。
  2. 分页结构和后端 paginate 返回字段保持一致。

十四、步骤 10:创建 Axios 请求封装

相对路径

text 复制代码
frontend/src/api/http.ts

完整代码

typescript 复制代码
import axios, {
  type AxiosError,
  type AxiosInstance,
  type AxiosRequestConfig,
  type InternalAxiosRequestConfig,
} from 'axios'
import { ElMessage } from 'element-plus'

import type { ApiResponse } from './types'

const http: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
  timeout: 10000,
})

http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  const token = localStorage.getItem('joyfit_token')

  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }

  return config
})

http.interceptors.response.use(
  (response) => {
    const body = response.data as ApiResponse

    if (body && typeof body.code === 'number' && body.code !== 200) {
      ElMessage.error(body.message || '请求失败')
      return Promise.reject(body)
    }

    return response
  },
  (error: AxiosError<ApiResponse>) => {
    const status = error.response?.status
    const message = error.response?.data?.message || error.message || '网络请求失败'

    if (status !== 401) {
      ElMessage.error(message)
    }

    return Promise.reject(error)
  },
)

export async function request<T = unknown>(config: AxiosRequestConfig): Promise<T> {
  const response = await http.request<ApiResponse<T>>(config)
  return response.data.data
}

export default http

注意事项

  1. 令牌(Token)暂从 localStorage.joyfit_token 读取,下一阶段由登录模块写入;Bearer <访问令牌> 为占位写法,实际值由请求拦截器拼接。
  2. 后端业务错误若 code !== 200,前端统一提示。
  3. HTTP 401 暂不弹出错误提示,下一阶段由登录权限模块统一处理跳转。

十五、步骤 11:创建全局样式

相对路径

text 复制代码
frontend/src/styles/global.css

完整代码

css 复制代码
:root {
  font-family:
    Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
  color: #1f2937;
  background: #f6f7fb;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

* {
  box-sizing: border-box;
}

html,
body,
#app {
  width: 100%;
  min-width: 320px;
  min-height: 100%;
  margin: 0;
}

body {
  min-height: 100vh;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
textarea,
select {
  font: inherit;
}

.page-panel {
  width: 100%;
}

.section-title {
  margin: 0;
  color: #111827;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.4;
}

.section-subtitle {
  margin: 6px 0 0;
  color: #6b7280;
  font-size: 14px;
  line-height: 1.6;
}

注意事项

  1. 全局样式保持精简,便于后续后台页面风格统一。
  2. 禁止在全局样式中编写复杂业务组件样式。

十六、步骤 12:创建基础布局

相对路径

text 复制代码
frontend/src/layouts/BasicLayout.vue

完整代码

vue 复制代码
<template>
  <div class="app-shell">
    <aside class="sidebar">
      <div class="brand">
        <span class="brand-mark">J</span>
        <span class="brand-name">JoyFit</span>
      </div>

      <nav class="nav-list">
        <RouterLink class="nav-item" to="/">工作台</RouterLink>
      </nav>
    </aside>

    <section class="workspace">
      <header class="topbar">
        <div>
          <p class="eyebrow">FastAPI + Vue3</p>
          <h1>JoyFit 管理端</h1>
        </div>
        <el-tag type="success" effect="light">Backend Ready</el-tag>
      </header>

      <main class="content-area">
        <RouterView />
      </main>
    </section>
  </div>
</template>

<style scoped>
.app-shell {
  display: grid;
  min-height: 100vh;
  grid-template-columns: 240px minmax(0, 1fr);
  background: #f6f7fb;
}

.sidebar {
  display: flex;
  flex-direction: column;
  gap: 28px;
  border-right: 1px solid #e5e7eb;
  background: #ffffff;
  padding: 24px 18px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 40px;
}

.brand-mark {
  display: inline-flex;
  width: 36px;
  height: 36px;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: #111827;
  color: #ffffff;
  font-size: 18px;
  font-weight: 800;
}

.brand-name {
  color: #111827;
  font-size: 18px;
  font-weight: 800;
}

.nav-list {
  display: grid;
  gap: 8px;
}

.nav-item {
  display: flex;
  min-height: 40px;
  align-items: center;
  border-radius: 8px;
  padding: 0 12px;
  color: #4b5563;
  font-size: 14px;
  font-weight: 600;
}

.nav-item.router-link-active {
  background: #eef2ff;
  color: #3730a3;
}

.workspace {
  min-width: 0;
}

.topbar {
  display: flex;
  min-height: 88px;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid #e5e7eb;
  background: #ffffff;
  padding: 0 28px;
}

.eyebrow {
  margin: 0;
  color: #6b7280;
  font-size: 13px;
  font-weight: 600;
}

.topbar h1 {
  margin: 4px 0 0;
  color: #111827;
  font-size: 22px;
  line-height: 1.3;
}

.content-area {
  padding: 28px;
}

@media (max-width: 760px) {
  .app-shell {
    grid-template-columns: 1fr;
  }

  .sidebar {
    gap: 14px;
    border-right: 0;
    border-bottom: 1px solid #e5e7eb;
    padding: 16px;
  }

  .topbar {
    align-items: flex-start;
    flex-direction: column;
    gap: 10px;
    padding: 18px 16px;
  }

  .content-area {
    padding: 18px 16px;
  }
}
</style>

注意事项

  1. 当前仅有一个"工作台"菜单。
  2. 登录权限阶段将依据 /api/auth/menus 动态生成菜单。
  3. 布局保留移动端适配,避免后续页面被侧边栏挤压。

十七、步骤 13:创建首页骨架

相对路径

text 复制代码
frontend/src/views/HomeView.vue

完整代码

vue 复制代码
<script setup lang="ts">
import { Connection, DataBoard, DocumentChecked, Operation } from '@element-plus/icons-vue'

const modules = [
  {
    title: '接口文档',
    description: '后端 Swagger 与 OpenAPI 已准备好,可以进入前端对接。',
    icon: DocumentChecked,
  },
  {
    title: '业务模块',
    description: '用户、门店、课程、动作、活动首页接口已完成基础验收。',
    icon: DataBoard,
  },
  {
    title: '请求封装',
    description: 'Axios 已统一处理 Token、业务错误和网络错误。',
    icon: Connection,
  },
  {
    title: '下一阶段',
    description: '继续实现登录页、Token 状态、路由守卫和动态菜单。',
    icon: Operation,
  },
]
</script>

<template>
  <section class="page-panel">
    <div class="hero-band">
      <div>
        <h2 class="section-title">前端基础工程</h2>
        <p class="section-subtitle">
          当前阶段完成工程骨架、路由、状态管理、请求封装和基础布局。
        </p>
      </div>
    </div>

    <div class="module-grid">
      <article v-for="item in modules" :key="item.title" class="module-card">
        <el-icon class="module-icon">
          <component :is="item.icon" />
        </el-icon>
        <div>
          <h3>{{ item.title }}</h3>
          <p>{{ item.description }}</p>
        </div>
      </article>
    </div>
  </section>
</template>

<style scoped>
.hero-band {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: #ffffff;
  padding: 24px;
}

.module-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  margin-top: 18px;
}

.module-card {
  display: grid;
  min-height: 148px;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 14px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: #ffffff;
  padding: 18px;
}

.module-icon {
  width: 40px;
  height: 40px;
  border-radius: 8px;
  background: #ecfeff;
  color: #0891b2;
  font-size: 22px;
}

.module-card h3 {
  margin: 0;
  color: #111827;
  font-size: 16px;
  line-height: 1.4;
}

.module-card p {
  margin: 8px 0 0;
  color: #6b7280;
  font-size: 14px;
  line-height: 1.6;
}

@media (max-width: 1120px) {
  .module-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .module-grid {
    grid-template-columns: 1fr;
  }
}
</style>

注意事项

  1. 首页仅为工程状态面板,不发起业务数据请求。
  2. 正式首页内容在用户端页面阶段对接 /api/home。

十八、步骤 14:创建 404 页面

相对路径

text 复制代码
frontend/src/views/NotFoundView.vue

完整代码

vue 复制代码
<template>
  <section class="not-found">
    <div>
      <p class="code">404</p>
      <h2>页面不存在</h2>
      <p>当前访问的页面还没有配置路由。</p>
      <el-button type="primary" @click="$router.push('/')">返回首页</el-button>
    </div>
  </section>
</template>

<style scoped>
.not-found {
  display: grid;
  min-height: calc(100vh - 144px);
  place-items: center;
  text-align: center;
}

.code {
  margin: 0;
  color: #3730a3;
  font-size: 48px;
  font-weight: 800;
  line-height: 1;
}

.not-found h2 {
  margin: 14px 0 8px;
  color: #111827;
  font-size: 24px;
}

.not-found p {
  margin: 0 0 18px;
  color: #6b7280;
}
</style>

注意事项

  1. 404 页面在本阶段完成,后续路由调整时便于发现路径配置错误。
  2. $router 可在模板中直接使用。

十九、步骤 15:启动前端服务

启动命令

确认当前目录为:

text 复制代码
FastAPI_Vue3_JoyFit/frontend

执行:

bash 复制代码
npm run dev

访问地址

text 复制代码
http://127.0.0.1:5173

若 5173 端口被占用,Vite 会提示新的端口,以终端输出为准。

二十、和后端联通的基础验证

启动后端:

bash 复制代码
cd backend
source .venv/bin/activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

随后启动前端:

bash 复制代码
cd frontend
npm run dev

验证:

  1. 浏览器访问 http://127.0.0.1:5173。
  2. 页面显示 JoyFit 管理端基础布局。
  3. 浏览器控制台无报错信息。
  4. 浏览器网络面板(Network)中访问 /api 的请求由 Vite 代理转发到后端。

本阶段尚无页面主动请求后端接口,仅验证工程可启动、代理配置已存在。

二十一、本阶段验收清单

  • 前端目录:frontend 创建成功
  • 依赖安装:npm install 成功
  • Vite 配置:/api 代理到后端
  • 环境变量:.env.development 存在
  • Vue 入口:main.ts 注册 Pinia、Router、Element Plus
  • 路由:首页与 404 页面可访问
  • 请求封装:src/api/http.ts 存在
  • 基础布局:首页显示基础管理端布局
  • 启动验证:npm run dev 成功
  • 构建验证:npm run build 成功

二十二、常见问题

1. npm create vite@latest 很慢或失败

原因:

网络连接 npm 镜像仓库(npm registry)不稳定。

修复:

稍后重试,或切换本机常用的 npm 镜像源。

2. npm run dev 提示端口被占用

原因:

5173 端口已被其他前端服务占用。

修复:

使用 Vite 提示的新端口访问,或关闭占用该端口的进程。

3. 页面空白

原因:

常见原因为 main.ts、router/index.ts 或组件路径配置错误。

修复:

  1. 打开浏览器控制台查看报错信息。
  2. 确认 src/router/index.ts 存在。
  3. 确认 BasicLayout.vue、HomeView.vue、NotFoundView.vue 文件路径正确。

4. TypeScript 提示找不到模块

原因:

依赖未安装,或 IDE(集成开发环境)未刷新 TypeScript 服务。

修复:

执行:

bash 复制代码
npm install

随后重启编辑器的 TypeScript 服务。

5. 前端请求 /api 失败

原因:

后端未启动,或 Vite 代理(proxy)配置不正确。

修复:

  1. 确认后端运行在 http://127.0.0.1:8000。
  2. 确认 vite.config.ts 中已配置 /api 代理。
  3. 重新启动 npm run dev。

二十三、进入下一阶段的条件

满足以下条件后,进入第十阶段:

必须满足:

  1. frontend 项目创建完成。
  2. npm install 成功。
  3. npm run dev 成功。
  4. 首页可正常显示。
  5. npm run build 成功。
  6. Axios 请求封装存在。
  7. Vite /api 代理配置完成。

以上条件全部满足后,进入下一阶段:阶段 10:前端登录权限与布局。

二十四、问题记录与修复方案

后续在第九阶段实践中遇到问题,可完整记录报错原文,便于检索与定位。

问题按以下两个步骤处理:

  1. 判断错误属于 Vite 创建、依赖安装、TypeScript、路由、Element Plus、Axios 还是代理配置问题。
  2. 将修复办法追加到本文件底部。

问题追加格式

text 复制代码
### 问题 N:问题标题

#### 报错现象

按下方模板记录报错信息。

#### 原因分析

这里说明为什么会报错。

#### 修复办法

这里给出具体修复步骤。

#### 需要修改的文件

- 相对路径:xxx
- 修改说明:xxx
- 完整代码或补丁:xxx

问题 1:Vue 报 Element is missing end tag

报错现象

执行:

bash 复制代码
npm run dev

Vite 报错:

text 复制代码
Internal server error: Element is missing end tag.
Plugin: vite:vue
File: frontend/src/layouts/BasicLayout.vue

或:

text 复制代码
File: frontend/src/views/HomeView.vue
原因分析

BasicLayout.vue、HomeView.vue 或 NotFoundView.vue 的 <style scoped> 未编写结束标签:

vue 复制代码
</style>

Vue 单文件组件(SFC, Single File Component)必须保证 <template>、<script>、<style> 标签完整闭合。编译器通常在解析到 <style scoped> 时才发现前面的结构未完整结束,因此报错位置看似位于 CSS,实际是 SFC 标签未闭合。

修复办法

在每个 .vue 文件的样式(CSS)末尾补充:

vue 复制代码
</style>
需要修改的文件
  • 相对路径:frontend/src/layouts/BasicLayout.vue
  • 修改说明:在文件末尾补充 </style>
  • 补丁:
vue 复制代码
@media (max-width: 760px) {
  .content-area {
    padding: 18px 16px;
  }
}
</style>
  • 相对路径:frontend/src/views/HomeView.vue
  • 修改说明:在文件末尾补充 </style>
  • 补丁:
vue 复制代码
@media (max-width: 640px) {
  .module-grid {
    grid-template-columns: 1fr;
  }
}
</style>
  • 相对路径:frontend/src/views/NotFoundView.vue
  • 修改说明:若该文件同样按本文档创建,也在文件末尾补充 </style>
  • 补丁:
vue 复制代码
.not-found p {
  margin: 0 0 18px;
  color: #6b7280;
}
</style>
处理完后再验证

重新运行:

bash 复制代码
npm run dev

若标签闭合正确,Element is missing end tag 提示消失,页面可正常编译。

问题 2:TS2307 找不到 .vue 组件模块

报错现象

TypeScript 报错:

text 复制代码
TS2307: Cannot find module '../layouts/BasicLayout.vue' or its corresponding type declarations.
TS2307: Cannot find module '../views/HomeView.vue' or its corresponding type declarations.
TS2307: Cannot find module '../views/NotFoundView.vue' or its corresponding type declarations.
原因分析

router/index.ts 中导入的是 .vue 单文件组件:

typescript 复制代码
component: () => import('../layouts/BasicLayout.vue')

若项目缺少 Vite/Vue 的类型声明文件(Type Declaration),TypeScript 无法识别 .vue 文件可作为模块导入,即报 TS2307。

Vite 创建的 Vue + TypeScript 项目通常会生成 src/vite-env.d.ts,若该文件被删除或未生成,需要手动补充。

修复办法

新增 frontend/src/vite-env.d.ts,声明 Vite 环境类型与 .vue 模块类型。

需要修改的文件
  • 相对路径:frontend/src/vite-env.d.ts
  • 修改说明:新增 Vue 单文件组件类型声明
  • 完整代码:
typescript 复制代码
/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'

  const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>
  export default component
}
处理完后再验证

执行:

bash 复制代码
npm run build

若类型声明生效,router/index.ts 中导入 .vue 文件的 TS2307 报错消失。

相关推荐
hunterandroid14 小时前
HarmonyOS HUKS 密钥全生命周期实战:生成、轮换与失效处理
前端·华为
东鹏特饮14 小时前
贪吃蛇小游戏html
前端·javascript·html
溪语流沙14 小时前
【每天一个CSS | Day23】边框上有一道光在跑的流光呼吸卡片
前端·css
小马不起床14 小时前
零基础玩转 FastAPI_10:前端登录、权限与布局
前端·状态模式·fastapi
程序员Sunday14 小时前
JavaScript 深拷贝和浅拷贝有什么区别?structuredClone 能替代 JSON 拷贝吗?
前端·javascript
ShineWinsu15 小时前
对于CSS:选择器的解析
前端·css·面试·html·web·选择器·网页
m0_5873830015 小时前
碰碰好评卡功能落地实战 从需求梳理到上线部署全流程实操指南
java·linux·服务器·前端·javascript·vue.js·人工智能
Pu_Nine_915 小时前
Vue‑Flow 从零入门实战|AI‑Agent 拓扑图开发
前端·javascript·vue.js
keyipatience15 小时前
CSS 学习(1):语法、引入方式、选择器、字体与背景
前端·css·html
程序人生88815 小时前
本地向量底座怎么搭?DocConverter Web 的 BGE 嵌入模型、ChromaDB 与外部 Milvus 接入
前端·milvus