mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-12-07 21:13:11 +00:00
691 lines
20 KiB
Vue
691 lines
20 KiB
Vue
<template>
|
|
<settings-card
|
|
:loading="loading"
|
|
border
|
|
icon="mdi-book-edit"
|
|
title="作业模板配置"
|
|
>
|
|
<!-- 顶部操作按钮 -->
|
|
<v-alert
|
|
v-if="error"
|
|
class="mb-4"
|
|
closable
|
|
type="error"
|
|
variant="tonal"
|
|
>
|
|
{{ error }}
|
|
</v-alert>
|
|
|
|
<div class="d-flex justify-space-between align-center mb-6">
|
|
<div>
|
|
<v-btn
|
|
:loading="loading"
|
|
class="mr-2"
|
|
color="primary"
|
|
prepend-icon="mdi-refresh"
|
|
size="large"
|
|
@click="loadConfig"
|
|
>
|
|
重新加载配置
|
|
</v-btn>
|
|
<v-btn
|
|
:loading="loading"
|
|
color="success"
|
|
prepend-icon="mdi-content-save"
|
|
size="large"
|
|
@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 border icon="mdi-book" title="科目配置">
|
|
<v-list>
|
|
<v-list-item>
|
|
<v-text-field
|
|
v-model="newSubject"
|
|
append-inner-icon="mdi-plus"
|
|
density="comfortable"
|
|
label="添加新科目"
|
|
variant="outlined"
|
|
@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"
|
|
hide-details
|
|
variant="plain"
|
|
@blur="updateSubject(subject)"
|
|
/>
|
|
<v-spacer/>
|
|
<v-btn
|
|
color="error"
|
|
icon="mdi-delete"
|
|
size="small"
|
|
variant="text"
|
|
@click="deleteSubject(subject)"
|
|
/>
|
|
</v-card-title>
|
|
<v-card-text>
|
|
<v-text-field
|
|
v-model="newBookTypes[subject]"
|
|
append-inner-icon="mdi-plus"
|
|
class="mb-2"
|
|
density="comfortable"
|
|
label="添加作业本名称"
|
|
variant="outlined"
|
|
@click:append-inner="() => addBookType(subject)"
|
|
@keyup.enter="() => addBookType(subject)"
|
|
/>
|
|
|
|
<v-list border density="compact" 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 class="mr-2" icon="mdi-book-open-variant"/>
|
|
</template>
|
|
<template v-slot:append>
|
|
<v-chip
|
|
class="mr-2"
|
|
color="info"
|
|
size="small"
|
|
>
|
|
{{ books.length }}个部分
|
|
</v-chip>
|
|
<v-btn
|
|
color="error"
|
|
icon="mdi-delete"
|
|
size="small"
|
|
variant="text"
|
|
@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 border icon="mdi-cog" title="通用配置">
|
|
<v-list>
|
|
<v-list-item>
|
|
<v-text-field
|
|
v-model="newCommonBook"
|
|
append-inner-icon="mdi-plus"
|
|
density="comfortable"
|
|
label="添加作业本名称"
|
|
variant="outlined"
|
|
@click:append-inner="addCommonBook"
|
|
@keyup.enter="addCommonBook"
|
|
/>
|
|
</v-list-item>
|
|
|
|
<v-list-item>
|
|
<v-list border density="compact" 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 class="mr-2" icon="mdi-book-multiple"/>
|
|
</template>
|
|
<template v-slot:append>
|
|
<v-chip
|
|
class="mr-2"
|
|
color="info"
|
|
size="small"
|
|
>
|
|
{{ books.length }}个部分
|
|
</v-chip>
|
|
<v-btn
|
|
color="error"
|
|
icon="mdi-delete"
|
|
size="small"
|
|
variant="text"
|
|
@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"
|
|
append-inner-icon="mdi-plus"
|
|
density="comfortable"
|
|
label="添加操作"
|
|
variant="outlined"
|
|
@click:append-inner="addAction"
|
|
@keyup.enter="addAction"
|
|
/>
|
|
</v-list-item>
|
|
|
|
<v-list-item>
|
|
<v-list border density="compact" rounded>
|
|
<v-list-item
|
|
v-for="action in config.actions"
|
|
:key="action"
|
|
:title="action"
|
|
@click="openActionDialog(action)"
|
|
>
|
|
<template v-slot:append>
|
|
<v-btn
|
|
color="error"
|
|
icon="mdi-delete"
|
|
size="small"
|
|
variant="text"
|
|
@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"
|
|
:rules="[v => !!v || '名称不能为空']"
|
|
density="comfortable"
|
|
variant="outlined"
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col v-if="dialog.editedItem.type === 'subjectBook'" cols="12">
|
|
<div class="text-subtitle-2 mb-2">所属科目</div>
|
|
<v-chip color="primary">{{ dialog.editedItem.subject }}</v-chip>
|
|
</v-col>
|
|
|
|
<v-col v-if="['subjectBook', 'commonBook'].includes(dialog.editedItem.type)" cols="12">
|
|
<v-card variant="outlined">
|
|
<v-card-title class="text-subtitle-1 py-2">需完成部分</v-card-title>
|
|
<v-card-text class="pt-0">
|
|
<v-list border class="mb-2" density="compact" rounded>
|
|
<v-list-item
|
|
v-for="(task, index) in dialog.editedItem.tasks"
|
|
:key="index"
|
|
>
|
|
<template v-slot:prepend>
|
|
<v-icon class="mr-2" icon="mdi-checkbox-blank-circle-outline" size="small"/>
|
|
</template>
|
|
<v-text-field
|
|
v-model="dialog.editedItem.tasks[index]"
|
|
density="compact"
|
|
hide-details
|
|
variant="plain"
|
|
/>
|
|
<template v-slot:append>
|
|
<v-btn
|
|
color="error"
|
|
icon="mdi-delete"
|
|
size="small"
|
|
variant="text"
|
|
@click="removeTask(index)"
|
|
/>
|
|
</template>
|
|
</v-list-item>
|
|
</v-list>
|
|
<v-text-field
|
|
v-model="newTask"
|
|
append-inner-icon="mdi-plus"
|
|
class="mt-2"
|
|
density="comfortable"
|
|
label="添加需完成部分"
|
|
variant="outlined"
|
|
@click:append-inner="addTask"
|
|
@keyup.enter="addTask"
|
|
/>
|
|
</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>
|