前端VUE3+Vite -- 框架搭建
卸载 vue –安装 vueVitevite 官网学习开始构建越来越大型的应用时,需要处理的代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。我们开始遇到性能瓶颈 —— 使用开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 ,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。1.旨在利用生态系统中的新进展
前端归纳
整理环境组件选择
卸载 vue – 安装 vue
npm uninstall vue-cli - g
vue install vue-cli -g
vue -v 
Vite
为什么选 Vite
开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。我们开始遇到性能瓶颈 —— 使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
1.Vite旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多
  2. JavaScript 工具使用编译型语言编写。
  3. Vite 天然支持引入 .ts 文件。
构建
构建 vite + vueTs 选择vue3
 npm init vite@latest

你还可以通过附加的命令行选项直接指定项目名称和你想要使用的模板。例如,要构建一个 Vite + Vue 项目,运行:
npm init vite@latest my-vue-app --template vue
# npm 7+, 需要额外的双横线:
npm init vite@latest my-vue-app -- --template vue
Vite总结
Vite 是什么:vite是下一代前端开发构建工具,同时它的插件API和JavaScript API 带来了高度的可扩展性,并有完整的类型支持
- 冷启动开发服务器
 - 天然支持
.ts文件 - 解析速度块
 Vite通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。使用缓慢的更新
初始化项目

构建组件模块包
整体结构 如下: install包下一个是自定义指令扩展,一个是后续插件扩展

对本包下面的方法进行一件组装: index.ts
import type {App} from 'vue'
const modules = import.meta.glob('./**/*', {eager: true})
// 安装方法,执行某一类相同操作
function install(app: App<Element>) {
    Object.keys(modules).forEach((key) => {
        const name = key.replace(/(.*\/)*([^.]+).*/gi, '$2')
        const type = key.replace(/^\.\/([\w-]+).*/gi, '$1')
        const module: any = modules[key]
        if (module.default) {
            switch (type) {
                // 用于注册全局指令
                case 'directives':
                    app.directive(name, module.default)
                    break
                // 使用插件
                case 'plugins':
                    typeof module.default === 'function' && module.default(app)
                    break
            }
        }
    })
}
export default install
启动一件组册组件
App.vue
import {createApp} from 'vue'
import App from './App.vue'
import install from './config/install'
// 下面这两个可以先不引入,一个色svg注册样式,一个色本地样式信息,本地样式可以自定义。svg需要引入jar 和配置插件后
import './styles/index.scss'
import 'virtual:svg-icons-register'
const app = createApp(App)
app.use(install)
app.mount('#app')
选择组件模块 element-plus封装组件
构建使用
# NPM
$ npm install element-plus --save

装配全局
创建插件 plugins包用于后续插件使用,上文图片所示 -创建ts文件 :element.ts
import ElementPlus from 'element-plus'
import * as ElementPlusIcons from '@element-plus/icons-vue'
import {App} from 'vue'
export default (app: App) => {
    app.use(ElementPlus)
    for (const [key, component] of Object.entries(ElementPlusIcons)) {
        app.component(key, component)
    }
}
关于仓库 Vuex介绍
什么是VueX:
Vuex  是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
白话文,中间缓存件
为什么选用 Vuex
Vuex 的状态存储是响应式的。当Vue组件从store中读取状态的时候,若 store  中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
Vuex  可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
总结
 Vuex 是一个状态存储响应式管理器,可以对于复杂应用提供一个很好的缓存中间件,方便抽出共享的变量响应式的存储进去,方便后期维护和开发维护
构建
npm install vuex@next --save
创建 store/index.ts文件
import { createStore } from 'vuex'
const store = createStore({
    state () {
        return {
            count: 0
        }
    },
    mutations: {
        increment (state) {
            state.count++
        }
    }
})
export default store
main.ts
import store from "./store";
import {createApp} from 'vue'
import App from './App.vue'
import install from './config/install'
import './styles/index.scss'
import 'virtual:svg-icons-register'
const app = createApp(App)
app.use(store)
app.use(install)
app.mount('#app')
一般在使用的时候,会进行拆分成模块使用,配置全局配置,和一些特殊状态变量改变一下举例:
创建store/modules
app.ts 主目录配置
  user.ts 用户权限状态配置
import {Module} from "vuex";
/**
 * app.ts 主目录配置
 */
export interface AppModule {
    config: any
}
const app: Module<AppModule, any> = {
    namespaced: true,
    state: {
        config: {}
    },
    mutations: {
        setConfig(state, data) {
            state.config = data
        }
    }
}
import {Module} from "vuex";
/**
 * user.ts
 */
export interface UserModule {
    token: string
    user: Record<string, any>
    sidebar: any[]
    permissions: string[]
}
const user: Module<UserModule, any> = {
    namespaced: true,
    state: {
        token: '',
        user: {},
        // 菜单
        sidebar: [],
        // 权限
        permissions: []
    },
    mutations: {
        setToken(state, data) {
            state.token = data
        },
        setUser(state, data) {
            state.user = data
        },
        setSidebar(state, data) {
            state.sidebar = data
        },
        setPermissions(state, data) {
            state.permissions = data
        }
    }
}
export default user
import app, { AppModule } from './app'
import user, { UserModule } from './user'
/**
 * index.ts
 */
export interface rootState {
    app: AppModule
    user: UserModule
}
export default {
    app,
    user
}
import { GetterTree } from 'vuex'
import { rootState } from './moudules'
/**
 * getters.ts
 */
const getters: GetterTree<rootState, any> = {
    // token
    token: state => state.user.token,
    // 管理员信息
    userInfo: state => state.user.user,
    // 通用配置
    config: state => state.app.config,
    // 权限列表
    permissions: state => state.user.permissions,
    sidebar: state => state.user.sidebar
}
export default getters
import { createStore, Store, useStore as baseUseStore } from 'vuex'
import getters from './getters'
import modules from './moudules'
/**
 * index.ts
 */
const store = createStore({
    modules: modules,
    getters
})
export default store
本次组件仓库选择 pinia
官网:链接

Pinia 的优点:
- 更加轻量级:相比 
Vuex,Pinia更加轻量级,因为它不需要使用Vuex的一些复杂的概念,如模块和getter。 - 更加简单易用:Pinia 的 API 设计更加简单易用,因为它使用了 
Vue.js 3的新特性,如Composition API。 - 更加灵活:
Pinia提供了更加灵活的状态管理方式,因为它支持多个store实例,而 Vuex 只支持一个store实例 
Vuex 的优点:
- 更加成熟:
Vuex是一个比较成熟的状态管理库,它已经被广泛使用和测试。 - 更加稳定:
Vuex的稳定性也比Pinia更高,因为它已经经过了多个版本的迭代和改进。 - 更加强大:
Vuex提供了一些高级功能,如中间件和插件,使得它可以处理更加复杂的状态管理需求。 
不过个人偏向于轻量级别的和简单可用的,因为支持多个store 相对vuex 来说小项目写着更舒服,特别适合后端管理系统,同时适用于多仓库定义更简单
构建pinia
npm install pinia
创建 pinia.ts文件。进行main 全局注册插件注册


import store from '@/base/stores'
import type {App} from 'vue'
export default (app: App<Element>) => {
    app.use(store)
}
使用同vuex区别不大 一般定义仓库模块进行统一管理

  详情同官网示例
Router
构建
npm install vue-router@4
router/index.ts

主要代码
/**
 * 固定路由
 */
export const constantRoutes: Array<RouteRecordRaw> = [
    {
        path: '/:pathMatch(.*)*',
        component: () => import('@/views/system/error/404.vue')
    },
    {
        path: '/403',
        component: () => import('@/views/system/error/403.vue')
    },
    {
        path: '/login',
        component: () => import('@/views/system/account/login.vue')
    },
]
/**
 * 基类
 */
export const INDEX_ROUTE_NAME = Symbol()
export const indexRoute: RouteRecordRaw = {
    path: '/',
    component: Layout,
    name: INDEX_ROUTE_NAME,
}
/**
 * 创建router
 */
const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: constantRoutes,
})
export default router
关于路由拦截
配置全局

网络请求 选用 – axios

主要代码
import axios, {AxiosInstance, AxiosRequestConfig, AxiosRequestHeaders} from "axios";
import configs from "@/config";
import useUserStore from "@/base/stores/modules/user";
import {ElMessage} from "element-plus";
import router from "@/base/router";
import feedback from "@/utils/feedback";
/**
 * 创建 axios 实例
 */
const service: AxiosInstance = axios.create({
    baseURL: configs.baseUrl,
    headers: {
        'content-type': 'application/json',
        version: configs.version
    },
    timeout: 10 * 1000 // Timeout
} as AxiosRequestConfig)
/**
 * 配置请求拦截器
 */
service.interceptors.request.use(
    (config: AxiosRequestConfig) => {
        const token = useUserStore().token
        // 增加token 配置
        if (token) {
            config.headers = {token} as AxiosRequestHeaders
        }
        return config
    },
    (error) => {
        return error
    }
)
/**
 * 配置响应拦截
 */
service.interceptors.response.use(
    (response) => {
        switch (response.data.code) {
            case 200:
                return eventResponse.success(response.data)
            case 500:
                return eventResponse.error(response.data)
            case -1:
                return eventResponse.redirect()
            case 2:
                return eventResponse.page(response.data)
            default:
                feedback.notifyError(response.data.msg)
                return eventResponse.error(response.data)
        }
    },
    (error) => {
        ElMessage({type: 'error', message: error.message})
        return Promise.reject(error)
    }
)
/**
 * 事件集
 */
const eventResponse = {
    // 成功
    success: (data: CumResponse<any>) => {
        return data.data
    },
    // 失败
    error: ({code, msg}: CumResponse<any>) => {
        return Promise.reject(msg)
    },
    // 重定向
    redirect: () => {
        const userStore = useUserStore()
        userStore.logout().then(() => {
            router.push('/login').then(r => {
            })
            userStore.resetLoginInfo().then(r => r)
        })
        return Promise.reject()
    },
    // 打开新的页面
    page: ({data}: CumResponse<any>) => {
        window.location.href = data.url
        return data
    }
}
export default service
注册全局 封装get,post 接口
import NProgress from "nprogress";
import service from "@/base/axios";
interface CumRequest {
    get(url: string, params?: any): Promise<any>
    post(url: string, params?: any): Promise<any>
    upload(url: string, params: any): Promise<any>
}
/**
 * 定义路由请求接口
 */
class RequestManager implements CumRequest {
    /**
     * get请求
     *
     * @param url
     * @param params
     */
    get(url: string, params?: unknown): Promise<any> {
        return new Promise((resolve, reject) => {
            NProgress.start()
            service.get(url, {params: params})
                .then((res) => {
                    NProgress.done()
                    resolve(res)
                })
                .catch((err) => {
                    NProgress.done()
                    reject(err)
                })
        })
    }
    /**
     * post 请求
     * @param url
     * @param params
     */
    post(url: string, params?: unknown): Promise<any> {
        return new Promise((resolve, reject) => {
            // 进度条
            NProgress.start()
            service.post(url, params)
                .then((res) => {
                    NProgress.done()
                    resolve(res)
                })
                .catch((err) => {
                    NProgress.done()
                    reject(err)
                })
        })
    }
    /**
     * 文件上传
     * @param url
     * @param file
     */
    upload(url: string, file: any): Promise<any> {
        return new Promise((resolve, reject) => {
            NProgress.start()
            service
                .post(url, file, {
                    headers: {'Content-Type': 'multipart/form-data'}
                })
                .then((res) => {
                    NProgress.done()
                    resolve(res)
                })
                .catch((err) => {
                    NProgress.done()
                    reject(err)
                })
        })
    }
}
const request = new RequestManager()
export default request
调用测试使用
import request from "@/manager/base/RequestManager";
import CooperateOrderSolveRequest from "@/api/afterSale/cooperate/request/CooperateOrderSolveRequest";
/**
 * 创建协同工单
 *
 * @author 徐寿春
 * 2023/4/13 11:42
 */
export function createCooperateOrder(params?: any) {
    return request.post('after/sale/cooperate/createCooperateOrder', params)
}
/**
 * 创建协同工单记录
 *
 * @author 徐寿春
 * 2023/4/13 11:42
 */
export function createCooperateOrderRecord(params?: any) {
    return request.post('after/sale/cooperate/createCooperateOrderRecord', params)
}
/**
 * 获取协同工单详情
 *
 * @author 徐寿春
 * 2023/4/13 11:42
 */
export function getCooperateOrderInfo(params?: any) {
    return request.post('after/sale/cooperate/getCooperateOrderInfo', params)
}
/**
 * 受理协同工单
 *
 * @author 徐寿春
 * 2023/4/13 11:42
 */
export function acceptCurrentCooperateOrder(params?: any) {
    return request.post('after/sale/cooperate/acceptCurrentCooperateOrder', params)
}
/**
 * 解决协同工单
 *
 * @author 徐寿春
 * 2023/4/13 11:42
 */
export function resolveCooperate(params: CooperateOrderSolveRequest) {
    return request.post('after/sale/cooperate/resolveCooperate', params)
}
/**
 *  获取协同工单状态机
 *
 * @author 徐寿春
 * 2023/4/5 13:00
 */
export function getCooperateOrderStatusManager(params?: any) {
    return request.post('after/sale/cooperate/getCooperateOrderStatusManager', params)
}
本地调用主要跨域权限,后段代码需要在web配置把跨域设置为 * 让前端链接

日常本地接口调用mock数据 :vite-plugin-mock
引入依赖
npm i mockjs -D
npm i vite-plugin-mock -D
配置 vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// 解决@ 映射到全路径地址信息
import path from "path";
import { viteMockServe } from "vite-plugin-mock";
// https://vitejs.dev/config/
export default defineConfig({
  // 解决编译@问题
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src")
    }
  },
  base: "/admin/",
  
  plugins: [vue(),
    viteMockServe({
      mockPath: "./src/mock",
      localEnabled: true, // 开发
      prodEnabled: false,// 生产
      injectFile: path.resolve("src/main.ts"), // 解决读取不到mock.ts 问题注入文件
    })
  ],
  server: {
    host: "0.0.0.0",//ip地址
    port: 10086, // 设置服务启动端口号
    open: true // 设置服务启动时是否自动打开浏览器
  }
});
建立包./src/mock 映射上文配置mockPath: “./src/mock”,扫描
书写mock请求
import { MockMethod } from 'vite-plugin-mock'
export default [
  {
    url: '/api/getConfig',
    method: 'get',
    response: () => {
       return 'mock 成功'
    }
  }
] as MockMethod[] // ts 类型返回数组
请求测试

这里的请求axios请求的时候不能加api 因为在使用创建axios 实例的时候我们加了baseUrl,所以如果加了api 那么映射的 /api/api/getConfig
同时因为是mock 数据在本机需要修改本机的baseurl 不要打到后端, 需要修改打到本机
  修改前是后端路由, 127.0.0.1:8080
  修改是服务本机地址 127.0.0.1:10086 用来访问mock

配置页面点击试试
<template>
<button @click="getUrl">点击</button>
34324
</template>
<script lang='ts' setup>
import { apiConfig } from "@/api/app";
function getUrl(){
  apiConfig()
}
</script>
<style scoped>
</style>
测试
可以访问
nprogress 进度条
进度条 官网
npm install --save nprogress
直接调用 start()或者done()来控制进度条。
vite-env.d.ts 配置
declare module 'nprogress' {
    export function configure(options: any): void
    export function start(): void
    export function done(): void
}
import 'nprogress/nprogress.css'
import NProgress from 'nprogress
NProgress.start();
NProgress.done();
登录页测试
<template>
  <el-container class="heightMax">
    <el-aside width="77%">
      <div class="demo-image__lazy">
        <el-image class="heightMax" v-for="url in urls" :key="url" :src="url" lazy />
      </div>
    </el-aside>
    <el-main class="mainMax">
      <div class="login-body">
        <div class="login-container">
          <div class="head">
            <img class="logo" src="https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg" alt="" />
            <div class="name">
              <div class="title">商城</div>
            </div>
          </div>
          <el-form label-position="top"
                   :model="state.ruleForm"
                   ref="formRef"
                   :rules="rules"
                   class="login-form">
            <el-form-item style="color:#545c64" prop="account">
              <el-input type="text"
                        v-model.trim="state.ruleForm.account"
                        autocomplete="off"
                        placeholder="账号">
              </el-input>
            </el-form-item>
            <el-form-item  prop="password">
              <el-input type="password"
                        v-model.trim="state.ruleForm.password"
                        autocomplete="off"
                        placeholder="密码">
              </el-input>
            </el-form-item>
            <el-form-item>
              <div style="color: rgb(67, 151, 84);">登录表示您已同意
                <a @click="visible = true" style=" color: chocolate;">《服务条款》</a>
              </div>
              <el-button style="width: 100%" type="primary" @click="submitForm(formRef)">立即登录</el-button>
              <el-checkbox v-model="state.checked" @change="!state.checked">下次自动登录</el-checkbox>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </el-main>
  </el-container>
  <el-dialog v-model="visible" :show-close="false">
    <template #header="{ close, titleId, titleClass }">
      <div class="my-header">
        <h4 :id="titleId" :class="titleClass">服务条款!</h4>
        <el-button type="danger" @click="close">
          <el-icon class="el-icon--left">
            <CircleCloseFilled />
          </el-icon>
          关闭
        </el-button>
      </div>
    </template>
    服务条款文档
  </el-dialog>
</template>
<script lang="ts" setup>
import type { FormInstance, FormRules } from "element-plus";
import { computed, onMounted, reactive, ref, Ref } from "vue";
import { ElForm, ElInput, ElMessage } from "element-plus";
import store from "@/store";
import { useAdmin } from "@/core/hooks/app";
const { route,router } = useAdmin()
const config = computed(() => store.getters.config);
const formRef = ref<FormInstance>();
const visible = ref(false);
const state = reactive({
  ruleForm: {
    account: "",
    password: ""
  },
  checked: true,
});
const rules = reactive<FormRules>({
  account: [
    { required: true, message: "账户不能为空", trigger: "blur" }
  ],
  password: [
    { required: true, message: "密码不能为空", trigger: "blur" }
  ]
})
function submitForm(formEl: FormInstance | undefined) {
  if (!formEl) return;
  formEl.validate((valid) => {
    if (valid) {
      store.dispatch("user/login", state.ruleForm).then(() => {
        // 增加搜索属性
        const {
          query: { redirect }
        } = route
        // 定位到搜索属性路由
        const path = typeof redirect === "string" ? redirect : "/";
        // 跳转连接
        router.replace(path);
      }).catch(err => {
        state.ruleForm.password = "";
        ElMessage.error("登录失败");
      });
    }
  });
}
const urls = [
  "https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg"
];
</script>
<style scoped lang="scss">
@import "../src/styles/common";
</style>
@import “…/src/styles/common”; 封装样式
.heightMax {
  height: 100%;
}
.mainMax {
  padding-top: 0;
  padding-bottom: 0;
  display: flex;
}
.demo-image__lazy {
  height: 100%;
  overflow-y: auto;
}
.demo-image__lazy .el-image {
  display: block;
  min-height: 200px;
  margin-bottom: 10px;
}
.demo-image__lazy .el-image:last-child {
  margin-bottom: 0;
}
.login-body {
  display: flex;
  width: 100%;
  background-color: #fff;
}
.my-header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}
.login-container {
  width: 100%;
  background-color: #fff;
  border-radius: 4px;
  box-shadow: 0 21px 41px 0 rgba(0, 0, 0, 0.2);
}
.head {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 40px 0 20px 0;
}
.head img {
  width: 100px;
  height: 100px;
  margin-right: 20px;
}
.head .title {
  font-size: 28px;
  color: #1BAEAE;
  font-weight: bold;
}
.head .tips {
  font-size: 12px;
  color: #999;
}
.login-form {
  width: 70%;
  margin: 0 auto;
}
存储token
import { Module } from "vuex";
import cache from "@/utils/cache";
import { TOKEN } from "@/config/cachekey";
import { apiLogin, apiLogout, apiUserInfo } from "@/api/user";
export interface UserModule {
    token: string;
    user: Record<string, any>;
    sidebar: any[];
    permissions: string[];
}
const user: Module<UserModule, any> = {
    namespaced: true,
    state: {
        token: cache.get(TOKEN) || "",
        user: {},
        // 菜
        sidebar: [],
        // 权限
        permissions: []
    },
    mutations: {
        setToken(state, data) {
            state.token = data;
        },
        setUser(state, data) {
            state.user = data;
        },
        setSidebar(state, data) {
            state.sidebar = data;
        },
        setPermissions(state, data) {
            state.permissions = data;
        }
    },
    actions: {
        //清除用户信息
        clearUserCache({ commit }) {
            commit("setToken", "");
            commit("setUser", {});
            commit("setPermissions", {});
        },
        // 登录
        login({ commit }, payload: any) {
            const { account, password } = payload;
            return new Promise((resolve, reject) => {
                apiLogin({
                    account: account.trim(),
                    password: password
                }).then((data: any) => {
                      commit("setToken", data.token);
                      cache.set(TOKEN, data.token);
                      resolve(data);
                  })
                  .catch((error) => {
                      reject(error);
                  });
            });
        },
        // 退出登录
        logout({ dispatch }) {
            return new Promise((resolve, reject) => {
                apiLogout()
                  .then((data) => {
                      cache.remove(TOKEN);
                      resolve(data);
                  })
                  .catch((error) => {
                      reject(error);
                  });
            });
        }
    }
};
export default user;
暴露 user
import { createStore, Module, Store, useStore as baseUseStore } from "vuex";
import app, { AppModule } from "@/store/modules/app";
import user, { UserModule } from "@/store/modules/user";
interface rootState {
    app: AppModule;
    user: UserModule;
}
const store = createStore<rootState>({
    modules: { app, user },
    getters: {
        config: state => state.app.config,
        token: state => state.user.token
    }
});
export default store;
(###) 后续页面搭建 落空
下面是搭建可能遇到的问题:
webstorm 识别不到 vite 的@
在tsConfig.json 编译选项添加
 "paths": {
      "@/*": ["./src/*"]
    }

启动可能会报错找不到@
vite.config.ts 配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// @ts-ignore
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
  plugins: [vue()]
})
没有 scss包报错:internal server error preprocessor dependency sass not found. did you install it vite
安装node-sass 或 sass
npm install node-sass
请求时vue中axios的post请求url自动带上本地ip

  如果是从环境里面获取,或者手写http时候记得写全
参考:VITE_APP_BASE_URL='http://127.0.0.1:8080'

更多推荐
 


所有评论(0)