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文档):

  1. margin 边框 margin-bottom: 20pxmargin: 15px 0 0 0;

  2. height width

  3. flex 布局 如 .content { display: flex; flex-direction: column; }

  4. 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 的 cssjs 文件

  • 其它: 原来的 public 目录下文件,充当静态资源

8. 如何和前端进行沟通

画原型图,对其做出描述,是最快最便携、前端最好理解的方式。

详情见:http://zxlmdonnie.cn/archives/1714294043838