Skip to content

Commit f5dab48

Browse files
committed
Add vuedraggable dependency for improved drag-and-drop functionality. Refactor index.vue to use subject names as keys and streamline subject management. Update settings.vue to include SubjectManagementCard for better subject configuration.
1 parent f2d8843 commit f5dab48

File tree

5 files changed

+571
-93
lines changed

5 files changed

+571
-93
lines changed

pnpm-workspace.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
onlyBuiltDependencies:
2+
- '@parcel/watcher'
3+
- esbuild
4+
- sharp
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
<template>
2+
<settings-card
3+
title="科目管理"
4+
icon="mdi-book-multiple"
5+
:loading="loading"
6+
border
7+
>
8+
<v-alert
9+
v-if="error"
10+
type="error"
11+
variant="tonal"
12+
closable
13+
class="mb-4"
14+
>
15+
{{ error }}
16+
</v-alert>
17+
18+
<div class="d-flex justify-space-between align-center mb-6">
19+
<div>
20+
<v-btn
21+
variant="text"
22+
color="primary"
23+
size="large"
24+
prepend-icon="mdi-refresh"
25+
:loading="loading"
26+
@click="loadConfig"
27+
class="mr-2"
28+
>
29+
重新加载
30+
</v-btn>
31+
32+
<v-btn
33+
color="success"
34+
size="large"
35+
prepend-icon="mdi-content-save"
36+
:loading="loading"
37+
@click="saveConfig"
38+
>
39+
保存
40+
</v-btn>
41+
<v-btn
42+
variant="text"
43+
prepend-icon="mdi-restore"
44+
:loading="loading"
45+
@click="resetToDefault"
46+
class="mr-2"
47+
>
48+
重置为默认
49+
</v-btn>
50+
</div>
51+
<v-chip
52+
v-if="hasChanges"
53+
color="warning"
54+
variant="elevated"
55+
>
56+
有未保存的更改
57+
</v-chip>
58+
</div>
59+
60+
<!-- 添加新科目 -->
61+
<v-card class="mb-4" variant="outlined">
62+
<v-card-text>
63+
<v-row>
64+
<v-col cols="12" sm="6">
65+
<v-text-field
66+
v-model="newSubjectName"
67+
label="科目名称"
68+
variant="outlined"
69+
density="comfortable"
70+
:rules="[v => !!v || '科目名称不能为空']"
71+
@keyup.enter="addSubject"
72+
append-inner-icon="mdi-plus"
73+
@click:append-inner="addSubject"
74+
/>
75+
</v-col>
76+
</v-row>
77+
</v-card-text>
78+
</v-card>
79+
80+
<!-- 科目列表 -->
81+
<v-card variant="outlined">
82+
<v-card-text class="pa-0">
83+
<v-list lines="one">
84+
<v-list-item
85+
v-for="(subject, index) in subjects"
86+
:key="subject.order"
87+
>
88+
<template v-slot:prepend>
89+
<div class="d-flex flex-column align-center mr-2">
90+
<v-btn
91+
icon="mdi-chevron-up"
92+
variant="text"
93+
size="small"
94+
:disabled="index === 0"
95+
@click="moveSubject(index, -1)"
96+
/>
97+
<v-btn
98+
icon="mdi-chevron-down"
99+
variant="text"
100+
size="small"
101+
:disabled="index === subjects.length - 1"
102+
@click="moveSubject(index, 1)"
103+
/>
104+
</div>
105+
</template>
106+
107+
<v-list-item-title>
108+
<v-text-field
109+
v-model="subject.name"
110+
variant="plain"
111+
density="compact"
112+
hide-details
113+
@blur="updateSubject(subject)"
114+
/>
115+
</v-list-item-title>
116+
117+
<template v-slot:append>
118+
<v-btn
119+
icon="mdi-delete"
120+
variant="text"
121+
color="error"
122+
size="small"
123+
@click="deleteSubject(subject)"
124+
/>
125+
</template>
126+
</v-list-item>
127+
</v-list>
128+
</v-card-text>
129+
</v-card>
130+
131+
<!-- 底部提示 -->
132+
<v-snackbar
133+
v-model="showSnackbar"
134+
:color="snackbarColor"
135+
:timeout="3000"
136+
>
137+
{{ snackbarText }}
138+
</v-snackbar>
139+
</settings-card>
140+
</template>
141+
142+
<script>
143+
import SettingsCard from '@/components/SettingsCard.vue';
144+
import dataProvider from "@/utils/dataProvider.js";
145+
146+
export default {
147+
name: 'SubjectManagementCard',
148+
149+
components: {
150+
SettingsCard
151+
},
152+
153+
data() {
154+
return {
155+
loading: false,
156+
error: null,
157+
subjects: [],
158+
originalSubjects: null,
159+
newSubjectName: '',
160+
showSnackbar: false,
161+
snackbarText: '',
162+
snackbarColor: 'success',
163+
defaultSubjects: [
164+
{ name: '语文', order: 0 },
165+
{ name: '数学', order: 1 },
166+
{ name: '英语', order: 2 },
167+
{ name: '物理', order: 3 },
168+
{ name: '化学', order: 4 },
169+
{ name: '生物', order: 5 },
170+
{ name: '政治', order: 6 },
171+
{ name: '历史', order: 7 },
172+
{ name: '地理', order: 8 },
173+
{ name: '其他', order: 9 }
174+
]
175+
};
176+
},
177+
178+
computed: {
179+
hasChanges() {
180+
return this.originalSubjects &&
181+
JSON.stringify(this.subjects) !== JSON.stringify(this.originalSubjects);
182+
}
183+
},
184+
185+
created() {
186+
this.loadConfig();
187+
},
188+
189+
methods: {
190+
async loadConfig() {
191+
this.loading = true;
192+
try {
193+
const response = await dataProvider.loadData("classworks-config-subject");
194+
if (response) {
195+
// 数据存在且加载成功
196+
this.subjects = response.map((subject, index) => ({
197+
name: subject.name,
198+
order: subject.order ?? index
199+
})).sort((a, b) => a.order - b.order);
200+
this.originalSubjects = JSON.parse(JSON.stringify(this.subjects));
201+
this.showMessage('配置已加载', 'success');
202+
} else {
203+
// 数据不存在,使用空数组
204+
this.subjects = [];
205+
this.originalSubjects = [];
206+
this.showMessage('使用默认配置', 'info');
207+
}
208+
} catch (error) {
209+
console.error('Failed to load config:', error);
210+
this.showMessage('加载失败,可继续编辑当前配置', 'warning');
211+
}
212+
this.loading = false;
213+
},
214+
215+
async saveConfig() {
216+
this.loading = true;
217+
try {
218+
const response = await dataProvider.saveData("classworks-config-subject", this.subjects);
219+
if (response) {
220+
this.originalSubjects = JSON.parse(JSON.stringify(this.subjects));
221+
this.showMessage('配置已保存', 'success');
222+
} else {
223+
throw new Error(response || '保存失败');
224+
}
225+
} catch (error) {
226+
console.error('Failed to save config:', error);
227+
this.showMessage(`保存失败: ${error.message},请稍后重试`, 'error');
228+
}
229+
this.loading = false;
230+
},
231+
232+
showMessage(text, color = 'success') {
233+
this.snackbarText = text;
234+
this.snackbarColor = color;
235+
this.showSnackbar = true;
236+
},
237+
238+
addSubject() {
239+
if (!this.newSubjectName) return;
240+
241+
const subject = {
242+
name: this.newSubjectName,
243+
order: this.subjects.length
244+
};
245+
246+
this.subjects.push(subject);
247+
this.newSubjectName = '';
248+
},
249+
250+
updateSubject(subject) {
251+
const index = this.subjects.findIndex(s => s.order === subject.order);
252+
if (index > -1) {
253+
this.subjects[index] = { ...subject };
254+
}
255+
},
256+
257+
deleteSubject(subject) {
258+
const index = this.subjects.findIndex(s => s.order === subject.order);
259+
if (index > -1) {
260+
this.subjects.splice(index, 1);
261+
// 更新剩余科目的顺序
262+
this.subjects.forEach((s, i) => {
263+
s.order = i;
264+
});
265+
}
266+
},
267+
268+
moveSubject(index, direction) {
269+
const newIndex = index + direction;
270+
if (newIndex >= 0 && newIndex < this.subjects.length) {
271+
// 交换位置
272+
const temp = this.subjects[index];
273+
this.subjects[index] = this.subjects[newIndex];
274+
this.subjects[newIndex] = temp;
275+
// 更新顺序
276+
this.subjects.forEach((subject, i) => {
277+
subject.order = i;
278+
});
279+
}
280+
},
281+
282+
resetToDefault() {
283+
this.subjects = JSON.parse(JSON.stringify(this.defaultSubjects));
284+
this.showMessage('已重置为默认科目列表', 'info');
285+
}
286+
}
287+
};
288+
</script>
289+
290+
<style scoped>
291+
.v-list-item {
292+
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
293+
}
294+
.v-list-item:last-child {
295+
border-bottom: none;
296+
}
297+
</style>

0 commit comments

Comments
 (0)