
如何创建与编写一个Vue3前端
0. 成果展示
由于是事后复盘,话不多说先上成品与完整代码:
<template>
<div class="top">
<el-container>
<el-header>
<Header />
</el-header>
<el-main>
<div class="content">
<div class="bannerText">大学物理虚拟实验平台</div>
<el-divider />
<div class="subTitle">基础实验</div>
<div class="cardContainer">
<el-card style="width: 30%" @click="toInfo">
<div style="cursor: pointer">
<div class = "cardImage"></div>
<div class="cardTitle">光学 - 等厚干涉实验</div>
<div class="cardDiscription">使用牛顿环与劈尖进行等厚干涉实验。</div>
</div>
</el-card>
</div>
<el-divider />
<div class="subTitle">拓展实验</div>
<el-empty description="栏目正在建设中" />
</div>
</el-main>
<el-footer>
<Footer />
</el-footer>
</el-container>
</div>
</template>
<script setup>
import Header from "../components/Header.vue"
import Footer from "../components/Footer.vue"
import { useRouter } from 'vue-router'
const router = useRouter()
const toInfo = () => {
const route = router.resolve("/detail")
window.open(route.href)
}
</script>
<style scoped>
.top {
width: 100%;
}
.content {
display: flex;
flex-direction: column;
}
.bannerText {
display: flex;
height: 3em;
width: 100%;
color: #000;
font-size: 3.2em;
font-weight: bold;
background-image: url(http://121.36.17.219:8089/images/background2.png);
background-size: cover;
background-position: center;
justify-content: center;
align-items: center;
}
.subTitle {
font-size: 2.3em;
font-weight: bolder;
text-align: center;
letter-spacing: 10px;
margin-bottom: 20px
}
.cardContainer {
display: grid;
columns: 3;
}
.cardTitle {
font-size: 1.3em;
font-weight: bold;
margin: 15px 0 0 0;
}
.cardDiscription {
font-size: 0.9em;
margin: 5px 0 0 0;
}
.cardImage {
background-image: url(http://121.36.17.219:8089/images/background1.png);
background-size: cover;
background-position: center;
width: 100%;
height: 200px
}
</style>
1. 搭Vue框架并引入element-plus 新建项目
$ npm init vue@latest
Need to install the following packages:
create-vue@3.10.3
Ok to proceed? (y) y
Vue.js - The Progressive JavaScript Framework
√ 请输入项目名称: ... xgd-front
√ 是否使用 TypeScript 语法? ... 否 / 是
√ 是否启用 JSX 支持? ... 否 / 是
√ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是
√ 是否引入 Pinia 用于状态管理? ... 否 / 是
√ 是否引入 Vitest 用于单元测试? ... 否 / 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? ... 否 / 是
√ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) ... 否 / 是
正在初始化项目 D:\code\project\Apr_Web_XGD_Front\xgd-front...
项目初始化完成,可执行以下命令:
cd xgd-front
npm install
npm run dev
运行 npm install npm run dev
之后,可以看到如下界面:
$ npm run dev
> xgd-front@0.0.0 dev
> vite
VITE v5.2.10 ready in 1331 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
然后进入 element-ui 官网(https://element-plus.org/zh-CN/guide/installation.html)按装相关包
npm install element-plus --save
// main.js
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// element-plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(router)
app.mount('#app')
// element-plus
app.use(ElementPlus)
2. 单页面开发 - vue文件组成
如果使用element-plus进行开发,建议查阅element-plus官方文档的页面布局部分(由于版本更新较块,此处仅供参考)
// HTML 模板部分
<template>
<div class="top">
<el-container>
<!-- el-container 部分 参照官方文档-->
<el-header>
<!-- HEADER 部分 -->
<Header />
</el-header>
<el-main>
<!-- main 部分 -->
</el-main>
<el-footer>
<!-- FOOTER 部分 -->
<Footer />
</el-footer>
</el-container>
</div>
</template>
// JS 部分
<script setup>
import Header from "../components/Header.vue"
import Footer from "../components/Footer.vue"
import { useRouter } from 'vue-router'
</script>
// CSS
<style scoped>
</style>
3. 基本CSS布局
一般在<template>
部分使用 class
属性,如:
<div class="bannerText">大学物理虚拟实验平台</div>
然后再用 CTRL+F 定位进行切换修改
.bannerText {
display: flex;
height: 3em;
width: 100%;
color: #000;
font-size: 3.2em;
font-weight: bold;
background-image: url(http://121.36.17.219:8089/images/background2.png);
background-size: cover;
background-position: center;
justify-content: center;
align-items: center;
}
几个常用的CSS属性(具体可查阅CSS文档):
margin 边框
margin-bottom: 20pxmargin: 15px 0 0 0;
height width
flex 布局 如
.content { display: flex; flex-direction: column; }
font-size 字体大小
.top {
width: 100%;
}
.subTitle {
font-size: 2.3em;
font-weight: bolder;
text-align: center;
letter-spacing: 10px;
margin-bottom: 20px
}
.cardContainer {
display: grid;
columns: 3;
}
.cardTitle {
font-size: 1.3em;
font-weight: bold;
margin: 15px 0 0 0;
}
.cardDiscription {
font-size: 0.9em;
margin: 5px 0 0 0;
}
.cardImage {
background-image: url(http://121.36.17.219:8089/images/background1.png);
background-size: cover;
background-position: center;
width: 100%;
height: 200px
}
4. 多页面开发 - 路由
我们现在在 Home.vue
页面内 点击某个组件 跳转到 Info.vue
页面。
main.js
部分
import Home from './pages/Home.vue'
import Info from './pages/Info.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/detail', component: Info },
]
const router = createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: createWebHashHistory(),
routes
})
Home.vue
JS部分代码
import { useRouter } from 'vue-router'
const router = useRouter()
const toInfo = () => {
const route = router.resolve("/detail")
window.open(route.href)
}
Home.vue
template部分代码,注意 @click="toInfo"
<el-card style="width: 30%" @click="toInfo">
<div style="cursor: pointer">
<div class="cardImage"></div>
<div class="cardTitle">光学 - 等厚干涉实验</div>
<div class="cardDiscription">使用牛顿环与劈尖进行等厚干涉实验。</div>
</div>
</el-card>
点击这个组件即可跳转页面
5. HTML 与 JS 变量绑定
Vue中,最常见的有两种方式
第一种,将JS里的变量用 {{ }}
包含,使用了 ref
,多用于展示从后端返回的数据
import { ref } from 'vue'
const expData = ref({ newtonStripeAndSquareDiffDetailVoList: [], wedgeStripeDataVoList: [] })
<el-col :span="4" v-for="i in [0, 1, 2, 3, 4]">
<div class="cellContent">
{{ expData.wedgeStripeDataVoList[i].value }}
</div>
</el-col>
第二种,使用 v-model
进行数据绑定,多用于向后端发请求的数据
import { ref } from 'vue'
let stripeNewtonM = ref([20, 19, 18, 17, 16])
<el-col :span="4" v-for="i in [0, 1, 2, 3, 4]">
<el-input-number v-model="stripeNewtonM[i]" :controls="false" class="cellContent" />
</el-col>
6. axios发请求
axios 主要是 JS 的部分,template部分只需要 v-model
或前述方式绑定即可
这里举出两个例子:
第一个例子,在加载页面时即显示
响应示例:
{
"code": 0,
"data": {
"experimentId": 0,
"newtonStripeAndSquareDiffDetailVoList": [
{
"delta": 0,
"diameterM": 0,
"diameterN": 0,
"leftLocationM": 0,
"leftLocationN": 0,
"rightLocationM": 0,
"rightLocationN": 0,
"ringLevelM": 0,
"ringLevelN": 0,
"squareDiff": 0
}
],
"wedgeStripeDataVoList": [
{
"stripe_level": 0,
"value": 0
}
]
},
"msg": ""
}
Info.vue
JS代码示例:
import { onMounted, ref } from 'vue'
import axios from "axios"
const expData = ref({ newtonStripeAndSquareDiffDetailVoList: [], wedgeStripeDataVoList: [] })
onMounted(() => {
axios({
method: 'GET',
url: 'http://121.36.17.219:8080/api/basic/eti/getDataDetail?experimentId=0',
}).then((res) => {
expData.value = res.data.data
console.log(expData.value.newtonStripeAndSquareDiffDetailVoList[0].ringLevelM)
})
})
第二种,需要按一个按钮之后再向后端发送POST请求
import axios from 'axios';
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const submitExp = () => {
// 判定类型是否为undefined
if ((stripeNewtonM.value.findIndex((value) => value === undefined) !== -1) ||
(stripeNewtonN.value.findIndex((value) => value === undefined) !== -1) ||
(leftLocationM.value.findIndex((value) => value === undefined) !== -1) ||
(leftLocationN.value.findIndex((value) => value === undefined) !== -1) ||
(rightLocationM.value.findIndex((value) => value === undefined) !== -1) ||
(rightLocationN.value.findIndex((value) => value === undefined) !== -1) ||
(diameterM.value.findIndex((value) => value === undefined) !== -1) ||
(diameterN.value.findIndex((value) => value === undefined) !== -1) ||
(delta.value.findIndex((value) => value === undefined) !== -1) ||
(squareDiff.value.findIndex((value) => value === undefined) !== -1) ||
(stripeWedgeM.value.findIndex((value) => value === undefined) !== -1) ||
(stripeWedgeN.value.findIndex((value) => value === undefined) !== -1) ||
(valueM.value.findIndex((value) => value === undefined) !== -1) ||
(valueN.value.findIndex((value) => value === undefined) !== -1)) {
ElMessage({
message: "请先完成表格再提交",
type: 'warning'
})
} else {
// 构造发请求的数据
let newtonList = []
let wedgeList = []
for (let i in [0, 1, 2, 3, 4]) {
// 填入请求体数据
newtonList.push({
delta: delta.value[i],
diameterM: diameterM.value[i],
diameterN: diameterN.value[i],
leftLocationM: leftLocationM.value[i],
leftLocationN: leftLocationN.value[i],
newtonId: 25,
rightLocationM: rightLocationM.value[i],
rightLocationN: rightLocationN.value[i],
ringLevelM: stripeNewtonM.value[i],
ringLevelN: stripeNewtonN.value[i],
squareDiff: squareDiff.value[i]
})
wedgeList.push({
stripe_level: stripeWedgeM.value[i],
value: valueM.value[i]
}, {
stripe_level: stripeWedgeN.value[i],
value: valueN.value[i]
})
}
axios({
method: 'POST',
url: 'http://121.36.17.219:8080/api/basic/eti/addExperimentData',
// 请求体内容
data: {
experimentId: 1,
newtonStripeAndSquareDiffDetailDtoList: newtonList,
wedgeStripeDetailDtoList: wedgeList
}
}).then((res) => {
// 根据相应内容,做接下来的事情
if (res.data.code === 200) {
// ......
}
})
}
}
7. 打包部署
控制台内输入 npm run build
后, dist 文件夹内就是打包好之后的内容,将其直接放入服务器渲染模板/静态资源部分即可。
dist 文件夹内有三个部分:
index.html
:渲染模板入口处assets
:所有 Vue 的css
与js
文件其它: 原来的
public
目录下文件,充当静态资源
8. 如何和前端进行沟通
画原型图,对其做出描述,是最快最便携、前端最好理解的方式。