回顾运行4条命令后
接【第三步+前后端分离调】 ,运行命令后
1. 创建Vue3项目骨架
npm create vite@latest vue-project -- --template vue
2. 进入项目文件夹
cd vue-project
3. 安装所有基础依赖
npm install
4. 启动项目
npm run dev
就会得到Vs Code中的这个项目完整结构


项目结构还原:从 0 到 Vue 项目完整结构
我帮你把「从 0 到生成这个项目文件结构」的完整过程,按你之前的操作一步步还原出来,你照着回忆一下就懂了��
一、还原你的完整操作流程(和你之前的步骤完全对应)
第 1 步:创建项目骨架(生成大部分文件)
你在终端执行了创建命令,这一步就生成了除了node_modules和package-lock.json之外的所有文件 / 文件夹:
|-----------------------------------------------------------|
| bash npm create vite@latest vue-project -- --template vue |
这一步是「搭房子」,自动复制了 Vue3 + Vite 模板里的所有基础文件,包括:
- src/ 文件夹(含App.vue、main.js、router等)
- index.html、package.json、vite.config.js 等配置文件
第 2 步:进入项目文件夹
|---------------------|
| bash cd vue-project |
第 3 步:执行 npm install (生成依赖相关文件)
这一步才会下载依赖,生成你看到的:
- node_modules/ 文件夹(所有第三方依赖都在这里)
- package-lock.json(锁定依赖版本)
第 4 步(可选):安装了额外依赖(比如 axios)
|------------------------|
| bash npm install axios |
这一步会更新package.json和package-lock.json,但不会生成新的文件夹,只会往node_modules里加东西。
第 5 步:用 VS Code 打开项目
你在 VS Code 里打开了vue-project文件夹,所以在资源管理器里看到了完整的项目结构。
二、总结标记下:哪些是哪一步生成的
|----------------------------------------|------------------------------|
| 文件 / 文件夹 | 生成时机 |
| .vscode/、public/、src/ | 第 1 步 npm create vite@latest |
| index.html、package.json、vite.config.js | 第 1 步 npm create vite@latest |
| node_modules/、package-lock.json | 第 3 步 npm install |
| router/、stores/ | 模板自带,也是第 1 步生成 |
你真正可能会改或者可能会改的文件


超精简版
Vue3 项目真正需要手动修改、经常用到的文件只有 5 个:
- src/router/index.js → 页面路由(跳转)
- src/stores/ → 全局状态(共享数据)
- src/App.vue → 主页面组件
- src/main.js → 项目入口配置
- vite.config.js → 跨域、端口、代理
目前我们这个项目就只改了src/App.vue → 主页面组件
再给你确认一遍:
- 前端:只改了 App.vue ,其他文件(main.js、vite.config.js 等)全都没动
- 后端:改了 5 个文件
-
ScoreServlet.java ✅
-
test11.java ✅
-
web.xml ✅
-
index.jsp ✅
-
register.html ✅
App.vue的代码
<template>
<!-- 页面最外层容器,控制整体布局和样式 -->
<div class="container">
<!-- 页面标题:成绩录入系统,标注技术栈 Vue3 + Java 后端 -->
<h1>📚 成绩录入系统(Vue3 + Java 后端)</h1>
<!-- 表单区域,用于输入学生人数和成绩 -->
<div class="form">
<!-- 表单项:输入学生人数 -->
<div class="form-item">
<label>学生人数:</label>
<!--
输入框:
type="number" 只能输入数字
v-model.number="n" 双向绑定到变量 n,并且自动转数字类型
min="1" 最小值为1
placeholder 提示文字
-->
<input type="number" v-model.number="n" min="1" placeholder="请输入人数" />
</div>
<!--
成绩输入区域:
v-if="n > 0" 只有当学生人数大于0时才显示这一整块
-->
<div class="form-item" v-if="n > 0">
<label>成绩列表:</label>
<div class="scores">
<!--
循环生成成绩输入框:
v-for="(s, index) in scores" 遍历 scores 数组,生成对应数量的输入框
:key="index" 循环必须加 key,提高 Vue 渲染效率
v-model.number="scores[index]" 双向绑定数组里的每一项成绩
type="number" 数字输入
min="0" 最低分0
max="100" 最高分100
placeholder 显示"第x科成绩"
-->
<input
v-for="(s, index) in scores"
:key="index"
v-model.number="scores[index]"
type="number"
min="0"
max="100"
placeholder="第{{ index + 1 }}科成绩"
/>
</div>
</div>
<!--
提交按钮:
@click="submit" 点击时执行 submit 函数
:disabled="loading" 提交中时按钮禁用,防止重复点击
按钮文字:loading 时显示"提交中...",否则显示正常文字
-->
<button @click="submit" :disabled="loading">
{{ loading ? "提交中..." : "提交计算平均分" }}
</button>
</div>
<!--
结果展示区域:
v-if="result" 只有拿到后端返回结果后才显示
-->
<div class="result" v-if="result">
<h2>✅ 计算结果</h2>
<!-- 展示后端返回的平均分 -->
<p>平均分:{{ result.avg }}</p>
<!-- 展示后端返回的成绩列表 -->
<p>成绩列表:{{ result.scores }}</p>
</div>
</div>
</template>
<!-- Vue3 组合式API 脚本 -->
<script setup>
// 导入 Vue 提供的响应式函数:ref(定义基础类型响应式变量)、watch(监听变量变化)
import { ref, watch } from "vue";
// 学生人数 n,默认值为 1,响应式变量
const n = ref(1);
// 成绩数组,默认有1个成绩0,会根据人数 n 自动生成对应长度
const scores = ref([0]);
// 加载状态:提交时为 true,按钮置灰;结束后为 false
const loading = ref(false);
// 存储后端返回的计算结果(平均分、成绩列表),默认 null
const result = ref(null);
// 监听器:监听学生人数 n 的变化
watch(n, (newVal) => {
// 当人数改变时,自动重置成绩数组:长度为新人数,全部填充 0
scores.value = Array(newVal).fill(0);
});
// 提交函数:把成绩发给 Java 后端,获取平均分
const submit = async () => {
// 开始提交,设置加载中
loading.value = true;
try {
// 1. 拼接请求参数(拼接成后端能识别的格式)
const params = new URLSearchParams();
// 添加学生人数参数
params.append("n", n.value);
// 循环把每一个成绩添加到参数里:score0, score1, score2...
scores.value.forEach((score, index) => {
params.append(`score${index}`, score);
});
// 2. 发送 GET 请求到 Java 后端接口
const res = await fetch("http://localhost:8089/wslant/scoreServlet?" + params.toString(), {
method: "GET", // 请求方式:GET
});
// 3. 把后端返回的数据转成 JSON 格式
const data = await res.json();
// 4. 把结果存到 result 中,页面自动渲染
result.value = data;
} catch (err) {
// 请求失败:弹出提示 + 控制台打印错误
alert("请求失败:" + err.message);
console.error(err);
} finally {
// 无论成功/失败,最后都关闭加载状态
loading.value = false;
}
};
</script>
<!-- 局部样式,只对当前组件生效 -->
<style scoped>
/* 最外层容器:宽度、居中、内边距、字体 */
.container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
font-family: Arial, sans-serif;
}
/* 表单背景、内边距、圆角 */
.form {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
}
/* 表单项间距 */
.form-item {
margin-bottom: 15px;
}
/* 标签样式:独占一行、下边距、加粗 */
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
/* 输入框样式:内边距、边框、圆角 */
input {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
/* 成绩输入框布局:弹性布局、自动换行、间距 */
.scores {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
/* 按钮样式:背景色、文字颜色、无边框、圆角、鼠标手势 */
button {
padding: 10px 20px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* 按钮禁用时的样式:灰色背景 */
button:disabled {
background: #ccc;
}
/* 结果区域:上边距、内边距、边框、圆角 */
.result {
margin-top: 20px;
padding: 15px;
border: 1px solid #42b983;
border-radius: 8px;
}
</style>
-