vue3+vite+pinia+cass最新版本编写流程,无报红

Vue Router,pinia插件安装和使用

npm install vue-router@4 pinia

新增src/router/index.ts

// 引入Vue Router的核心方法与类型
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'


// 定义所有路由规则,便于权限控制、自动化管理
const routes: RouteRecordRaw[] = [
  {
    path: '/',                // 首页路由
    name: 'Home',
    component: () => import('@/views/Home.vue'), // 懒加载,提升首屏性能
  },
  {
    path: '/about',           // 关于页路由
    name: 'About',
    component: () => import('@/views/About.vue'),
  },
]

// 创建并导出router实例,统一在main.ts注册
export const router = createRouter({
  history: createWebHistory(), // 使用HTML5 history模式,支持SEO与回退
  routes,
})

新增src/store/index.ts (mkdir -p src/store && touch src/store/index.ts)

// 引入并创建Pinia实例,便于主入口集成
import { createPinia } from 'pinia'

// 导出Pinia实例
export const pinia = createPinia()

新增src/store/useUserStore.ts (mkdir -p src/store && touch src/store/useUserStore.ts)

// src/store/useUserStore.ts
import { defineStore } from 'pinia'
import http from '@/utils/http'
import type { UserInfo } from '@/types/user'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '未登录用户' as string,
    isLoggedIn: false as boolean,
  }),
  actions: {
    async login(name: string) {
      // 演示写死,实际可以用 http.post 登录
      this.name = name
      this.isLoggedIn = true
    },
    logout() {
      this.name = '未登录用户'
      this.isLoggedIn = false
    },
    async fetchUser() {
      // 泛型声明:确保 data 是 UserInfo 类型
      const data = await http.get<UserInfo>('/user/info')
      this.name = data.name
      this.isLoggedIn = data.isLoggedIn
    }
  }
})

新增 src/views/AppHome.vue (mkdir -p src/views && touch src/views/AppHome.vue)

<template>
  <div class="home-box">
    <h1>首页</h1>
    <p>欢迎:{{ user.name }}</p>
    <button v-if="!user.isLoggedIn" @click="user.login('张三')">登录</button>
    <button v-else @click="user.logout()">登出</button>
    <button @click="refreshUser">刷新用户信息</button>
    <router-link to="/about">关于我们</router-link>
  </div>
</template>

<script setup lang="ts">
import { useUserStore } from '@/store/useUserStore'
const user = useUserStore()
const refreshUser = () => user.fetchUser()
</script>

<style lang="scss" scoped>
@use 'sass:color';
// 无需再次 import,$primary-color 已注入
.home-box {
  padding: 20px;
  background: color.adjust($primary-color, $lightness: 40%);
  color: $primary-color;
  border: 1px solid $primary-color;
  border-radius: 8px;
}
</style>

新增tsconfig.app.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

新增src/views/AppAbout.vue (touch src/views/AppAbout.vue)

<template>
  <div class="about-box">
    <h1>关于我们</h1>
    <p>本网站是由 Vue 3 + Vite + TypeScript 构建的现代前端项目。</p>
    <router-link class="link" to="/">返回首页</router-link>
  </div>
</template>

<script setup lang="ts">
// 此页面目前不需要逻辑,留空 script 即可
</script>

<style lang="scss" scoped>
@use 'sass:color';
// 变量 $primary-color 已自动全局注入,无需重复 @use

.about-box {
  padding: 24px;
  max-width: 600px;
  margin: 0 auto;
  border: 1px solid $primary-color;
  border-radius: 8px;
  color: $primary-color;
  background: color.adjust($primary-color, $lightness: 42%);
  // 或 color.scale($primary-color, $lightness: 42%);
}

.link {
  display: inline-block;
  margin-top: 16px;
  color: color.adjust($primary-color, $lightness: -10%);
  // 或 color.scale($primary-color, $lightness: -10%);
  font-weight: bold;
  text-decoration: none;

  &:hover {
    text-decoration: underline;
  }
}
</style>

新增vite.config.ts

import path from 'path'  // ← 这一行很重要!

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),  // 确保 __dirname 和 'src' 用对
    },
  },
})

修改App.vue

<template>
  <router-view />
</template>

sass,axios插件的安装和使用,环境变量配置,跨域

安装

npm install -D sass          # 只需安装 sass,Vite 已自动支持 .scss/.sass
npm install axios

新建src/styles/_variables.scss (touch src/styles/_variables.scss)

$primary-color: #42b983;
$font-size-base: 16px;

src/styles/global.scss (touch src/styles/global.scss )

@use './variables' as *;

body {
  font-size: $font-size-base;
  color: $primary-color;
  font-family: 'Roboto', Arial, sans-serif;
  margin: 0;
}

main.ts新增

import './styles/global.scss'

vite.config.ts新增

  css: {
    preprocessorOptions: {
      scss: {
      additionalData: `@use "@/styles/variables.scss" as *;`
      }
    }
  },

新建src/utils/http.ts (touch src/utils/http.ts),处理不当会报错("Property 'isLoggedIn' does not exist on type 'AxiosResponse<UserInfo, any>'.ts(2339)")

import axios  from 'axios'
import type { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'

const http = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
  headers: { 'Content-Type': 'application/json' }
})

http.interceptors.request.use(
  config => config,
  error => Promise.reject(error)
)

http.interceptors.response.use(
  (response: AxiosResponse) => {
    if (response.data && typeof response.data === 'object' && 'data' in response.data) {
      return response.data.data
    }
    return response.data
  },
  (error: AxiosError) => Promise.reject(error)
)

// 重点是这一行的泛型
const get = <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
  return http.get<unknown, T>(url, config)
}

const post = <T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> => {
  return http.post<unknown, T>(url, data, config)
}

export default { get, post }

新建.env .env.production (touch .env && touch .env.production )

VITE_API_BASE_URL=https://dev-api.example.com
VITE_APP_TITLE=My Vite App

VITE_API_BASE_URL=https://prod-api.example.com
VITE_APP_TITLE=My Vite App [PROD]

修改vite.config.ts,新增

  server: {
    proxy: {
      // 只要是 /api 开头的请求,代理到后端
      '/api': {
        target: 'https://dev-api.example.com',   // 后端接口地址
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, ''),
      }
    }
  },

新建src/types/user.ts (mkdir -p src/types && touch src/types/user.ts )

// src/types/user.ts
export interface UserInfo {
  name: string
  isLoggedIn: boolean
}

总结:
数据流写法,先写http自定义请求,再写pinia连接口拿数据,之后写view层代码,写router层,(注册插件,代码结构,一般是会提前搞好)

posted @ 2025-05-18 04:44  $Traitor$  阅读(62)  评论(0)    收藏  举报