1. 从 vite 目录到 npm run dev:VUE3 第二章本地调试链路怎么跑通
VUE3 第二章的学习路径,很多人卡在一个很具体的地方:项目能跑起来,但说不清npm run dev到底做了什么;.vue文件能写,但不知道<template>、<script setup>、<style>三块是怎么被编译成浏览器认识的代码;插件装了一堆,Volar 和 Vetur 打架导致类型提示全红。这一章要解决的就是这条链路:vite 认识、SFC 语法规范、VUE3-vscode 插件配置、npm run dev 执行过程、模板语法和 V3 指令,最后把本地调试请求的 endpoint 统一改到 TaoToken,用一把 Key 完成一次可复现的联调验证。
先说清楚这套东西是什么、能做什么、适合谁。vite 是 VUE3 官方推荐的构建工具,它的核心特点是开发阶段不打包,浏览器请求哪个模块就编译哪个模块,所以冷启动快。SFC(Single-File Component)是.vue文件的写法规范,一个文件里同时写模板、逻辑和样式。VUE3-vscode 插件负责让编辑器认识这些语法,给出类型提示和错误检查。npm run dev是把这三者串起来的启动命令。适合刚学完 VUE3 基础语法、准备动手写第一个完整组件、但被工程化细节绊住的人。
我试过在同一个项目里同时开着 Vetur 和 Volar,结果<script setup>里的ref一直报「找不到名称」,排查了半小时才发现是插件冲突。这一章会把这类坑提前标出来。
整章的结构是这样:先讲 vite 目录和 SFC 规范,再讲插件配置,然后拆npm run dev的执行过程,接着写模板语法和指令的实战代码,最后把请求 endpoint 改到 TaoToken 做联调验证,并给出常见报错排查。每一步都有可复制的配置和命令,跟着敲就能跑通。
2. vite 目录结构与 SFC 语法规范:VUE3 单文件组件写法详解
2.1 vite 项目目录里每个文件夹到底干什么
用npm create vite@latest建出来的 VUE3 项目,根目录大概长这样:
my-vue3-app/ ├── public/ ├── src/ │ ├── assets/ │ ├── components/ │ ├── App.vue │ └── main.ts ├── index.html ├── vite.config.ts ├── package.json └── tsconfig.jsonpublic下面的文件不会被编译,原样拷贝到产物目录,适合放favicon.ico、robots.txt这类不需要处理的静态资源。src/assets下面放需要被编译的资源,比如图片、样式文件,vite 会处理它们的引用路径。src/components放组件,App.vue是根组件,main.ts是全局入口脚本。
重点说index.html。webpack、rollup 这类工具的入口是一个 JS 文件(entry input),而 vite 的入口是一个 HTML 文件。vite 启动时不会立刻编译所有 JS,只有浏览器请求到<script type="module" src="/src/main.ts">时,vite 拦截这个请求,才去解析对应的模块。这就是 vite 开发态快的根本原因:按需编译。
vite.config.ts是配置文件,后面改 endpoint 就在这里动手。
2.2 SFC 三种顶层语法块的规范
每个.vue文件由三种顶层块组成:<template>、<script>、<style>。
<template>每个文件最多一个顶层块。它的内容会被提取出来交给@vue/compiler-dom预编译成 JavaScript 渲染函数,挂到导出组件的render选项上。
<script>可以有多个(不含<script setup>),作为 ES Module 执行,默认导出应该是组件选项对象,要么是普通对象,要么是defineComponent的返回值。
<script setup>每个文件最多一个,会被预处理成组件的setup()函数,在每个组件实例中执行。这是 VUE3 组合式 API 的推荐写法。
<style>可以有多个,通过scoped或module属性把样式封装在当前组件内,不同封装模式的<style>可以在同一个组件里混用。
一个完整的 SFC 长这样:
<template> <div class="box">{{ message }}</div> </template> <script setup lang="ts"> import { ref } from 'vue' const message = ref('hello vue3') </script> <style scoped> .box { color: #42b883; } </style>scoped会给样式加上唯一属性选择器,避免污染其他组件。如果你需要全局样式,去掉scoped即可。
2.3 VUE3-vscode 插件配置清单
打开 VS Code 扩展面板,搜索并安装:
| 插件名 | 作用 | 是否必装 |
|---|---|---|
| Vue Language Features (Volar) | VUE3 语法高亮、类型提示、模板检查 | 必装 |
| TypeScript Vue Plugin (Volar) | 让 TS 文件认识.vue导入 | 必装 |
| ESLint | 代码规范检查 | 可选 |
| Prettier | 代码格式化 | 可选 |
注意:VUE3 的 Volar 和 VUE2 的 Vetur 不能同时开启,会冲突。如果你之前装过 Vetur,在扩展面板里把它禁用或卸载,然后重启 VS Code。判断是否生效的方法:打开一个.vue文件,<script setup>里的变量在<template>中应该有类型提示,鼠标悬停能看到类型。
如果提示不生效,检查 VS Code 右下角语言模式是不是Vue,以及settings.json里有没有把.vue关联到错误的语言。可以在settings.json加:
{ "files.associations": { "*.vue": "vue" } }3. npm run dev 执行过程与 vite.config.ts 可复制配置
3.1 npm run dev 到底执行了什么
执行npm run dev时,npm 先去找package.json的scripts字段:
{ "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && vite build", "preview": "vite preview" } }找到dev对应的vite命令。但电脑上并没有全局安装 vite,为什么能执行?因为npm install时,vite 作为依赖被装进node_modules,同时在node_modules/.bin/下创建了可执行文件的软链接。
.bin目录不是任何一个 npm 包,里面的文件是软链接。打开node_modules/.bin/vite,顶部写着#!/bin/sh,说明它是个脚本。npm 执行npm run xxx时,会通过软链接找到node_modules/vite,再看 vite 包里的package.json:
{ "bin": { "vite": "bin/vite.js" } }于是找到bin/vite.js来执行。
查找规则是:先从当前项目的node_modules/.bin找,找不到去全局node_modules/.bin找,再找不到去环境变量 PATH 找。
node_modules/.bin里通常有三个 vite 文件:vite(Unix/Linux/macOS 默认可执行文件,必须输入完整文件名)、vite.cmd(Windows cmd 默认,不写后缀时按 PATHEXT 查找)、vite.ps1(Windows PowerShell 可执行)。Windows 一般执行第二个,macOS/Linux 执行第一个。
3.2 vite.config.ts 可复制配置片段
下面这份配置可以直接复制到项目根目录的vite.config.ts,包含路径别名、开发服务器端口和代理:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { fileURLToPath, URL } from 'node:url' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { port: 5173, host: '0.0.0.0', proxy: { '/api': { target: 'https://taotoken.net/api', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })resolve.alias让@指向src,导入组件时可以写import Hello from '@/components/Hello.vue'。server.proxy把/api开头的请求转发到 TaoToken 的 API 地址,changeOrigin处理跨域,rewrite去掉/api前缀。
如果你用 TypeScript,还需要在tsconfig.json里同步路径别名,否则编辑器会报找不到模块:
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }3.3 启动验证步骤
配置写完后,在项目根目录执行:
npm install npm run dev终端会输出类似:
VITE v5.x.x ready in 300 ms ➜ Local: http://localhost:5173/ ➜ Network: http://192.168.x.x:5173/ ➜ press h + enter to show help浏览器打开http://localhost:5173/,能看到 VUE3 默认页面就说明启动成功。如果端口被占用,vite 会自动换到 5174,注意看终端输出。
4. 模板语法与 V3 指令实战:从插值到 v-model 双向绑定
4.1 模板插值语法
在<script setup>里声明变量,直接在<template>用{{ 变量名 }}使用:
<template> <div>{{ message }}</div> </template> <script setup lang="ts"> const message = 'sh' </script>模板里支持条件运算、算术运算和 API 调用:
<template> <div>{{ message == 0 ? '111' : '222' }}</div> <div>{{ count + 1 }}</div> <div>{{ text.split(',') }}</div> </template> <script setup lang="ts"> const message: number = 1 const count: number = 10 const text: string = '1,2,3,4' </script>注意:模板表达式里不要写太复杂的逻辑,超过一行的运算建议放到computed里。
4.2 v-on 修饰符与冒泡处理
v-on简写@,用来绑定事件。冒泡案例:
<template> <div @click="parent"> <div @click.stop="child">child</div> </div> </template> <script setup lang="ts"> const child = () => { console.log('child') } const parent = () => { console.log('parent') } </script>不加.stop时,点击内层 div 会同时触发 child 和 parent。加上.stop后只触发 child。阻止表单提交用.prevent:
<template> <form action="/"> <button @click.prevent="submit" type="submit">submit</button> </form> </template> <script setup lang="ts"> const submit = () => { console.log('submit') } </script>4.3 v-bind 绑定 class 和 style
普通数组写法:
<template> <div :class="[flag ? 'active' : 'other', 'h']">12323</div> </template> <script setup lang="ts"> const flag: boolean = false </script> <style> .active { color: red; } .other { color: blue; } .h { height: 300px; border: 1px solid #ccc; } </style>对象写法配合 TS 类型:
<template> <div :class="flag">{{ flag }}</div> </template> <script setup lang="ts"> type Cls = { other: boolean h: boolean } const flag: Cls = { other: false, h: true } </script>绑定 style:
<template> <div :style="style">2222</div> </template> <script setup lang="ts"> type Style = { height: string color: string } const style: Style = { height: '300px', color: 'blue' } </script>4.4 v-model 双向绑定
<template> <input v-model="message" type="text" /> <div>{{ message }}</div> </template> <script setup lang="ts"> import { ref } from 'vue' const message = ref('v-model') </script>输入框内容变化时,message同步更新,下面的 div 实时显示。v-model本质是:value加@input的语法糖。
4.5 其他常用指令速查
v-text显示文本,v-html展示富文本(注意 XSS 风险,不要渲染用户输入),v-if/v-else-if/v-else控制元素真假 DOM 切换,v-show通过 CSSdisplay切换,v-for遍历元素。v-if和v-show的区别:v-if会销毁和重建 DOM,v-show只是切换显示,频繁切换用v-show,条件很少变用v-if。
5. 常见报错排查:401、local proxy failed、reading choices 怎么解
5.1 401 Unauthorized
请求 TaoToken API 返回 401,通常是 Key 没带或带错。检查请求头:
Authorization: Bearer sk-xxxxxxxxKey 从 TaoToken 控制台的 API Keys 页面获取。注意不要有多余空格,不要用单引号包裹。如果是在.env文件里配置,变量名建议用VITE_前缀,vite 才会暴露给客户端:
VITE_TAOTOKEN_KEY=sk-xxxxxxxx代码里用import.meta.env.VITE_TAOTOKEN_KEY读取。
5.2 local proxy failed
vite 代理报local proxy failed或ECONNREFUSED,先确认vite.config.ts里server.proxy.target写的是https://taotoken.net/api,不是http。再确认本地网络能访问该地址。如果代理路径 rewrite 写错,比如把/api替换成了空字符串但后端需要这个前缀,也会 404。改完配置要重启npm run dev,vite 不会热更新配置文件。
5.3 reading choices 报错
调用模型接口时出现Cannot read properties of undefined (reading 'choices'),说明返回结构和你解析的字段不匹配。先打印完整响应:
const res = await fetch('/api/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${import.meta.env.VITE_TAOTOKEN_KEY}` }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: '你好' }] }) }) const data = await res.json() console.log(JSON.stringify(data, null, 2))确认data.choices存在后再取值。如果返回的是错误对象,choices自然不存在,先看data.error.message。
5.4 OAuth 与 Codex auth.json 相关
如果你在用 Codex 或类似工具,认证信息可能写在auth.json里。出现 OAuth 相关报错时,检查该文件里的base_url是否指向https://taotoken.net/api,api_key是否和 TaoToken 控制台一致。三件套要写全:Base URL、Key、Model ID,缺一个都会失败。
5.5 插件冲突导致类型全红
<script setup>里所有变量都报「找不到名称」,八成是 Vetur 和 Volar 同时开着。禁用 Vetur,重启 VS Code。如果还不行,在项目根目录建.vscode/settings.json:
{ "vetur.validation.template": false, "vue.server.hybridMode": true }6. 把本地调试 endpoint 改到 TaoToken 完成联调验证
前面配置都跑通后,最后一步是把请求真正发到 TaoToken,验证整条链路。在src下新建api/chat.ts:
const BASE_URL = '/api' export async function chat(prompt: string) { const res = await fetch(`${BASE_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${import.meta.env.VITE_TAOTOKEN_KEY}` }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: prompt }] }) }) if (!res.ok) { throw new Error(`请求失败: ${res.status}`) } const data = await res.json() return data.choices[0].message.content }在组件里调用:
<template> <div> <input v-model="input" type="text" /> <button @click="send">发送</button> <div>{{ reply }}</div> </div> </template> <script setup lang="ts"> import { ref } from 'vue' import { chat } from '@/api/chat' const input = ref('') const reply = ref('') const send = async () => { reply.value = await chat(input.value) } </script>启动npm run dev,输入问题点发送,能看到模型返回内容就说明联调成功。如果要在浏览器里直接对比不同模型的效果,可以打开模型对话页面手动试;如果准备长期做编码类 Agent 或批量调用,建议看 Coding Plan 的额度方案;Key 的创建和管理在 API Keys 页面,接入细节参考接入文档。整条链路的关键就是把 Base URL、Key、Model ID 三件套对齐,本地代理指向https://taotoken.net/api,剩下的就是写业务代码了。