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>
|
</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
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