前端完成所有页面

FRONT_a
helloworld180 1 month ago
parent 04a8a968e0
commit d298b5856b

@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib": ["ES2015", "DOM"],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }

@ -0,0 +1,139 @@
.lucky-popup {
/* background-image: url('../assets/image/bgcImg.png'); */
width: 100%;
height: 100%;
/* position: relative; */
/* position: fixed; */
/* background-size: 100% 100%; */
}
/* header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 60px;
padding-bottom: 0;
font-size: 50px;
font-weight: bold;
color: #626868;
}
.icon {
width: 45px;
cursor: pointer;
}
.icon:hover {
transform: scale(1.3);
}
/* 姓名学号 */
.lucky-info {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 200px;
}
.lucky-icon {
width: 780px;
position: relative;
}
.student-id {
position: absolute;
top: 35%;
font-size: 75px;
font-weight: bold;
color: black;
}
.student-name {
position: absolute;
top: 50%;
font-size: 75px;
font-weight: bold;
color: black;
}
.student-msg {
position: absolute;
left: 5%;
right: 5%;
top: 65%;
font-size: 35px;
text-align: center;
font-weight: bold;
color: #333;
}
/* 分数按钮 */
.score-buttons {
display: flex;
justify-content: space-between;
margin: 0 5%;
}
.score-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 18px 115px;
border: none;
border-radius: 5px;
background-color: #A182FF;
color: white;
cursor: pointer;
font-size: 30px;
transition: background-color 0.3s;
}
.score-btn.active, .score-btn:hover {
background-color: #6231F5;
}
.score-btn input {
width: 50px;
height: 28px;
padding: 5px;
border-radius: 5px;
margin-right: 8px;
font-size: 28px;
border: none;
background-color: #EBE4FF;
color: #333;
text-align: center;
}
/* 底部 */
.bottom{
position: absolute;
display: flex; align-items: center;justify-content: space-around;
bottom: 0;
width: 100%;
padding: 20px 0;
background-color: #EBE4FF;
}
.cancel {
width: auto;
border-radius: 5px;
padding: 10px 25px;
font-size: 23px;
background-color: #fff;
color: #A182FF;
cursor: pointer;
}
.cancel:hover {
background-color: #dfdede;
}
.confirm{
width: auto;
border-radius: 5px;
padding: 10px 25px;
font-size: 23px;
background-color: #A182FF;
color: #fff;
cursor: pointer;
}
.confirm:hover {
background-color: #6231F5;
}

@ -0,0 +1,142 @@
.container {
position: relative;
background-color: #fff;
border-radius: 40px;
height: 100%;
padding-top: 10px;
box-shadow: 5px 10px 10px 5px rgba(2, 2, 2, 0.1);
overflow: hidden;
}
/* 头部 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 60px;
font-size: 35px;
font-weight: bold;
}
.icon {
width: 45px;
cursor: pointer;
}
.icon:hover {
transform: scale(1.3);
}
/* 搜索框 */
.search-bar {
padding: 20px 40px;
display: flex;
gap: 30px;
}
.search-input {
flex-grow: 1;
padding: 10px;
border: 2px solid #ddd;
border-radius: 10px;
font-size: 20px;
}
.search-btn {
background-color: #A182FF;
color: #fff;
border: none;
padding: 10px 30px;
border-radius: 50px;
cursor: pointer;
font-size: 30px;
}
.search-btn:hover{
background-color: #6231F5;
}
/* 表格滚动容器样式 */
.table-scroll-container {
max-height: 480px; /* 设置一个合适的高度 */
overflow-y: auto; /* 当内容超出时显示滚动条 */
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
text-align: center;
font-size: 20px;
padding: 12px 40px;
border-bottom: 1.5px solid #ddd;
border-right: 1.5px solid #ddd;
/* 防止换行 */
/* white-space: nowrap; */
&:last-child{
border-right: none;
}
}
th {
background-color: #EBE4FF;
font-weight: normal;
color: black;
font-weight: bold;
}
.medal {
font-size: 20px;
}
/* 分页 */
.pagination {
padding: 30px 20px 15px 0px;
text-align: right;
color: #797878;
font-weight: bold;
font-size: 18px;
}
.page-btn {
background-color: #b39cf2;
color: #fff;
border: none;
padding: 8px 15px;
margin: 0 8px;
font-size: 18px;
border-radius: 3px;
cursor: pointer;
}
.page-btn:hover {
background-color: #9272f3;
}
/* 底部 */
.bottom{
position: absolute;
display: flex; align-items: center; justify-content: right;
bottom: 0;
width: 100%;
padding: 30px 30px;
background-color: #EBE4FF;
}
.cancel {
width: auto;
border-radius: 5px;
padding: 20px 35px;
margin-right: 70px;
font-size: 28px;
background-color: #fff;
color: #A182FF;
cursor: pointer;
}
.cancel:hover {
background-color: #dfdede;
}
.confirm{
width: auto;
border-radius: 5px;
padding: 20px 35px;
margin-right: 30px;
font-size: 28px;
background-color: #A182FF;
color: #fff;
cursor: pointer;
}
.confirm:hover {
background-color: #6231F5;
}

@ -0,0 +1,10 @@
<svg width="828" height="480" viewBox="0 0 828 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47 428.984L124.026 432.577" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
<path d="M54.9106 250.873L124.503 295.693" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
<path d="M140.931 103.239L182.5 191.097" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
<path d="M781 428.984L703.974 432.577" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
<path d="M773.089 250.873L703.497 295.693" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
<path d="M687.069 103.239L645.479 191.097" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
<path d="M492.836 52.5361L467.76 151.2" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
<path d="M337.57 47L349.859 150.028" stroke="#F9BA41" stroke-width="93.2383" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

@ -5,6 +5,9 @@ import login from '@/views/login2.vue'
import home from '@/views/home.vue' import home from '@/views/home.vue'
import importFile from '@/views/importFile.vue' import importFile from '@/views/importFile.vue'
import ruleSetting from '@/views/ruleSetting.vue' import ruleSetting from '@/views/ruleSetting.vue'
import seeChart from '@/views/seeChart.vue'
import beginCall from '@/views/beginCall.vue'
import result from '@/views/result.vue'
// 创建路由器 // 创建路由器
const router = createRouter({ const router = createRouter({
@ -28,8 +31,20 @@ const router = createRouter({
{ {
path:'ruleSetting', path:'ruleSetting',
component: ruleSetting component: ruleSetting
},
{
path:'seeChart',
component:seeChart
} }
] ]
},
{
path:'/beginCall',
component:beginCall
},
{
path:'/result',
component:result
} }
] ]

@ -4,7 +4,7 @@ import {getToken} from '@/token/auth' // 注意这里使用了解构赋值来导
// 创建axios实例 // 创建axios实例
const service = axios.create({ const service = axios.create({
baseURL: 'http://example.com/api', // 配置基础URL baseURL: 'http://localhost:8080/api', // 配置基础URL
timeout: 5000, // 请求超时时间 timeout: 5000, // 请求超时时间
}); });
@ -29,6 +29,7 @@ service.interceptors.request.use(
service.interceptors.response.use( service.interceptors.response.use(
response => { response => {
// 对响应数据做点什么 // 对响应数据做点什么
// 注意这里返回已经包含data
const res = response.data; const res = response.data;
// 你可以根据实际情况在这里添加一些通用的响应处理逻辑 // 你可以根据实际情况在这里添加一些通用的响应处理逻辑
// 例如,根据返回的状态码判断请求是否成功 // 例如,根据返回的状态码判断请求是否成功

@ -0,0 +1,181 @@
<template>
<div class="beginCall">
<img src="../assets/image/coffee.png" alt="Coffee Image">
<div class="text-container">
<div
v-for="(text, index) in texts"
:key="index"
class="text"
:style="text.style"
@animationend="removeText(index)"
>
{{ text.content }}
</div>
</div>
<div class="btnBack" @click="back"></div>
<div class="btnStop" @click="stop"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
//
maxTexts: 80,
names: [],
texts: [],
};
},
mounted() {
// this.fetchNames();
this.names = ['文字1', '文字2', '文字3', '文字4', '文字5'];
this.startRain();
},
methods: {
// names
async fetchNames() {
try {
const response = await axios.get('/students/export-students');
this.names = response.data;
} catch (error) {
console.error('获取names失败:', error);
//
this.names = ['令狐', '令狐新锐', '令狐冲', '文字4', '文字5'];
}
},
//
getTextStyle() {
const left = Math.floor(Math.random() * 80) + 5; // 10%90%
const size = Math.random() * 1.7 + 0.5; // 0.5em1.5em
const duration = Math.random() * 2 + 5; // 2-7
return {
left: `${left}%`,
fontSize: `${size}em`,
animationDuration: `${duration}s`,
};
},
//
startRain() {
// 200
setInterval(() => {
if (this.texts.length < this.maxTexts) {
// names
const content = this.names[Math.floor(Math.random() * this.names.length)];
// getTextStyle()
const style = this.getTextStyle();
// texts
this.texts.push({ content, style });
}
}, 100); // 1s
},
//
removeText(index) {
this.texts.splice(index, 1);
},
//
stop() {
this.$router.push('/result')
},
//
back() {
this.$router.push('/home')
}
},
};
</script>
<style scoped>
.beginCall {
background-image: url('../assets/image/bgcImg.png');
width: 100%;
height: 100%;
position: fixed;
background-size: 100% 100%;
}
.beginCall img {
position: absolute;
top: 0;
left: 50%;
width: 30%;
height: auto;
}
.text-container {
position: absolute;
top: 35%;
left: 15%;
width: 70%;
height: 70%;
overflow: hidden;
}
.text {
position: absolute;
animation: rain 8s linear infinite;
opacity: 0; /* 初始状态设为透明 */
}
@keyframes rain {
0%, 100% {
transform: translateY(-10%) scale(1);
opacity: 0;
color: #f13f69;
}
10% {
/* transform: translateY(0) scale(1); */
opacity: 1;
color: #f13f69;
}
50% {
color: #e34166;
}
80% {
transform: translateY(60vh) scale(1.3);
opacity: 1;
color: #ccb2f9; /* 中间颜色 */
}
100% {
transform: translateY(50vh) scale(1.3);
opacity: 0;
color: #F5B2C2;
}
}
.btnStop {
position: absolute;
bottom: 4%;
right: 3%;
padding: 10px 100px;
border-radius: 50px;
font-size: 35px;
font-weight: bold;
color: #fff;
background-color: #A182FF;
cursor: pointer;
}
.btnStop:hover, .btnBack:hover {
background-color: #6231F5;
}
.btnBack {
position: absolute;
bottom: 4%;
left: 3%;
padding: 10px 100px;
border-radius: 50px;
font-size: 35px;
font-weight: bold;
color: #fff;
background-color: #A182FF;
cursor: pointer;
}
</style>

@ -10,13 +10,13 @@
<div @click="gotoImpt" class="selectItem"> <div @click="gotoImpt" class="selectItem">
导入文件 导入文件
</div> </div>
<div class="selectItem"> <div @click="gotoCall" class="selectItem">
开始点名 开始点名
</div> </div>
<div @click="gotoRule" class="selectItem"> <div @click="gotoRule" class="selectItem">
规则设置 规则设置
</div> </div>
<div class="selectItem"> <div @click="gotoChart" class="selectItem">
查看排行 查看排行
</div> </div>
</div> </div>
@ -38,6 +38,12 @@ export default {
}, },
gotoRule() { gotoRule() {
this.$router.push('/home/ruleSetting') this.$router.push('/home/ruleSetting')
},
gotoChart() {
this.$router.push('/home/seeChart')
},
gotoCall() {
this.$router.push('/beginCall')
} }
} }
} }

@ -11,11 +11,10 @@
<el-upload <el-upload
ref="uploadRef" ref="uploadRef"
class="upload-demo" class="upload-demo"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:auto-upload="false"
:limit="1" :limit="1"
accept=".xlsx, .xls" accept=".xlsx, .xls"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:http-request="customUpload"
> >
<div style="font-size: 30px; color: #7D7878;">选择文件</div> <div style="font-size: 30px; color: #7D7878;">选择文件</div>
<input placeholder="请选择上传的文件" class="inputFile" /> <input placeholder="请选择上传的文件" class="inputFile" />
@ -38,7 +37,6 @@
<!-- 下载模板 --> <!-- 下载模板 -->
<a <a
class="download" class="download"
href="后端提供的下载URL"
download="student-template.xlsx" download="student-template.xlsx"
@click="onDownload" @click="onDownload"
> >
@ -59,17 +57,18 @@
<div class="confirm" @click="submitUpload"></div> <div class="confirm" @click="submitUpload"></div>
<div class="cancel" @click="close"></div> <div class="cancel" @click="close"></div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import { genFileId } from 'element-plus' import { genFileId, ElMessage } from 'element-plus'
import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus' import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'
import router from '@/router'; import router from '@/router';
import axios from 'axios';
const uploadRef = ref<UploadInstance>() const uploadRef = ref<UploadInstance>()
// //
@ -79,21 +78,70 @@ const handleExceed: UploadProps['onExceed'] = (files) => {
file.uid = genFileId() file.uid = genFileId()
uploadRef.value!.handleStart(file) uploadRef.value!.handleStart(file)
} }
//
const customUpload = (options) => {
const { file } = options;
const formData = new FormData();
formData.append('file', file);
axios.post('http://localhost:8080/api/students/import', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
ElMessage({
type: 'success',
message: '文件导入成功!'
});
console.log('导入结果:', response.data);
})
.catch(error => {
ElMessage({
type: 'error',
message: '文件导入失败,请重试!'
});
console.error('导入失败:', error);
});
}
// //
const submitUpload = () => { const submitUpload = () => {
uploadRef.value!.submit() uploadRef.value!.submit()
} }
//
async function getTemplateDownloadLink(): Promise<string> {
try {
const response = await axios.get('/students/download-template');
return response.data; //
} catch (error) {
console.error('获取下载链接失败:', error);
throw error; //
}
}
// //
const onDownload = (event: MouseEvent) => { const onDownload = async (event: MouseEvent) => {
// event.preventDefault(); // <a>
//
try {
const downloadLink = await getTemplateDownloadLink(); //
if (downloadLink) {
// 使window.open<a>href
window.open(downloadLink, '_blank');
} else {
//
alert('无法获取下载链接,请稍后再试。');
}
} catch (error) {
//
console.error('下载模板时出错:', error);
alert('下载模板失败,请稍后再试。');
}
};
// <a>href
event.preventDefault();
// 使fetchhref
window.open("后端提供的下载URL", '_blank');
}
function close() { function close() {
router.push('/home') router.push('/home')
} }

@ -147,7 +147,9 @@
username: this.loginForm.phone, username: this.loginForm.phone,
password: this.loginForm.password password: this.loginForm.password
}); });
console.log('登录成功', response)
// tokendata // tokendata
// !!!
const token = response.data.token; const token = response.data.token;
// token // token
setToken(token); setToken(token);
@ -156,7 +158,7 @@
this.$router.push('/home'); this.$router.push('/home');
} catch (error) { } catch (error) {
// //
console.error('Login Error:', error); console.error('登录 Error:', error);
} }
}, },
// //

@ -0,0 +1,161 @@
<template>
<div class="lucky-popup">
<!-- header -->
<div class="header">
<div>幸运儿出现了 </div>
<img src="../assets/image/close.png" alt="" class="icon" @click="close">
</div>
<div class="lucky-info">
<img src="../assets/image/group.svg" alt="" class="lucky-icon">
<div class="student-id">{{ studentId }}</div>
<div class="student-name">{{ studentName }}</div>
<div class="student-msg" v-if="nameOrQuestion === '提问'">{{ message }}</div>
</div>
<!-- 选择分数 -->
<div class="score-buttons" v-if="nameOrQuestion === '提问'">
<button
v-for="(score, index) in qsScores"
:key="index"
:class="['score-btn', { active: selectedScore === score.value }]"
@click="selectScore(score.value)"
>
{{ score.label }}
<input
v-if="index === 2"
type="number"
v-model="customScore"
@input="selectCustomScore"
><div v-if="index === 2"></div>
</button>
</div>
<!-- 点名的选项 -->
<div class="score-buttons" v-else-if="nameOrQuestion === ''">
<button
v-for="(score, index) in dmScores"
:key="index"
:class="['score-btn', { active: selectedScore === score.value }]"
@click="selectScore(score.value)"
>
{{ score.label }}
</button>
</div>
<!-- 底部 -->
<div class="bottom">
<div class="confirm" @click="confirmAdjustment"></div>
<div class="cancel" @click="close"></div>
</div>
</div>
</template>
<script>
import axios from '@/utils/axiosConfig';
import router from '@/router';
export default {
data() {
return {
studentId: '102201338',
studentName: '令狐新锐',
message: '赌徒事件--学生可以在回答问题前下注一定数量的积分,如果回答正确,则按赌注倍数获得积分;如果错误,则失去赌注积分',
selectedScore: null,
customScore: '',
//
nameOrQuestion:'',
triggerRandomEvent:'',
enableFateWheel:'',
qsScores: [
{ label: '+0.5分', value: 0.5 },
{ label: '-1分', value: -1 },
{ label: '', value: null }
],
dmScores: [
{ label: '已到达课堂加1分', value: 1 },
{ label: '未到达课堂减1分', value: -1 },
],
};
},
mounted() {
this.loadStudentData();
},
methods: {
async loadStudentData () {
this.nameOrQuestion = localStorage.getItem('nameOrQuestion');
console.log('nameOrQuestion的值为' + this.nameOrQuestion)
this.triggerRandomEvent = localStorage.getItem('triggerRandomEvent');
console.log('triggerRandomEvent的值为' + this.triggerRandomEvent)
this.enableFateWheel = localStorage.getItem('enableFateWheel');
console.log('enableFateWheel的值为' + this.enableFateWheel)
const data = {
rollCallMode: this.nameOrQuestion,
triggerRandomEvent: this.triggerRandomEvent,
wheelOfFortune: this.enableFateWheel,
}
//
try {
const response = await axios.post('/rollcall/start', data);
console.log('后端响应:', response.data);
this.studentId = response.studentId;
this.studentName = response.name;
this.message = response.message;
} catch (error) {
console.error('发送请求时出错:', error);
}
},
//
selectScore(value) {
this.selectedScore = value;
if (value !== null) {
this.customScore = '';
}
},
selectCustomScore() {
if (this.customScore !== '') {
this.selectedScore = parseFloat(this.customScore);
} else {
this.selectedScore = null;
}
},
//
async confirmAdjustment() {
let pointsDelta = this.selectedScore;
if (this.customScore !== '') {
pointsDelta = parseFloat(this.customScore);
}
if (pointsDelta === null || isNaN(pointsDelta)) {
alert('请选择或输入有效的调整分数');
return;
}
try {
// const response = await axios.post('/students/${this.studentId}/adjustPoints', pointsDelta)
await axios.put(`http://localhost:8080/api/students/${this.studentId}/adjustPoints`, {
pointsDelta: pointsDelta
});
alert('积分调整成功');
this.close();
} catch (error) {
console.error('积分调整失败:', error);
alert('积分调整失败,请重试');
}
},
close() {
this.$router.push('/home')
}
}
};
</script>
<style scoped>
@import '../assets/css/result.css'
</style>

@ -16,7 +16,7 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="是否触发随机事件 "> <el-form-item label="是否触发随机事件 " v-if="form.nameOrQuestion === '提问'">
<el-radio-group v-model="form.triggerRandomEvent"> <el-radio-group v-model="form.triggerRandomEvent">
<el-radio label="触发">触发</el-radio> <el-radio label="触发">触发</el-radio>
<el-radio label="不触发">不触发</el-radio> <el-radio label="不触发">不触发</el-radio>
@ -54,24 +54,55 @@
import router from '@/router'; import router from '@/router';
import { ref } from 'vue' import { ref } from 'vue'
import { ElTooltip } from 'element-plus' import { ElTooltip } from 'element-plus'
import axios from 'axios';
const form = ref({ const form = ref({
nameOrQuestion: '点名', nameOrQuestion: '点名',
triggerRandomEvent: '触发', triggerRandomEvent: '触发',
enableFateWheel: '否' enableFateWheel: '否'
}) })
const confirmSetting = () => {
// ,
const data = { const confirmSetting = async () => {
nameOrQuestion: form.value.nameOrQuestion,
triggerRandomEvent: form.value.triggerRandomEvent, if(form.value.nameOrQuestion === '点名') {
enableFateWheel: form.value.enableFateWheel, form.value.triggerRandomEvent = '不触发'
} }
// form // localStorage
console.log(form.value.nameOrQuestion) localStorage.setItem('nameOrQuestion', form.value.nameOrQuestion);
// API localStorage.setItem('triggerRandomEvent', form.value.triggerRandomEvent);
console.log('发送到后端的数据:', data) localStorage.setItem('enableFateWheel', form.value.enableFateWheel);
// const nameOrQuestion1 = localStorage.getItem('nameOrQuestion');
// console.log('nameOrQuestion' + nameOrQuestion1)
// const triggerRandomEvent1 = localStorage.getItem('triggerRandomEvent');
// console.log('triggerRandomEvent' + triggerRandomEvent1)
// const enableFateWheel1 = localStorage.getItem('enableFateWheel');
// console.log('enableFateWheel' + enableFateWheel1)
router.push('/home')
// ,
// const data = {
// isRollCall: form.value.nameOrQuestion,
// triggerRandomEvent: form.value.triggerRandomEvent,
// wheelOfFortune: form.value.enableFateWheel,
// }
// console.log(form.value.nameOrQuestion)
// try {
// const response = await axios.post('/rollcall/start', data);
// console.log(':', response.data);
// } catch (error) {
// console.error(':', error);
// }
// console.log(':', data)
} }
function close() { function close() {
router.push('/home') router.push('/home')
} }

@ -0,0 +1,172 @@
<template>
<div class="container">
<!-- 头部 -->
<div class="header">
<div>查看排行</div>
<img src="../assets/image/close.png" alt="" class="icon" @click="close">
</div>
<div style="border: 1.5px solid #C8C1C1;"></div>
<!-- 搜索框 -->
<div class="search-bar">
<input
v-model="searchInput"
type="text"
class="search-input"
placeholder="输入需要查询积分同学的学号"
/>
<button class="search-btn" @click="searchStudent"></button>
</div>
<!-- 表格滚动容器 -->
<div class="table-scroll-container">
<!-- 排行榜 -->
<table>
<thead>
<tr>
<th>名次</th>
<th>学号</th>
<th>姓名</th>
<th>积分</th>
</tr>
</thead>
<tbody>
<tr v-for="(student, index) in filteredStudents" :key="student.studentNumber">
<td v-if="student.id === 1"><span class="medal">🥇</span></td>
<td v-else-if="student.id === 2"><span class="medal">🥈</span></td>
<td v-else-if="student.id === 3"><span class="medal">🥉</span></td>
<td v-else class="center-align">{{ student.id }}</td>
<td>{{ student.studentNumber }}</td>
<td>{{ student.name }}</td>
<td>{{ student.points }}</td>
</tr>
</tbody>
</table>
<!-- 分页 -->
<div class="pagination">
{{ totalPages }}当前是第{{ currentPage }}
<button class="page-btn" @click="prevPage" :disabled="currentPage === 1">上一页</button>
<button class="page-btn" @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<div style="display: flex;">
<div class="confirm" @click="exportData"></div>
<div class="cancel" @click="close"></div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import router from '@/router';
import axios from '@/utils/axiosConfig';
//
// const students = ref([
// { id: 1, studentNumber: '102201403', name: '', points: 100 },
// { id: 2, studentNumber: '102201338', name: '', points: 99 },
// { id: 3, studentNumber: '102201111', name: '', points: 95 },
// { id: 4, studentNumber: '102201112', name: '', points: 90 },
// { id: 5, studentNumber: '102201113', name: '', points: 89 },
// { id: 6, studentNumber: '102201113', name: '', points: 89 },
// { id: 7, studentNumber: '102201113', name: '', points: 89 },
// { id: 8, studentNumber: '102201113', name: '', points: 89 },
// ])
const students = ref([])
//
// ref
const searchInput = ref('')
const currentPage = ref(1)
const itemsPerPage = ref(6)
const totalPages = ref(Math.ceil(students.value.length / itemsPerPage.value))
//
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++
}
}
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--
}
}
//
const searchStudent = () => {
currentPage.value = 1 //
}
//
const filteredStudents = computed(() => {
let filtered = students.value.filter(student =>
student.studentNumber.includes(searchInput.value)
)
let start = (currentPage.value - 1) * itemsPerPage.value
let end = start + itemsPerPage.value
return filtered.slice(start, end)
})
//
const fetchidData = async () => {
try {
const response = await axios.get('/students/id') // API
students.value = response.data // students
} catch (error) {
console.error('Failed to fetch id data:', error)
//
}
}
//
onMounted(() => {
fetchidData()
})
// Blob
const exportData = async () => {
try {
const response = await axios.get('/students/export-students', {
responseType: 'blob', // blob
});
// URL Blob
const url = window.URL.createObjectURL(new Blob([response.data]));
// a
const a = document.createElement('a');
a.href = url;
a.download = 'filename'; //
document.body.appendChild(a);
a.click();
// URL
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Failed to export data:', error);
//
}
};
//
function close() {
router.push('/home')
}
</script>
<style scoped>
@import '../assets/css/seeChart.css'
</style>
Loading…
Cancel
Save