1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-07-01 16:49:22 +00:00

Refactor StudentListCard.vue and settings.vue to improve student data handling and UI interactions. Update student list management methods, enhance data loading and saving functionality, and streamline component props. Remove redundant code and improve overall readability.

This commit is contained in:
SunWuyuan 2025-05-18 16:04:25 +08:00
parent 088e19eaa7
commit ab20d6cecb
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
2 changed files with 204 additions and 249 deletions

View File

@ -142,7 +142,7 @@
class="text-body-1 flex-grow-1"
@click="handleClick(index, student)"
>
{{ student }}
{{ student.name }}
</span>
<div
@ -174,14 +174,14 @@
<!-- 高级编辑模式 -->
<div v-else class="pt-2">
<v-textarea
v-model="text"
v-model="modelValue.text"
label="批量编辑学生列表"
placeholder="每行输入一个学生姓名"
hint="使用文本编辑模式批量编辑学生名单,保存时会自动去除空行"
persistent-hint
variant="outlined"
rows="10"
@input="handleTextInput"
@update:model-value="handleTextInput"
/>
</div>
</v-expand-transition>
@ -194,7 +194,7 @@
size="large"
:loading="loading"
:disabled="loading"
@click="$emit('save')"
@click="saveStudents"
>
保存名单
</v-btn>
@ -205,7 +205,7 @@
size="large"
:loading="loading"
:disabled="loading"
@click="$emit('reload')"
@click="loadStudents"
>
重载名单
</v-btn>
@ -219,6 +219,8 @@
import UnsavedWarning from "../common/UnsavedWarning.vue";
import "@/styles/warnings.scss";
import { pinyin } from "pinyin-pro";
import dataProvider from "@/utils/dataProvider";
import { getSetting } from "@/utils/settings";
export default {
name: "StudentListCard",
@ -226,19 +228,7 @@ export default {
UnsavedWarning,
},
props: {
modelValue: {
type: Object,
required: true,
default: () => ({
list: [],
text: "",
advanced: false,
}),
},
loading: Boolean,
error: String,
isMobile: Boolean,
unsavedChanges: Boolean,
},
data() {
@ -248,101 +238,200 @@ export default {
index: -1,
name: "",
},
modelValue: {
list: [],
text: "",
advanced: false,
},
loading: false,
error: null,
lastSavedData: null,
unsavedChanges: false,
};
},
emits: ["update:modelValue", "save", "reload"],
computed: {
text: {
get() {
return this.modelValue.text;
},
set(value) {
this.handleTextInput(value);
watch: {
modelValue: {
handler(newData) {
if (this.lastSavedData) {
this.unsavedChanges = JSON.stringify(newData.list) !== JSON.stringify(this.lastSavedData);
}
if (!this.modelValue.advanced) {
this.modelValue.text = newData.list
.slice()
.sort((a, b) => a.id - b.id)
.map(s => s.name)
.join("\n");
}
},
deep: true,
},
},
mounted() {
this.loadStudents();
},
methods: {
// UI
async loadStudents() {
this.error = null;
try {
this.loading = true;
const classNum = getSetting("server.classNumber");
if (!classNum) {
throw new Error("请先设置班号");
}
try {
const response = await dataProvider.loadData("classworks-list-main");
if (response.success != false && Array.isArray(response)) {
this.modelValue.list = response.map((item, index) => {
if (typeof item === 'string') {
return { id: index + 1, name: item };
}
return {
id: item.id || index + 1,
name: item.name || item.toString()
};
});
this.modelValue.list.sort((a, b) => a.id - b.id);
this.modelValue.text = this.modelValue.list.map(s => s.name).join("\n");
this.lastSavedData = JSON.parse(JSON.stringify(this.modelValue.list));
this.unsavedChanges = false;
return;
}
} catch (error) {
console.warn(
"Failed to load student list from dedicated key, falling back to config",
error
);
}
} catch (error) {
console.error("加载学生列表失败:", error);
this.error = error.message || "加载失败,请检查设置";
this.$message?.error("加载失败", this.error);
} finally {
this.loading = false;
}
},
async saveStudents() {
try {
const classNum = getSetting("server.classNumber");
if (!classNum) {
throw new Error("请先设置班号");
}
const formattedList = this.modelValue.list
.slice()
.sort((a, b) => a.id - b.id)
.map((student, index) => ({
id: index + 1,
name: student.name
}));
const response = await dataProvider.saveData(
"classworks-list-main",
formattedList
);
if (response.success === false) {
throw new Error(response.error?.message || "保存失败");
}
this.modelValue.list = formattedList;
this.lastSavedData = JSON.parse(JSON.stringify(formattedList));
this.unsavedChanges = false;
this.$message?.success("保存成功", "学生列表已更新");
} catch (error) {
console.error("保存学生列表失败:", error);
this.$message?.error("保存失败", error.message || "请重试");
}
},
toggleAdvanced() {
const advanced = !this.modelValue.advanced;
this.updateModelValue({
advanced,
text: advanced ? this.modelValue.list.join("\n") : this.modelValue.text,
list: this.modelValue.list,
});
this.modelValue.advanced = !this.modelValue.advanced;
},
updateModelValue(newData) {
this.$emit("update:modelValue", {
...this.modelValue,
...newData,
handleTextInput(text) {
if (!this.modelValue.advanced) return;
// Split the text into lines and filter out empty lines
const lines = text.split("\n").filter((line) => line.trim());
// Create a map of existing student names to their IDs
const currentIds = new Map(this.modelValue.list.map(s => [s.name, s.id]));
let maxId = Math.max(0, ...this.modelValue.list.map(s => s.id));
// Create new list preserving IDs for existing names and generating new IDs for new names
const newList = lines.map(name => {
name = name.trim();
if (currentIds.has(name)) {
return { id: currentIds.get(name), name };
}
return { id: ++maxId, name };
});
// Update the list
this.modelValue.list = newList;
},
//
addStudent() {
const name = this.newStudentName.trim();
if (!name || this.modelValue.list.includes(name)) return;
const newList = [...this.modelValue.list, name];
this.updateModelValue({
list: newList,
text: newList.join("\n"),
});
this.newStudentName = "";
},
removeStudent(index) {
const newList = this.modelValue.list.filter((_, i) => i !== index);
this.updateModelValue({
list: newList,
text: newList.join("\n"),
});
},
moveStudent(index, direction) {
const newList = [...this.modelValue.list];
let targetIndex;
if (direction === "top") {
targetIndex = 0;
} else if (direction === "up") {
targetIndex = index - 1;
} else {
targetIndex = index + 1;
}
if (targetIndex >= 0 && targetIndex < newList.length) {
const [student] = newList.splice(index, 1);
newList.splice(targetIndex, 0, student);
this.updateModelValue({
list: newList,
text: newList.join("\n"),
});
if (name && !this.modelValue.list.some(s => s.name === name)) {
const maxId = Math.max(0, ...this.modelValue.list.map(s => s.id));
this.modelValue.list.push({ id: maxId + 1, name });
this.newStudentName = "";
}
},
//
startEdit(index, name) {
this.editState = { index, name };
startEdit(index, student) {
this.editState.index = index;
this.editState.name = student.name;
},
saveEdit() {
const { index, name } = this.editState;
if (index === -1 || !name.trim()) return;
if (this.editState.index !== -1) {
const newName = this.editState.name.trim();
if (newName && newName !== this.modelValue.list[this.editState.index].name) {
this.modelValue.list[this.editState.index].name = newName;
}
this.editState.index = -1;
this.editState.name = "";
}
},
const newList = [...this.modelValue.list];
newList[index] = name.trim();
removeStudent(index) {
if (index !== undefined) {
this.modelValue.list.splice(index, 1);
}
},
this.updateModelValue({
list: newList,
text: newList.join("\n"),
});
this.editState = { index: -1, name: "" };
moveStudent(index, direction) {
if (direction === "top") {
if (index > 0) {
const student = this.modelValue.list[index];
this.modelValue.list.splice(index, 1);
this.modelValue.list.unshift(student);
this.modelValue.list.forEach((s, i) => s.id = i + 1);
}
} else {
const newIndex = direction === "up" ? index - 1 : index + 1;
if (newIndex >= 0 && newIndex < this.modelValue.list.length) {
[this.modelValue.list[index], this.modelValue.list[newIndex]] = [
this.modelValue.list[newIndex],
this.modelValue.list[index],
];
[this.modelValue.list[index].id, this.modelValue.list[newIndex].id] = [
this.modelValue.list[newIndex].id,
this.modelValue.list[index].id,
];
}
}
},
handleClick(index, student) {
@ -351,35 +440,20 @@ export default {
}
},
handleTextInput(value) {
const list = value
.split("\n")
.map((s) => s.trim())
.filter((s) => s);
this.updateModelValue({
text: value,
list,
});
},
sortStudentsByPinyin() {
const newList = [...this.modelValue.list].sort((a, b) => {
const pinyinA = pinyin(a, { toneType: "none" ,mode: 'surname'});
const pinyinB = pinyin(b, { toneType: "none",mode: 'surname' });
const sorted = [...this.modelValue.list].sort((a, b) => {
const pinyinA = pinyin(a.name, { toneType: "none" });
const pinyinB = pinyin(b.name, { toneType: "none" });
return pinyinA.localeCompare(pinyinB);
});
this.updateModelValue({
list: newList,
text: newList.join("\n"),
});
sorted.forEach((s, i) => s.id = i + 1);
this.modelValue.list = sorted;
},
},
};
</script>
<style scoped>
<style lang="scss" scoped>
.student-card {
transition: all 0.2s ease;
}
@ -389,26 +463,7 @@ export default {
transition: opacity 0.2s ease;
}
/* 修改警告样式的选择器和实现 */
.v-card.unsaved-changes {
animation: pulse-warning 2s infinite;
border: 2px solid rgb(var(--v-theme-warning)) !important;
}
@keyframes pulse-warning {
0%,
100% {
border-color: rgba(var(--v-theme-warning), 1) !important;
}
50% {
border-color: rgba(var(--v-theme-warning), 0.5) !important;
}
}
/* 移动端样式 */
@media (max-width: 600px) {
.action-buttons {
opacity: 1;
}
.unsaved-changes {
border-color: rgb(var(--v-theme-warning)) !important;
}
</style>

View File

@ -34,12 +34,13 @@
direction="vertical"
>
<v-tabs-window-item value="index">
<v-card title="Classworks" subtitle="设置" class="rounded-xl"
border
>
<v-card title="Classworks" subtitle="设置" class="rounded-xl" border>
<v-card-text>
<v-alert color="error" variant="tonal" icon="mdi-alert-circle"
class="rounded-xl"
<v-alert
color="error"
variant="tonal"
icon="mdi-alert-circle"
class="rounded-xl"
>Classworks
是开源免费的软件官方没有提供任何形式的付费支持服务源代码仓库地址在
<a
@ -140,16 +141,7 @@
</v-tabs-window-item>
<v-tabs-window-item value="student">
<student-list-card
border
:loading="loading.students"
:error="studentsError"
:is-mobile="isMobile"
:unsaved-changes="hasUnsavedChanges"
@save="saveStudents"
@reload="loadStudents"
@update:modelValue="handleStudentDataChange"
/>
<student-list-card border :is-mobile="isMobile" />
</v-tabs-window-item>
<v-tabs-window-item value="developer"
@ -255,7 +247,8 @@ export default {
},
data() {
const provider = getSetting("server.provider");
const showNamespaceSettings = provider === "kv-server" || provider === "classworkscloud";
const showNamespaceSettings =
provider === "kv-server" || provider === "classworkscloud";
const settings = {
server: {
@ -340,13 +333,15 @@ export default {
icon: "mdi-server",
value: "server",
},
...(showNamespaceSettings ? [
{
title: "命名空间",
icon: "mdi-database-lock",
value: "namespace",
}
] : []),
...(showNamespaceSettings
? [
{
title: "命名空间",
icon: "mdi-database-lock",
value: "namespace",
},
]
: []),
{
title: "分享设置",
icon: "mdi-share",
@ -417,7 +412,6 @@ export default {
this.unwatchSettings = watchSettings(() => {
this.loadAllSettings();
});
this.loadStudents();
},
beforeUnmount() {
@ -435,9 +429,7 @@ export default {
});
},
//
handleSettingsChange(newSettings) {
// 使
if (this.settingsChangeTimeout) {
clearTimeout(this.settingsChangeTimeout);
}
@ -453,13 +445,12 @@ export default {
this.showMessage("设置已更新", `${settingKey} 已保存`);
} else {
this.showError("保存失败", `${settingKey} 设置失败`);
//
this.settings[section][key] = currentValue;
}
}
});
});
}, 100); // 100ms
}, 100);
},
showMessage(title, content = "", type = "success") {
@ -470,89 +461,6 @@ export default {
this.$message.error(title, content);
},
async loadStudents() {
this.studentsError = null;
try {
this.loading.students = true;
const classNum = getSetting("server.classNumber");
if (!classNum) {
throw new Error("请先设置班号");
}
try {
// Try to get student list from the dedicated key
const response = await dataProvider.loadData("classworks-list-main");
if (response.success != false && Array.isArray(response)) {
// Transform the data into a simple list of names
this.studentData.list = response.map((student) => student.name);
this.studentData.text = this.studentData.list.join("\n");
this.lastSavedData = [...this.studentData.list];
this.hasUnsavedChanges = false;
return;
}
} catch (error) {
console.warn(
"Failed to load student list from dedicated key, falling back to config",
error
);
}
} catch (error) {
console.error("加载学生列表失败:", error);
this.studentsError = error.message || "加载失败,请检查设置";
this.showError("加载失败", this.studentsError);
} finally {
this.loading.students = false;
}
},
async saveStudents() {
try {
const classNum = getSetting("server.classNumber");
if (!classNum) {
throw new Error("请先设置班号");
}
// Convert the list of names to the new format with IDs
const formattedStudentList = this.studentData.list.map(
(name, index) => ({
id: index + 1,
name,
})
);
// Save the student list to the dedicated key
const response = await dataProvider.saveData(
"classworks-list-main",
formattedStudentList
);
if (response.success == false) {
throw new Error(response.error?.message || "保存失败");
}
//
this.lastSavedData = [...this.studentData.list];
this.hasUnsavedChanges = false;
this.showMessage("保存成功", "学生列表已更新");
} catch (error) {
console.error("保存学生列表失败:", error);
this.showError("保存失败", error.message || "请重试");
}
},
handleStudentDataChange(newData) {
//
if (
JSON.stringify(newData.list) !== JSON.stringify(this.studentData.list)
) {
this.studentData = { ...newData };
this.hasUnsavedChanges = true;
}
},
saveEdit() {
if (this.editingIndex !== -1) {
const newName = this.editingName.trim();
@ -587,12 +495,6 @@ export default {
}
},
setStudentNumber(index) {
this.studentToMove = index;
this.newPosition = String(index + 1);
this.numberDialog = true;
},
applyNewPosition() {
const newPos = parseInt(this.newPosition) - 1;
if (
@ -680,11 +582,9 @@ export default {
onSettingsSaved() {
this.showMessage("设置已更新", "您的设置已成功保存");
//
},
onSettingUpdate(key, value) {
//
this.showMessage("设置已更新", `${key} 已保存为 ${value}`);
},
},