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