mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-07-03 01:39:22 +00:00
1
This commit is contained in:
parent
2004acc573
commit
dfdb662e11
@ -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>
|
@ -5,16 +5,29 @@
|
||||
</template>
|
||||
|
||||
<v-app-bar-title>
|
||||
<strong>{{ dateString }}</strong> 作业
|
||||
{{ classNumber }}班 - {{ titleText }}
|
||||
</v-app-bar-title>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<v-btn
|
||||
icon="mdi-cog"
|
||||
variant="text"
|
||||
@click="ServerSelectionDialog = true"
|
||||
/>
|
||||
<template #append>
|
||||
<v-btn
|
||||
icon="mdi-calendar"
|
||||
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-container
|
||||
class="main-window"
|
||||
@ -200,19 +213,32 @@
|
||||
>
|
||||
{{ snackbarText }}
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { useDisplay } from "vuetify";
|
||||
import ServerSelection from "../components/ServerSelection.vue";
|
||||
|
||||
export default {
|
||||
name: "HomeworkBoard",
|
||||
components: { ServerSelection },
|
||||
data() {
|
||||
return {
|
||||
backurl: localStorage.getItem("backendServerUrl") || "",
|
||||
backurl: '',
|
||||
classNumber: '',
|
||||
currentEditSubject: null,
|
||||
studentList: ["加载中"],
|
||||
selectedSet: new Set(), // Absent students
|
||||
@ -232,6 +258,9 @@ export default {
|
||||
snackbarText: "",
|
||||
fontSize: 28,
|
||||
ServerSelectionDialog: false,
|
||||
datePickerDialog: false,
|
||||
selectedDate: null,
|
||||
refreshInterval: null,
|
||||
};
|
||||
},
|
||||
|
||||
@ -239,12 +268,25 @@ export default {
|
||||
isMobile() {
|
||||
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() {
|
||||
try {
|
||||
this.updateBackendUrl();
|
||||
await this.initializeData();
|
||||
this.setupAutoRefresh();
|
||||
} catch (err) {
|
||||
console.error("初始化失败:", err);
|
||||
this.showError("初始化失败,请刷新页面重试");
|
||||
@ -253,7 +295,7 @@ export default {
|
||||
|
||||
methods: {
|
||||
async initializeData() {
|
||||
const res = await axios.get(this.backurl + "/config.json");
|
||||
const res = await axios.get(`${this.backurl}/config`);
|
||||
this.studentList = res.data.studentList;
|
||||
localStorage.setItem("studentList", res.data.studentList);
|
||||
this.homeworkArrange = res.data.homeworkArrange;
|
||||
@ -276,10 +318,21 @@ export default {
|
||||
|
||||
setCurrentDate() {
|
||||
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 {
|
||||
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() {
|
||||
try {
|
||||
this.uploadLoading = true;
|
||||
await axios.post(this.backurl + "/upload", {
|
||||
date: this.dateString,
|
||||
await axios.post(`${this.backurl}/homework`, {
|
||||
date: new Date(this.dateString).toISOString().split('T')[0],
|
||||
data: this.homeworkData,
|
||||
attendance: Array.from(this.selectedSet),
|
||||
late: Array.from(this.lateSet), // Upload late students as well
|
||||
late: Array.from(this.lateSet),
|
||||
});
|
||||
this.synced = true;
|
||||
this.showSyncMessage();
|
||||
@ -385,8 +438,9 @@ export default {
|
||||
},
|
||||
|
||||
async downloadDataDirectly() {
|
||||
const formattedDate = new Date(this.dateString).toISOString().split('T')[0];
|
||||
const res = await axios.get(
|
||||
this.backurl + "/download?date=" + this.dateString
|
||||
`${this.backurl}/homework?date=${formattedDate}`
|
||||
);
|
||||
this.homeworkData = res.data.data || this.homeworkData;
|
||||
|
||||
@ -440,9 +494,39 @@ export default {
|
||||
},
|
||||
|
||||
updateBackendUrl() {
|
||||
const savedUrl = localStorage.getItem("backendServerUrl");
|
||||
if (savedUrl) {
|
||||
this.backurl = savedUrl;
|
||||
const domain = localStorage.getItem('backendServerDomain');
|
||||
const classNum = localStorage.getItem('classNumber');
|
||||
|
||||
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
160
src/pages/settings.vue
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user