1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-07-03 01:39:22 +00:00
This commit is contained in:
SunWuyuan 2025-02-23 14:16:51 +08:00
parent 2004acc573
commit dfdb662e11
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
3 changed files with 263 additions and 120 deletions

View File

@ -1,101 +0,0 @@
<template>
<v-card border>
<v-card>
<v-card-title>选择服务器</v-card-title>
<v-card-subtitle>没事别乱动</v-card-subtitle>
<v-card-text>
<v-text-field v-model="serverUrl" label="后端地址" required />
<v-btn type="submit" color="primary" @click="saveServerUrl"> 保存 </v-btn>
</v-card-text>
</v-card>
<v-card>
<v-card-title>设置学生列表</v-card-title>
<v-card-subtitle>没事别乱动</v-card-subtitle>
<v-card-text>
<v-textarea v-model="students" label="学生列表" required />
<v-btn type="submit" color="primary" @click="saveStudents" border>
保存
</v-btn>
</v-card-text>
</v-card></v-card
>
<v-snackbar v-model="snackbar">
{{ snackbarText }}
</v-snackbar>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
serverUrl: "",
snackbar: false,
snackbarText: "",
students: "",
studentsList: [],
};
},
mounted() {
this.loadServerUrl();
this.loadStudents();
},
methods: {
saveServerUrl() {
try {
// URL/http://使new URL()
if (this.serverUrl == "") {
localStorage.removeItem("backendServerUrl");
this.snackbarText = "删除成功,请刷新页面。";
this.snackbar = true;
return;
}
this.serverUrl =
new URL(this.serverUrl).protocol +
"//" +
new URL(this.serverUrl).host;
localStorage.setItem("backendServerUrl", this.serverUrl);
this.snackbarText = "保存成功,请刷新页面。";
this.snackbar = true;
} catch (error) {
console.log(error);
this.snackbarText = "保存失败,请检查后端地址。";
this.snackbar = true;
}
},
saveStudents() {
try {
this.studentsList = this.students.split("\n");
axios
.post(this.serverUrl + "/setstudentlist", {
studentList: this.studentsList,
id: 1,
})
.then((response) => {
console.log(response);
});
localStorage.setItem("studentList", this.students);
this.snackbarText = "保存成功,请刷新页面。";
this.snackbar = true;
} catch (error) {
console.log(error);
this.snackbarText = "保存失败,请检查学生列表。";
this.snackbar = true;
}
},
loadServerUrl() {
const savedUrl = localStorage.getItem("backendServerUrl");
if (savedUrl) {
this.serverUrl = savedUrl;
}
},
loadStudents() {
if (localStorage.getItem("studentList")) {
this.students = localStorage.getItem("studentList").replace(/,/g, "\n");
}
},
},
};
</script>

View File

@ -5,16 +5,29 @@
</template> </template>
<v-app-bar-title> <v-app-bar-title>
<strong>{{ dateString }}</strong> 作业 {{ classNumber }} - {{ titleText }}
</v-app-bar-title> </v-app-bar-title>
<v-spacer /> <v-spacer />
<v-btn <template #append>
icon="mdi-cog" <v-btn
variant="text" icon="mdi-calendar"
@click="ServerSelectionDialog = true" variant="text"
/> @click="datePickerDialog = true"
/>
<v-btn
icon="mdi-refresh"
variant="text"
:loading="downloadLoading"
@click="downloadData"
/>
<v-btn
icon="mdi-cog"
variant="text"
@click="$router.push('/settings')"
/>
</template>
</v-app-bar> </v-app-bar>
<v-container <v-container
class="main-window" class="main-window"
@ -200,19 +213,32 @@
> >
{{ snackbarText }} {{ snackbarText }}
</v-snackbar> </v-snackbar>
<v-dialog v-model="datePickerDialog" width="auto">
<v-card>
<v-card-title>选择日期</v-card-title>
<v-card-text>
<v-date-picker v-model="selectedDate" />
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="primary" @click="goToDate">确定</v-btn>
<v-btn @click="datePickerDialog = false">取消</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template> </template>
<script> <script>
import axios from "axios"; import axios from "axios";
import { useDisplay } from "vuetify"; import { useDisplay } from "vuetify";
import ServerSelection from "../components/ServerSelection.vue";
export default { export default {
name: "HomeworkBoard", name: "HomeworkBoard",
components: { ServerSelection },
data() { data() {
return { return {
backurl: localStorage.getItem("backendServerUrl") || "", backurl: '',
classNumber: '',
currentEditSubject: null, currentEditSubject: null,
studentList: ["加载中"], studentList: ["加载中"],
selectedSet: new Set(), // Absent students selectedSet: new Set(), // Absent students
@ -232,6 +258,9 @@ export default {
snackbarText: "", snackbarText: "",
fontSize: 28, fontSize: 28,
ServerSelectionDialog: false, ServerSelectionDialog: false,
datePickerDialog: false,
selectedDate: null,
refreshInterval: null,
}; };
}, },
@ -239,12 +268,25 @@ export default {
isMobile() { isMobile() {
return useDisplay().mobile.value; return useDisplay().mobile.value;
}, },
titleText() {
const today = new Date().toISOString().split('T')[0];
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
if (this.dateString === today) {
return '今天的作业';
} else if (this.dateString === yesterday) {
return '昨天的作业';
} else {
return `${this.dateString}的作业`;
}
},
}, },
async mounted() { async mounted() {
try { try {
this.updateBackendUrl(); this.updateBackendUrl();
await this.initializeData(); await this.initializeData();
this.setupAutoRefresh();
} catch (err) { } catch (err) {
console.error("初始化失败:", err); console.error("初始化失败:", err);
this.showError("初始化失败,请刷新页面重试"); this.showError("初始化失败,请刷新页面重试");
@ -253,7 +295,7 @@ export default {
methods: { methods: {
async initializeData() { async initializeData() {
const res = await axios.get(this.backurl + "/config.json"); const res = await axios.get(`${this.backurl}/config`);
this.studentList = res.data.studentList; this.studentList = res.data.studentList;
localStorage.setItem("studentList", res.data.studentList); localStorage.setItem("studentList", res.data.studentList);
this.homeworkArrange = res.data.homeworkArrange; this.homeworkArrange = res.data.homeworkArrange;
@ -276,10 +318,21 @@ export default {
setCurrentDate() { setCurrentDate() {
if (this.$route.query.date) { if (this.$route.query.date) {
this.dateString = this.$route.query.date; //
try {
const date = new Date(this.$route.query.date);
if (isNaN(date.getTime())) {
throw new Error('Invalid date');
}
this.dateString = date.toISOString().split('T')[0];
} catch (e) {
// 使
const today = new Date();
this.dateString = today.toISOString().split('T')[0];
}
} else { } else {
const today = new Date(); const today = new Date();
this.dateString = today.toISOString().split("T")[0]; this.dateString = today.toISOString().split('T')[0];
} }
}, },
@ -356,11 +409,11 @@ export default {
async uploadData() { async uploadData() {
try { try {
this.uploadLoading = true; this.uploadLoading = true;
await axios.post(this.backurl + "/upload", { await axios.post(`${this.backurl}/homework`, {
date: this.dateString, date: new Date(this.dateString).toISOString().split('T')[0],
data: this.homeworkData, data: this.homeworkData,
attendance: Array.from(this.selectedSet), attendance: Array.from(this.selectedSet),
late: Array.from(this.lateSet), // Upload late students as well late: Array.from(this.lateSet),
}); });
this.synced = true; this.synced = true;
this.showSyncMessage(); this.showSyncMessage();
@ -385,8 +438,9 @@ export default {
}, },
async downloadDataDirectly() { async downloadDataDirectly() {
const formattedDate = new Date(this.dateString).toISOString().split('T')[0];
const res = await axios.get( const res = await axios.get(
this.backurl + "/download?date=" + this.dateString `${this.backurl}/homework?date=${formattedDate}`
); );
this.homeworkData = res.data.data || this.homeworkData; this.homeworkData = res.data.data || this.homeworkData;
@ -440,9 +494,39 @@ export default {
}, },
updateBackendUrl() { updateBackendUrl() {
const savedUrl = localStorage.getItem("backendServerUrl"); const domain = localStorage.getItem('backendServerDomain');
if (savedUrl) { const classNum = localStorage.getItem('classNumber');
this.backurl = savedUrl;
if (domain && classNum) {
this.backurl = `${domain}/${classNum}`;
this.classNumber = classNum;
}
},
setupAutoRefresh() {
const autoRefresh = localStorage.getItem('autoRefresh') === 'true';
const interval = parseInt(localStorage.getItem('refreshInterval')) || 300;
if (autoRefresh) {
this.refreshInterval = setInterval(() => {
this.downloadData();
}, interval * 1000);
}
},
goToDate() {
if (this.selectedDate) {
const formattedDate = new Date(this.selectedDate).toISOString().split('T')[0];
this.dateString = formattedDate;
this.$router.push(`/?date=${formattedDate}`);
this.datePickerDialog = false;
this.downloadData();
}
},
beforeDestroy() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
} }
}, },
}, },

160
src/pages/settings.vue Normal file
View File

@ -0,0 +1,160 @@
<template>
<v-app-bar>
<template #prepend>
<v-btn icon="mdi-arrow-left" variant="text" @click="$router.push('/')" />
</template>
<v-app-bar-title>设置</v-app-bar-title>
</v-app-bar>
<v-container>
<v-row>
<v-col cols="12">
<v-card>
<v-card-title>服务器设置</v-card-title>
<v-card-text>
<v-text-field
v-model="serverDomain"
label="服务器域名"
placeholder="例如: http://example.com"
required
/>
<v-text-field
v-model="classNumber"
label="班号"
placeholder="例如: 1 或 A"
required
:rules="[v => !!v || '班号不能为空', v => /^[A-Za-z0-9]+$/.test(v) || '班号只能包含字母和数字']"
/>
<v-btn color="primary" @click="saveServerSettings">保存</v-btn>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12">
<v-card>
<v-card-title>学生列表设置</v-card-title>
<v-card-text>
<v-textarea v-model="students" label="学生列表" required />
<v-btn color="primary" @click="saveStudents">保存</v-btn>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12">
<v-card>
<v-card-title>自动刷新设置</v-card-title>
<v-card-text>
<v-switch v-model="autoRefresh" label="启用自动刷新" />
<v-text-field
v-model="refreshInterval"
type="number"
label="刷新间隔(秒)"
:disabled="!autoRefresh"
/>
<v-btn color="primary" @click="saveRefreshSettings">保存</v-btn>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
<v-snackbar v-model="snackbar">
{{ snackbarText }}
</v-snackbar>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
serverDomain: '',
classNumber: '',
students: '',
studentsList: [],
snackbar: false,
snackbarText: '',
autoRefresh: false,
refreshInterval: 300,
};
},
mounted() {
this.loadSettings();
},
methods: {
loadSettings() {
const savedDomain = localStorage.getItem('backendServerDomain');
const savedClass = localStorage.getItem('classNumber');
if (savedDomain) {
this.serverDomain = savedDomain;
}
if (savedClass) {
this.classNumber = savedClass;
}
if (localStorage.getItem('studentList')) {
this.students = localStorage.getItem('studentList').replace(/,/g, '\n');
}
this.autoRefresh = localStorage.getItem('autoRefresh') === 'true';
this.refreshInterval = parseInt(localStorage.getItem('refreshInterval')) || 300;
},
saveServerSettings() {
try {
if (this.serverDomain === '') {
localStorage.removeItem('backendServerDomain');
localStorage.removeItem('classNumber');
this.showMessage('删除成功');
return;
}
new URL(this.serverDomain);
const cleanDomain = this.serverDomain.replace(/\/+$/, '');
if (!this.classNumber || !/^[A-Za-z0-9]+$/.test(this.classNumber)) {
throw new Error('Invalid class number');
}
localStorage.setItem('backendServerDomain', cleanDomain);
localStorage.setItem('classNumber', this.classNumber);
this.showMessage('保存成功');
} catch (error) {
console.error(error);
this.showMessage('保存失败,请检查服务器域名和班号');
}
},
async saveStudents() {
try {
this.studentsList = this.students.split('\n');
await axios.put(`${this.serverDomain}/students`, {
studentList: this.studentsList,
id: 1,
});
localStorage.setItem('studentList', this.students);
this.showMessage('保存成功');
} catch (error) {
console.error(error);
this.showMessage('保存失败,请检查学生列表');
}
},
saveRefreshSettings() {
localStorage.setItem('autoRefresh', this.autoRefresh);
localStorage.setItem('refreshInterval', this.refreshInterval);
this.showMessage('保存成功');
},
showMessage(text) {
this.snackbarText = text;
this.snackbar = true;
},
},
};
</script>