mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-07-07 12:39:22 +00:00
Refactor index.vue to replace v-dialog with homework-edit-dialog for improved homework editing experience. Add HomeworkTemplateCard to settings.vue for homework management. Enhance kvServerProvider with additional error logging for better debugging.
This commit is contained in:
parent
53ed1f556f
commit
f2d88437e6
541
src/components/HomeworkEditDialog.vue
Normal file
541
src/components/HomeworkEditDialog.vue
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
# 创建新的作业编辑对话框组件
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialogVisible" width="auto" max-width="900" @click:outside="handleClose">
|
||||||
|
<v-card border>
|
||||||
|
<v-card-title>{{ title }}</v-card-title>
|
||||||
|
<v-card-subtitle>
|
||||||
|
{{ autoSave ? "喵?喵呜!" : "写完后点击上传谢谢喵" }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<v-textarea
|
||||||
|
ref="inputRef"
|
||||||
|
v-model="content"
|
||||||
|
auto-grow
|
||||||
|
placeholder="使用换行表示分条"
|
||||||
|
rows="5"
|
||||||
|
@click="updateCurrentLine"
|
||||||
|
@keyup="updateCurrentLine"
|
||||||
|
width="480"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Template Buttons Section -->
|
||||||
|
<div v-if="templateData" class="mt-4">
|
||||||
|
<div v-if="hasTemplates" class="template-buttons">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Subject specific books -->
|
||||||
|
<template v-if="subjectBooks">
|
||||||
|
<div v-for="(pages, book) in subjectBooks" :key="book" class="button-group">
|
||||||
|
<v-chip
|
||||||
|
class="ma-1 book-chip"
|
||||||
|
:color="isBookSelected(book) ? 'success' : 'default'"
|
||||||
|
:variant="isBookSelected(book) ? 'elevated' : 'flat'"
|
||||||
|
@click="handleBookClick(book)"
|
||||||
|
>
|
||||||
|
{{ book }}
|
||||||
|
</v-chip>
|
||||||
|
|
||||||
|
<!-- Show pages only if book is selected -->
|
||||||
|
<div v-if="isBookSelected(book)" class="pages-container mt-2">
|
||||||
|
<v-chip
|
||||||
|
v-for="page in pages"
|
||||||
|
:key="page"
|
||||||
|
class="ma-1"
|
||||||
|
:color="isPageSelected(book, page) ? 'info' : 'default'"
|
||||||
|
:variant="isPageSelected(book, page) ? 'elevated' : 'flat'"
|
||||||
|
@click="handlePageClick(book, page)"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Common books -->
|
||||||
|
<template v-if="commonBooks">
|
||||||
|
<div v-for="(pages, book) in commonBooks" :key="book" class="button-group">
|
||||||
|
<v-chip
|
||||||
|
class="ma-1 book-chip"
|
||||||
|
:color="isBookSelected(book) ? 'success' : 'default'"
|
||||||
|
:variant="isBookSelected(book) ? 'elevated' : 'flat'"
|
||||||
|
@click="handleBookClick(book)"
|
||||||
|
>
|
||||||
|
{{ book }}
|
||||||
|
</v-chip>
|
||||||
|
|
||||||
|
<!-- Show pages only if book is selected -->
|
||||||
|
<div v-if="isBookSelected(book)" class="pages-container mt-2">
|
||||||
|
<v-chip
|
||||||
|
v-for="page in pages"
|
||||||
|
:key="page"
|
||||||
|
class="ma-1"
|
||||||
|
:color="isPageSelected(book, page) ? 'info' : 'default'"
|
||||||
|
:variant="isPageSelected(book, page) ? 'elevated' : 'flat'"
|
||||||
|
@click="handlePageClick(book, page)"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div v-if="templateData.actions?.length" class="button-group">
|
||||||
|
<v-chip
|
||||||
|
v-for="action in templateData.actions"
|
||||||
|
:key="action"
|
||||||
|
class="ma-1"
|
||||||
|
color="primary"
|
||||||
|
variant="flat"
|
||||||
|
@click="insertTemplate(action)"
|
||||||
|
>
|
||||||
|
{{ action }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-center text-body-2 text-disabled mt-2">
|
||||||
|
暂无可用的模板
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Tools Section -->
|
||||||
|
<div class="quick-tools ml-4" style="min-width: 180px;">
|
||||||
|
<!-- Numeric Keypad -->
|
||||||
|
<div class="numeric-keypad mb-4">
|
||||||
|
<div class="keypad-row">
|
||||||
|
<v-btn
|
||||||
|
v-for="n in 3"
|
||||||
|
:key="n"
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn"
|
||||||
|
@click="insertAtCursor(String(n))"
|
||||||
|
>
|
||||||
|
{{ n }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="keypad-row">
|
||||||
|
<v-btn
|
||||||
|
v-for="n in 3"
|
||||||
|
:key="n"
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn"
|
||||||
|
@click="insertAtCursor(String(n + 3))"
|
||||||
|
>
|
||||||
|
{{ n + 3 }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="keypad-row">
|
||||||
|
<v-btn
|
||||||
|
v-for="n in 3"
|
||||||
|
:key="n"
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn"
|
||||||
|
@click="insertAtCursor(String(n + 6))"
|
||||||
|
>
|
||||||
|
{{ n + 6 }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="keypad-row">
|
||||||
|
<v-btn
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn"
|
||||||
|
@click="insertAtCursor('-')"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn"
|
||||||
|
@click="insertAtCursor('0')"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn"
|
||||||
|
color="error"
|
||||||
|
@click="deleteLastChar"
|
||||||
|
>
|
||||||
|
←
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="keypad-row">
|
||||||
|
<v-btn
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn space-btn"
|
||||||
|
@click="insertAtCursor(' ')"
|
||||||
|
>
|
||||||
|
空格
|
||||||
|
</v-btn><v-btn
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
class="keypad-btn space-btn"
|
||||||
|
@click="insertAtCursor('\n')"
|
||||||
|
>
|
||||||
|
换行
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-1">
|
||||||
|
<v-btn
|
||||||
|
v-for="text in quickTexts"
|
||||||
|
:key="text"
|
||||||
|
size="small"
|
||||||
|
variant="flat"
|
||||||
|
@click="insertAtCursor(text)"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import dataProvider from "@/utils/dataProvider";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "HomeworkEditDialog",
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
initialContent: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
autoSave: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue", "save"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
content: "",
|
||||||
|
templateData: null,
|
||||||
|
currentLine: "",
|
||||||
|
currentLineStart: 0,
|
||||||
|
currentLineEnd: 0,
|
||||||
|
quickTexts: ["课", "题", "T", "P"]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dialogVisible: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit("update:modelValue", value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subject() {
|
||||||
|
// 标题直接就是科目名称
|
||||||
|
return this.title;
|
||||||
|
},
|
||||||
|
hasTemplates() {
|
||||||
|
return !!(
|
||||||
|
(this.templateData?.actions?.length) ||
|
||||||
|
this.subjectBooks ||
|
||||||
|
this.commonBooks
|
||||||
|
);
|
||||||
|
},
|
||||||
|
subjectBooks() {
|
||||||
|
if (!this.subject || !this.templateData?.subjects?.[this.subject]?.books) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.templateData.subjects[this.subject].books;
|
||||||
|
},
|
||||||
|
commonBooks() {
|
||||||
|
if (!this.templateData?.commonSubject?.books) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.templateData.commonSubject.books;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
async modelValue(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
// 当对话框打开时,重置内容为初始内容
|
||||||
|
this.content = this.initialContent;
|
||||||
|
// 加载模板数据
|
||||||
|
try {
|
||||||
|
this.templateData = await dataProvider.loadData("classworks-config-homework-template");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to load homework templates:", error);
|
||||||
|
this.templateData = null;
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.inputRef) {
|
||||||
|
this.$refs.inputRef.focus();
|
||||||
|
this.updateCurrentLine();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose() {
|
||||||
|
const trimmedContent = this.content.trim();
|
||||||
|
if (trimmedContent !== this.initialContent.trim()) {
|
||||||
|
this.$emit("save", trimmedContent);
|
||||||
|
}
|
||||||
|
this.dialogVisible = false;
|
||||||
|
},
|
||||||
|
updateCurrentLine() {
|
||||||
|
const textarea = this.$refs.inputRef.$el.querySelector('textarea');
|
||||||
|
const cursorPosition = textarea.selectionStart;
|
||||||
|
const content = this.content;
|
||||||
|
|
||||||
|
let currentPos = 0;
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const lineLength = lines[i].length;
|
||||||
|
const totalLength = currentPos + lineLength;
|
||||||
|
|
||||||
|
if (cursorPosition <= totalLength || i === lines.length - 1) {
|
||||||
|
this.currentLine = lines[i];
|
||||||
|
this.currentLineStart = currentPos;
|
||||||
|
this.currentLineEnd = totalLength;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPos = totalLength + 1; // +1 for the newline character
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果光标在文本末尾或内容为空
|
||||||
|
if (!this.currentLine) {
|
||||||
|
this.currentLine = "";
|
||||||
|
this.currentLineStart = content.length;
|
||||||
|
this.currentLineEnd = content.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isBookSelected(book) {
|
||||||
|
return this.currentLine.includes(book);
|
||||||
|
},
|
||||||
|
isPageSelected(book, page) {
|
||||||
|
return this.currentLine.includes(page);
|
||||||
|
},
|
||||||
|
handleBookClick(book) {
|
||||||
|
if (this.isBookSelected(book)) {
|
||||||
|
// 删除包含该作业本的整行
|
||||||
|
const lines = this.content.split('\n');
|
||||||
|
const lineToDelete = lines.findIndex(line => line.includes(book));
|
||||||
|
if (lineToDelete !== -1) {
|
||||||
|
lines.splice(lineToDelete, 1);
|
||||||
|
this.content = lines.join('\n');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 在末尾插入新行
|
||||||
|
const hasContent = this.content.trim().length > 0;
|
||||||
|
this.content = (hasContent ? this.content.trim() + '\n' : '') + book;
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const textarea = this.$refs.inputRef.$el.querySelector('textarea');
|
||||||
|
textarea.focus();
|
||||||
|
|
||||||
|
if (!this.isBookSelected(book)) {
|
||||||
|
// 找到新插入的行的末尾位置
|
||||||
|
const lines = this.content.split('\n');
|
||||||
|
let position = 0;
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].includes(book)) {
|
||||||
|
position += lines[i].length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
position += lines[i].length + 1; // +1 for newline
|
||||||
|
}
|
||||||
|
textarea.setSelectionRange(position, position);
|
||||||
|
}
|
||||||
|
this.updateCurrentLine();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handlePageClick(book, page) {
|
||||||
|
if (this.isPageSelected(book, page)) {
|
||||||
|
// 删除当前行最后一处匹配的页码
|
||||||
|
const start = this.currentLineStart;
|
||||||
|
const end = this.currentLineEnd;
|
||||||
|
const currentLineContent = this.content.slice(start, end);
|
||||||
|
const lastIndex = currentLineContent.lastIndexOf(page);
|
||||||
|
if (lastIndex !== -1) {
|
||||||
|
const newLineContent =
|
||||||
|
currentLineContent.slice(0, lastIndex) +
|
||||||
|
currentLineContent.slice(lastIndex + page.length);
|
||||||
|
this.content = this.content.slice(0, start) +
|
||||||
|
newLineContent.trim() +
|
||||||
|
this.content.slice(end);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 在当前行末尾插入
|
||||||
|
const start = this.currentLineStart;
|
||||||
|
const end = this.currentLineEnd;
|
||||||
|
const currentLineContent = this.content.slice(start, end);
|
||||||
|
this.content = this.content.slice(0, start) +
|
||||||
|
currentLineContent.trim() +
|
||||||
|
(currentLineContent.trim().length > 0 ? ' ' : '') +
|
||||||
|
page +
|
||||||
|
this.content.slice(end);
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const textarea = this.$refs.inputRef.$el.querySelector('textarea');
|
||||||
|
textarea.focus();
|
||||||
|
|
||||||
|
// 将光标移动到当前行末尾
|
||||||
|
const lines = this.content.split('\n');
|
||||||
|
let position = 0;
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
position += lines[i].length;
|
||||||
|
if (position > this.currentLineStart) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
position += 1; // +1 for newline
|
||||||
|
}
|
||||||
|
textarea.setSelectionRange(position, position);
|
||||||
|
this.updateCurrentLine();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
insertTemplate(text) {
|
||||||
|
const textarea = this.$refs.inputRef.$el.querySelector('textarea');
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
|
||||||
|
// 在快捷操作前添加空格
|
||||||
|
const needsSpace = start > 0 && this.content[start - 1] !== ' ' && this.content[start - 1] !== '\n';
|
||||||
|
this.content = this.content.slice(0, start) + (needsSpace ? ' ' : '') + text + this.content.slice(end);
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
textarea.focus();
|
||||||
|
const newPosition = start + text.length + (needsSpace ? 1 : 0);
|
||||||
|
textarea.setSelectionRange(newPosition, newPosition);
|
||||||
|
this.updateCurrentLine();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
insertAtCursor(text) {
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
const textarea = this.$refs.inputRef.$el.querySelector('textarea');
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
|
||||||
|
this.content = this.content.slice(0, start) + text + this.content.slice(end);
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
textarea.focus();
|
||||||
|
const newPosition = start + text.length;
|
||||||
|
textarea.setSelectionRange(newPosition, newPosition);
|
||||||
|
this.updateCurrentLine();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteLastChar() {
|
||||||
|
const textarea = this.$refs.inputRef.$el.querySelector('textarea');
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
|
||||||
|
if (start === end) {
|
||||||
|
// 如果没有选中文本,删除光标前一个字符
|
||||||
|
if (start > 0) {
|
||||||
|
this.content = this.content.slice(0, start - 1) + this.content.slice(start);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
textarea.focus();
|
||||||
|
textarea.setSelectionRange(start - 1, start - 1);
|
||||||
|
this.updateCurrentLine();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果有选中文本,删除选中部分
|
||||||
|
this.content = this.content.slice(0, start) + this.content.slice(end);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
textarea.focus();
|
||||||
|
textarea.setSelectionRange(start, start);
|
||||||
|
this.updateCurrentLine();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.template-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.book-chip {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
margin-right: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.v-chip) {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-tools {
|
||||||
|
border-left: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numeric-keypad {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keypad-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keypad-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 36px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-btn {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
690
src/components/settings/cards/HomeworkTemplateCard.vue
Normal file
690
src/components/settings/cards/HomeworkTemplateCard.vue
Normal file
@ -0,0 +1,690 @@
|
|||||||
|
<template>
|
||||||
|
<settings-card
|
||||||
|
title="作业模板配置"
|
||||||
|
icon="mdi-book-edit"
|
||||||
|
:loading="loading"
|
||||||
|
border
|
||||||
|
>
|
||||||
|
<!-- 顶部操作按钮 -->
|
||||||
|
<v-alert
|
||||||
|
v-if="error"
|
||||||
|
type="error"
|
||||||
|
variant="tonal"
|
||||||
|
closable
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
{{ error }}
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<div class="d-flex justify-space-between align-center mb-6">
|
||||||
|
<div>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
prepend-icon="mdi-refresh"
|
||||||
|
:loading="loading"
|
||||||
|
@click="loadConfig"
|
||||||
|
class="mr-2"
|
||||||
|
>
|
||||||
|
重新加载配置
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="success"
|
||||||
|
size="large"
|
||||||
|
prepend-icon="mdi-content-save"
|
||||||
|
:loading="loading"
|
||||||
|
@click="saveConfig"
|
||||||
|
>
|
||||||
|
保存所有更改
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<v-chip
|
||||||
|
v-if="hasChanges"
|
||||||
|
color="warning"
|
||||||
|
variant="elevated"
|
||||||
|
>
|
||||||
|
有未保存的更改
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<setting-group title="科目配置" icon="mdi-book" border>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-text-field
|
||||||
|
v-model="newSubject"
|
||||||
|
label="添加新科目"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
append-inner-icon="mdi-plus"
|
||||||
|
@click:append-inner="addSubject"
|
||||||
|
@keyup.enter="addSubject"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item v-for="subject in subjectList" :key="subject">
|
||||||
|
<v-card border class="w-100 mb-2">
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-text-field
|
||||||
|
v-model="editedSubjects[subject]"
|
||||||
|
:placeholder="subject"
|
||||||
|
density="comfortable"
|
||||||
|
variant="plain"
|
||||||
|
hide-details
|
||||||
|
@blur="updateSubject(subject)"
|
||||||
|
/>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
@click="deleteSubject(subject)"
|
||||||
|
/>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
v-model="newBookTypes[subject]"
|
||||||
|
label="添加作业本名称"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
class="mb-2"
|
||||||
|
append-inner-icon="mdi-plus"
|
||||||
|
@click:append-inner="() => addBookType(subject)"
|
||||||
|
@keyup.enter="() => addBookType(subject)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-list density="compact" border rounded>
|
||||||
|
<v-list-item
|
||||||
|
v-for="(books, bookType) in config.subjects[subject].books"
|
||||||
|
:key="bookType"
|
||||||
|
:title="bookType"
|
||||||
|
@click="openSubjectBookDialog(subject, bookType, books)"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon icon="mdi-book-open-variant" class="mr-2" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-chip
|
||||||
|
size="small"
|
||||||
|
class="mr-2"
|
||||||
|
color="info"
|
||||||
|
>
|
||||||
|
{{ books.length }}个部分
|
||||||
|
</v-chip>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
@click.stop="() => deleteBookType(subject, bookType)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</setting-group>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<setting-group title="通用配置" icon="mdi-cog" border>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-text-field
|
||||||
|
v-model="newCommonBook"
|
||||||
|
label="添加作业本名称"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
append-inner-icon="mdi-plus"
|
||||||
|
@click:append-inner="addCommonBook"
|
||||||
|
@keyup.enter="addCommonBook"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<v-list density="compact" border rounded>
|
||||||
|
<v-list-item
|
||||||
|
v-for="(books, bookType) in config.commonSubject.books"
|
||||||
|
:key="bookType"
|
||||||
|
:title="bookType"
|
||||||
|
@click="openSubjectBookDialog('common', bookType, books)"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon icon="mdi-book-multiple" class="mr-2" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-chip
|
||||||
|
size="small"
|
||||||
|
class="mr-2"
|
||||||
|
color="info"
|
||||||
|
>
|
||||||
|
{{ books.length }}个部分
|
||||||
|
</v-chip>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
@click.stop="() => deleteBookType('common', bookType)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-divider class="my-2" />
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<v-text-field
|
||||||
|
v-model="newAction"
|
||||||
|
label="添加操作"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
append-inner-icon="mdi-plus"
|
||||||
|
@click:append-inner="addAction"
|
||||||
|
@keyup.enter="addAction"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<v-list density="compact" border rounded>
|
||||||
|
<v-list-item
|
||||||
|
v-for="action in config.actions"
|
||||||
|
:key="action"
|
||||||
|
:title="action"
|
||||||
|
@click="openActionDialog(action)"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
@click.stop="removeAction(action)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</setting-group>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<!-- 编辑弹框 -->
|
||||||
|
<v-dialog v-model="dialog.show" max-width="600px">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h5 pa-4">
|
||||||
|
{{ dialog.title }}
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
v-model="dialog.editedItem.name"
|
||||||
|
:label="dialog.nameLabel"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
:rules="[v => !!v || '名称不能为空']"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" v-if="dialog.editedItem.type === 'subjectBook'">
|
||||||
|
<div class="text-subtitle-2 mb-2">所属科目</div>
|
||||||
|
<v-chip color="primary">{{ dialog.editedItem.subject }}</v-chip>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" v-if="['subjectBook', 'commonBook'].includes(dialog.editedItem.type)">
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-title class="text-subtitle-1 py-2">需完成部分</v-card-title>
|
||||||
|
<v-card-text class="pt-0">
|
||||||
|
<v-list density="compact" border rounded class="mb-2">
|
||||||
|
<v-list-item
|
||||||
|
v-for="(task, index) in dialog.editedItem.tasks"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-icon icon="mdi-checkbox-blank-circle-outline" size="small" class="mr-2" />
|
||||||
|
</template>
|
||||||
|
<v-text-field
|
||||||
|
v-model="dialog.editedItem.tasks[index]"
|
||||||
|
variant="plain"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-delete"
|
||||||
|
variant="text"
|
||||||
|
color="error"
|
||||||
|
size="small"
|
||||||
|
@click="removeTask(index)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
<v-text-field
|
||||||
|
v-model="newTask"
|
||||||
|
label="添加需完成部分"
|
||||||
|
variant="outlined"
|
||||||
|
density="comfortable"
|
||||||
|
append-inner-icon="mdi-plus"
|
||||||
|
@click:append-inner="addTask"
|
||||||
|
@keyup.enter="addTask"
|
||||||
|
class="mt-2"
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions class="pa-4">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
variant="elevated"
|
||||||
|
@click="saveDialog"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
variant="text"
|
||||||
|
@click="closeDialog"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- 底部保存提示 -->
|
||||||
|
<v-snackbar
|
||||||
|
v-model="showSnackbar"
|
||||||
|
:color="snackbarColor"
|
||||||
|
:timeout="3000"
|
||||||
|
>
|
||||||
|
{{ snackbarText }}
|
||||||
|
</v-snackbar>
|
||||||
|
</settings-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
import SettingsCard from '@/components/SettingsCard.vue';
|
||||||
|
import SettingGroup from '@/components/settings/SettingGroup.vue';
|
||||||
|
import dataProvider from "@/utils/dataProvider.js";
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG = {
|
||||||
|
subjects: {
|
||||||
|
"语文": {
|
||||||
|
books: {
|
||||||
|
"课本": ["第一单元", "第二单元"],
|
||||||
|
"练习册": ["第一章", "第二章"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"数学": {
|
||||||
|
books: {
|
||||||
|
"课本": ["第一章", "第二章"],
|
||||||
|
"习题册": ["基础练习", "提高练习"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"英语": {
|
||||||
|
books: {
|
||||||
|
"课本": ["Unit 1", "Unit 2"],
|
||||||
|
"练习册": ["Chapter 1", "Chapter 2"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
commonSubject: {
|
||||||
|
books: {
|
||||||
|
"试卷": ["单元测试", "期中测试", "期末测试"],
|
||||||
|
"假期作业": ["必做题", "选做题"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: ["写完", "下一课", "不交", "明天交"]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HomeworkTemplateCard',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
SettingsCard,
|
||||||
|
SettingGroup
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
config: reactive(JSON.parse(JSON.stringify(DEFAULT_CONFIG))),
|
||||||
|
originalConfig: null,
|
||||||
|
newSubject: '',
|
||||||
|
newCommonBook: '',
|
||||||
|
newAction: '',
|
||||||
|
newTask: '',
|
||||||
|
editedSubjects: {},
|
||||||
|
editedBookTypes: {},
|
||||||
|
newBookTypes: {},
|
||||||
|
newBooks: {},
|
||||||
|
showSnackbar: false,
|
||||||
|
snackbarText: '',
|
||||||
|
snackbarColor: 'success',
|
||||||
|
isNewConfig: true,
|
||||||
|
dialog: {
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
nameLabel: '',
|
||||||
|
editedItem: {
|
||||||
|
name: '',
|
||||||
|
type: '', // 'book', 'commonBook', 'action'
|
||||||
|
subject: '',
|
||||||
|
bookType: '',
|
||||||
|
originalName: '',
|
||||||
|
tasks: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
subjectList() {
|
||||||
|
return Object.keys(this.config.subjects);
|
||||||
|
},
|
||||||
|
hasChanges() {
|
||||||
|
if (this.isNewConfig) return true;
|
||||||
|
return this.originalConfig &&
|
||||||
|
JSON.stringify(this.config) !== JSON.stringify(this.originalConfig);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.loadConfig();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async loadConfig() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await dataProvider.loadData("classworks-config-homework-template");
|
||||||
|
if (response) {
|
||||||
|
// 数据存在且加载成功
|
||||||
|
const config = response;
|
||||||
|
Object.assign(this.config, config);
|
||||||
|
this.originalConfig = JSON.parse(JSON.stringify(config));
|
||||||
|
this.isNewConfig = false;
|
||||||
|
this.showMessage('配置已加载', 'success');
|
||||||
|
} else if (response.error?.code === 'NOT_FOUND') {
|
||||||
|
// 数据不存在,使用默认配置
|
||||||
|
this.showMessage('使用默认配置', 'info');
|
||||||
|
this.isNewConfig = true;
|
||||||
|
} else {
|
||||||
|
// 其他错误,继续使用当前配置
|
||||||
|
const errorMsg = response.error?.message || '加载失败';
|
||||||
|
this.showMessage(`加载失败: ${errorMsg},可继续编辑当前配置`, 'warning');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 发生错误,继续使用当前配置
|
||||||
|
console.error('Failed to load config:', error);
|
||||||
|
this.showMessage('加载失败,可继续编辑当前配置', 'warning');
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveConfig() {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await dataProvider.saveData("classworks-config-homework-template", this.config);
|
||||||
|
if (response) {
|
||||||
|
this.originalConfig = JSON.parse(JSON.stringify(this.config));
|
||||||
|
this.isNewConfig = false;
|
||||||
|
this.showMessage('配置已保存', 'success');
|
||||||
|
} else {
|
||||||
|
throw new Error(response || '保存失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save config:', error);
|
||||||
|
this.showMessage(`保存失败: ${error.message},请稍后重试`, 'error');
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
showMessage(text, color = 'success') {
|
||||||
|
this.snackbarText = text;
|
||||||
|
this.snackbarColor = color;
|
||||||
|
this.showSnackbar = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
addSubject() {
|
||||||
|
if (!this.newSubject) return;
|
||||||
|
if (!this.config.subjects[this.newSubject]) {
|
||||||
|
this.config.subjects[this.newSubject] = { books: {} };
|
||||||
|
}
|
||||||
|
this.newSubject = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSubject(oldSubject) {
|
||||||
|
const newSubject = this.editedSubjects[oldSubject];
|
||||||
|
if (newSubject && newSubject !== oldSubject) {
|
||||||
|
const subjectData = this.config.subjects[oldSubject];
|
||||||
|
this.config.subjects[newSubject] = subjectData;
|
||||||
|
delete this.config.subjects[oldSubject];
|
||||||
|
}
|
||||||
|
delete this.editedSubjects[oldSubject];
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteSubject(subject) {
|
||||||
|
delete this.config.subjects[subject];
|
||||||
|
},
|
||||||
|
|
||||||
|
addBookType(subject) {
|
||||||
|
const newType = this.newBookTypes[subject];
|
||||||
|
if (!newType) return;
|
||||||
|
|
||||||
|
if (!this.config.subjects[subject].books[newType]) {
|
||||||
|
this.config.subjects[subject].books[newType] = [];
|
||||||
|
}
|
||||||
|
this.newBookTypes[subject] = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
updateBookType(subject, oldType) {
|
||||||
|
const key = `${subject}-${oldType}`;
|
||||||
|
const newType = this.editedBookTypes[key];
|
||||||
|
if (newType && newType !== oldType) {
|
||||||
|
const books = this.config.subjects[subject].books[oldType];
|
||||||
|
this.config.subjects[subject].books[newType] = books;
|
||||||
|
delete this.config.subjects[subject].books[oldType];
|
||||||
|
}
|
||||||
|
delete this.editedBookTypes[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteBookType(subject, bookType) {
|
||||||
|
if (subject === 'common') {
|
||||||
|
delete this.config.commonSubject.books[bookType];
|
||||||
|
} else {
|
||||||
|
delete this.config.subjects[subject].books[bookType];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addBook(subject, bookType) {
|
||||||
|
const key = `${subject}-${bookType}`;
|
||||||
|
const newBook = this.newBooks[key];
|
||||||
|
if (!newBook) return;
|
||||||
|
|
||||||
|
if (!this.config.subjects[subject].books[bookType].includes(newBook)) {
|
||||||
|
this.config.subjects[subject].books[bookType].push(newBook);
|
||||||
|
}
|
||||||
|
this.newBooks[key] = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
removeBook(subject, bookType, book) {
|
||||||
|
const books = this.config.subjects[subject].books[bookType];
|
||||||
|
const index = books.indexOf(book);
|
||||||
|
if (index > -1) {
|
||||||
|
books.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommonBook() {
|
||||||
|
if (!this.newCommonBook) return;
|
||||||
|
if (!this.config.commonSubject.books[this.newCommonBook]) {
|
||||||
|
this.config.commonSubject.books[this.newCommonBook] = [];
|
||||||
|
}
|
||||||
|
this.newCommonBook = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
removeCommonBook(book) {
|
||||||
|
delete this.config.commonSubject.books[book];
|
||||||
|
},
|
||||||
|
|
||||||
|
addAction() {
|
||||||
|
if (!this.newAction) return;
|
||||||
|
if (!this.config.actions.includes(this.newAction)) {
|
||||||
|
this.config.actions.push(this.newAction);
|
||||||
|
}
|
||||||
|
this.newAction = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAction(action) {
|
||||||
|
const index = this.config.actions.indexOf(action);
|
||||||
|
if (index > -1) {
|
||||||
|
this.config.actions.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openBookDialog(subject, bookType, book) {
|
||||||
|
this.dialog.show = true;
|
||||||
|
this.dialog.title = '编辑需完成部分';
|
||||||
|
this.dialog.nameLabel = '部分名称';
|
||||||
|
this.dialog.editedItem = {
|
||||||
|
name: book,
|
||||||
|
type: 'book',
|
||||||
|
subject,
|
||||||
|
bookType,
|
||||||
|
originalName: book,
|
||||||
|
tasks: this.config.subjects[subject].books[bookType]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
openCommonBookDialog(book) {
|
||||||
|
this.dialog.show = true;
|
||||||
|
this.dialog.title = '编辑通用作业本';
|
||||||
|
this.dialog.nameLabel = '作业本名称';
|
||||||
|
this.dialog.editedItem = {
|
||||||
|
name: book,
|
||||||
|
type: 'commonBook',
|
||||||
|
originalName: book,
|
||||||
|
tasks: Array.isArray(this.config.commonSubject.books[book]) ? [...this.config.commonSubject.books[book]] : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
openActionDialog(action) {
|
||||||
|
this.dialog = {
|
||||||
|
show: true,
|
||||||
|
title: '编辑操作',
|
||||||
|
nameLabel: '操作名称',
|
||||||
|
editedItem: {
|
||||||
|
name: action,
|
||||||
|
type: 'action',
|
||||||
|
originalName: action,
|
||||||
|
tasks: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addTask() {
|
||||||
|
if (!this.newTask) return;
|
||||||
|
if (!this.dialog.editedItem.tasks) {
|
||||||
|
this.dialog.editedItem.tasks = [];
|
||||||
|
}
|
||||||
|
this.dialog.editedItem.tasks.push(this.newTask);
|
||||||
|
this.newTask = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
removeTask(index) {
|
||||||
|
this.dialog.editedItem.tasks.splice(index, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
openSubjectBookDialog(subject, bookType, books) {
|
||||||
|
this.dialog.show = true;
|
||||||
|
this.dialog.title = subject === 'common' ? '编辑通用作业本' : '编辑作业本';
|
||||||
|
this.dialog.nameLabel = '作业本名称';
|
||||||
|
this.dialog.editedItem = {
|
||||||
|
name: bookType,
|
||||||
|
type: 'subjectBook',
|
||||||
|
subject,
|
||||||
|
originalName: bookType,
|
||||||
|
tasks: Array.isArray(books) ? [...books] : []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
saveDialog() {
|
||||||
|
const { type, name, subject, originalName, tasks } = this.dialog.editedItem;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
this.showMessage('名称不能为空', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionIndex;
|
||||||
|
const targetBooks = subject === 'common'
|
||||||
|
? this.config.commonSubject.books
|
||||||
|
: subject ? this.config.subjects[subject].books : null;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'subjectBook':
|
||||||
|
if (targetBooks) {
|
||||||
|
if (originalName !== name) {
|
||||||
|
// 如果名称改变了,需要创建新的条目并删除旧的
|
||||||
|
targetBooks[name] = tasks || [];
|
||||||
|
delete targetBooks[originalName];
|
||||||
|
} else {
|
||||||
|
// 如果只改变了任务列表
|
||||||
|
targetBooks[name] = tasks || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'action':
|
||||||
|
actionIndex = this.config.actions.indexOf(originalName);
|
||||||
|
if (actionIndex > -1) {
|
||||||
|
this.config.actions[actionIndex] = name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
//this.showMessage('修改成功', 'success');
|
||||||
|
},
|
||||||
|
|
||||||
|
closeDialog() {
|
||||||
|
this.dialog = {
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
nameLabel: '',
|
||||||
|
editedItem: {
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
subject: '',
|
||||||
|
originalName: '',
|
||||||
|
tasks: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.newTask = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.v-card-text {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -241,27 +241,13 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-dialog
|
<homework-edit-dialog
|
||||||
v-model="state.dialogVisible"
|
v-model="state.dialogVisible"
|
||||||
width="500"
|
:title="state.dialogTitle"
|
||||||
@click:outside="handleClose"
|
:initial-content="state.textarea"
|
||||||
>
|
:auto-save="autoSave"
|
||||||
<v-card border>
|
@save="handleHomeworkSave"
|
||||||
<v-card-title>{{ state.dialogTitle }}</v-card-title>
|
/>
|
||||||
<v-card-subtitle>
|
|
||||||
{{ autoSave ? "喵?喵呜!" : "写完后点击上传谢谢喵" }}
|
|
||||||
</v-card-subtitle>
|
|
||||||
<v-card-text>
|
|
||||||
<v-textarea
|
|
||||||
ref="inputRef"
|
|
||||||
v-model="state.textarea"
|
|
||||||
auto-grow
|
|
||||||
placeholder="使用换行表示分条"
|
|
||||||
rows="5"
|
|
||||||
/>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
|
|
||||||
<v-snackbar v-model="state.snackbar" :timeout="2000">
|
<v-snackbar v-model="state.snackbar" :timeout="2000">
|
||||||
{{ state.snackbarText }}
|
{{ state.snackbarText }}
|
||||||
@ -622,6 +608,7 @@ import RandomPicker from "@/components/RandomPicker.vue";
|
|||||||
import NamespaceAccess from "@/components/NamespaceAccess.vue";
|
import NamespaceAccess from "@/components/NamespaceAccess.vue";
|
||||||
import FloatingToolbar from "@/components/FloatingToolbar.vue";
|
import FloatingToolbar from "@/components/FloatingToolbar.vue";
|
||||||
import FloatingICP from "@/components/FloatingICP.vue";
|
import FloatingICP from "@/components/FloatingICP.vue";
|
||||||
|
import HomeworkEditDialog from "@/components/HomeworkEditDialog.vue";
|
||||||
import dataProvider from "@/utils/dataProvider";
|
import dataProvider from "@/utils/dataProvider";
|
||||||
import {
|
import {
|
||||||
getSetting,
|
getSetting,
|
||||||
@ -645,6 +632,7 @@ export default {
|
|||||||
NamespaceAccess,
|
NamespaceAccess,
|
||||||
FloatingToolbar,
|
FloatingToolbar,
|
||||||
FloatingICP,
|
FloatingICP,
|
||||||
|
HomeworkEditDialog,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -1241,11 +1229,20 @@ export default {
|
|||||||
subject;
|
subject;
|
||||||
this.state.textarea = this.state.boardData.homework[subject].content;
|
this.state.textarea = this.state.boardData.homework[subject].content;
|
||||||
this.state.dialogVisible = true;
|
this.state.dialogVisible = true;
|
||||||
this.$nextTick(() => {
|
},
|
||||||
if (this.$refs.inputRef) {
|
|
||||||
this.$refs.inputRef.focus();
|
async handleHomeworkSave(content) {
|
||||||
}
|
if (!this.currentEditSubject) return;
|
||||||
});
|
|
||||||
|
this.state.boardData.homework[this.currentEditSubject] = {
|
||||||
|
content: content,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state.synced = false;
|
||||||
|
|
||||||
|
if (this.autoSave) {
|
||||||
|
await this.trySave(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
splitPoint(content) {
|
splitPoint(content) {
|
||||||
|
@ -156,6 +156,9 @@
|
|||||||
<v-tabs-window-item value="randomPicker">
|
<v-tabs-window-item value="randomPicker">
|
||||||
<random-picker-card border :is-mobile="isMobile" />
|
<random-picker-card border :is-mobile="isMobile" />
|
||||||
</v-tabs-window-item>
|
</v-tabs-window-item>
|
||||||
|
<v-tabs-window-item value="homework">
|
||||||
|
<homework-template-card border />
|
||||||
|
</v-tabs-window-item>
|
||||||
<v-tabs-window-item value="developer"
|
<v-tabs-window-item value="developer"
|
||||||
><settings-card border title="开发者选项" icon="mdi-developer-board">
|
><settings-card border title="开发者选项" icon="mdi-developer-board">
|
||||||
<v-list>
|
<v-list>
|
||||||
@ -234,6 +237,7 @@ import SettingsExplorer from "@/components/settings/SettingsExplorer.vue";
|
|||||||
import SettingsLinkGenerator from "@/components/SettingsLinkGenerator.vue";
|
import SettingsLinkGenerator from "@/components/SettingsLinkGenerator.vue";
|
||||||
import NamespaceSettingsCard from "@/components/settings/cards/NamespaceSettingsCard.vue";
|
import NamespaceSettingsCard from "@/components/settings/cards/NamespaceSettingsCard.vue";
|
||||||
import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue";
|
import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue";
|
||||||
|
import HomeworkTemplateCard from '@/components/settings/cards/HomeworkTemplateCard.vue';
|
||||||
export default {
|
export default {
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
components: {
|
components: {
|
||||||
@ -252,6 +256,7 @@ export default {
|
|||||||
SettingsLinkGenerator,
|
SettingsLinkGenerator,
|
||||||
NamespaceSettingsCard,
|
NamespaceSettingsCard,
|
||||||
RandomPickerCard,
|
RandomPickerCard,
|
||||||
|
HomeworkTemplateCard,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { mobile } = useDisplay();
|
const { mobile } = useDisplay();
|
||||||
@ -389,6 +394,11 @@ export default {
|
|||||||
icon: "mdi-dice-multiple",
|
icon: "mdi-dice-multiple",
|
||||||
value: "randomPicker",
|
value: "randomPicker",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "作业模板",
|
||||||
|
icon: "mdi-book-edit",
|
||||||
|
value: "homework",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "开发者",
|
title: "开发者",
|
||||||
icon: "mdi-developer-board",
|
icon: "mdi-developer-board",
|
||||||
|
@ -133,7 +133,7 @@ export const kvServerProvider = {
|
|||||||
if (error.response?.status === 404) {
|
if (error.response?.status === 404) {
|
||||||
return formatError("数据不存在", "NOT_FOUND");
|
return formatError("数据不存在", "NOT_FOUND");
|
||||||
}
|
}
|
||||||
|
console.log(error);
|
||||||
return formatError(
|
return formatError(
|
||||||
error.response?.data?.message || "服务器连接失败",
|
error.response?.data?.message || "服务器连接失败",
|
||||||
"NETWORK_ERROR"
|
"NETWORK_ERROR"
|
||||||
@ -150,6 +150,7 @@ export const kvServerProvider = {
|
|||||||
});
|
});
|
||||||
return formatResponse(true);
|
return formatResponse(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
return formatError(
|
return formatError(
|
||||||
error.response?.data?.message || "保存失败",
|
error.response?.data?.message || "保存失败",
|
||||||
"SAVE_ERROR"
|
"SAVE_ERROR"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user