1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-07-03 01:39:22 +00:00
This commit is contained in:
SunWuyuan 2025-03-09 14:15:54 +08:00
parent d214f172f6
commit 08e95a3efc
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
2 changed files with 89 additions and 12 deletions

View File

@ -1,11 +1,23 @@
<template> <template>
<v-card border> <v-card
border
:color="hasChanges ? 'warning-subtle' : undefined"
:class="{ 'unsaved-changes': hasChanges }"
>
<v-card-item> <v-card-item>
<template #prepend> <template #prepend>
<v-icon icon="mdi-account-group" size="large" class="mr-2" /> <v-icon icon="mdi-account-group" size="large" class="mr-2" />
</template> </template>
<v-card-title class="text-h6">学生列表</v-card-title> <v-card-title class="text-h6">学生列表</v-card-title>
<template #append> <template #append>
<v-chip
v-if="hasChanges"
color="warning"
size="small"
class="mr-2"
>
未保存
</v-chip>
<v-btn <v-btn
:color="modelValue.advanced ? 'primary' : undefined" :color="modelValue.advanced ? 'primary' : undefined"
variant="text" variant="text"
@ -163,7 +175,7 @@
<!-- 高级编辑模式 --> <!-- 高级编辑模式 -->
<div v-else class="pt-2"> <div v-else class="pt-2">
<v-textarea <v-textarea
v-model="modelValue.text" v-model="text"
label="批量编辑学生列表" label="批量编辑学生列表"
placeholder="每行输入一个学生姓名" placeholder="每行输入一个学生姓名"
hint="使用文本编辑模式批量编辑学生名单,保存时会自动去除空行" hint="使用文本编辑模式批量编辑学生名单,保存时会自动去除空行"
@ -182,8 +194,8 @@
prepend-icon="mdi-content-save" prepend-icon="mdi-content-save"
size="large" size="large"
:loading="loading" :loading="loading"
:disabled="loading" :disabled="loading || !hasChanges"
@click="$emit('save')" @click="handleSave"
> >
保存名单 保存名单
</v-btn> </v-btn>
@ -193,7 +205,7 @@
prepend-icon="mdi-refresh" prepend-icon="mdi-refresh"
size="large" size="large"
:loading="loading" :loading="loading"
:disabled="loading" :disabled="loading || !hasChanges"
@click="$emit('reload')" @click="$emit('reload')"
> >
重载名单 重载名单
@ -219,19 +231,60 @@ export default {
}, },
loading: Boolean, loading: Boolean,
error: String, error: String,
isMobile: Boolean isMobile: Boolean,
originalList: {
type: Array,
default: () => []
}
}, },
data() { data() {
return { return {
newStudent: '', newStudent: '',
editingIndex: -1, editingIndex: -1,
editingName: '' editingName: '',
internalOriginalList: [] //
}
},
created() {
//
this.internalOriginalList = [...this.originalList];
},
watch: {
originalList: {
handler(newList) {
this.internalOriginalList = [...newList];
},
immediate: true
} }
}, },
emits: ['update:modelValue', 'save', 'reload'], emits: ['update:modelValue', 'save', 'reload'],
computed: {
text: {
get() {
return this.modelValue.text;
},
set(value) {
this.handleTextInput(value);
}
},
hasChanges() {
const currentList = this.modelValue.list;
const originalList = this.internalOriginalList;
if (currentList.length !== originalList.length) {
return true;
}
// 使 JSON
return JSON.stringify(currentList) !== JSON.stringify(originalList);
}
},
methods: { methods: {
toggleAdvanced() { toggleAdvanced() {
const advanced = !this.modelValue.advanced; const advanced = !this.modelValue.advanced;
@ -321,6 +374,19 @@ export default {
} }
this.editingIndex = -1; this.editingIndex = -1;
this.editingName = ''; this.editingName = '';
},
handleClick(index, student) {
//
if (this.isMobile) {
this.startEdit(index, student);
}
},
async handleSave() {
await this.$emit('save');
//
this.internalOriginalList = [...this.modelValue.list];
} }
} }
} }
@ -336,6 +402,17 @@ export default {
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
} }
.unsaved-changes {
animation: subtle-pulse 2s infinite;
border: 2px solid rgb(var(--v-theme-warning));
}
@keyframes subtle-pulse {
0% { border-color: rgba(var(--v-theme-warning), 1); }
50% { border-color: rgba(var(--v-theme-warning), 0.5); }
100% { border-color: rgba(var(--v-theme-warning), 1); }
}
@media (max-width: 600px) { @media (max-width: 600px) {
.action-buttons { .action-buttons {
opacity: 1; opacity: 1;

View File

@ -40,7 +40,7 @@ const providers = {
return formatResponse(JSON.parse(rawData)); return formatResponse(JSON.parse(rawData));
} catch (error) { } catch (error) {
return formatError('读取本地数据失败'); return formatError('读取本地数据失败'+error);
} }
}, },
@ -57,7 +57,7 @@ const providers = {
localStorage.setItem(storageKey, JSON.stringify(data)); localStorage.setItem(storageKey, JSON.stringify(data));
return formatResponse(null, '保存成功'); return formatResponse(null, '保存成功');
} catch (error) { } catch (error) {
return formatError('保存本地数据失败'); return formatError('保存本地数据失败:'+error);
} }
}, },
@ -80,7 +80,7 @@ const providers = {
return formatResponse(JSON.parse(rawData)); return formatResponse(JSON.parse(rawData));
} catch (error) { } catch (error) {
return formatError('读取本地配置失败'); return formatError('读取本地配置失败:'+error);
} }
}, },
@ -95,7 +95,7 @@ const providers = {
localStorage.setItem(storageKey, JSON.stringify(config)); localStorage.setItem(storageKey, JSON.stringify(config));
return formatResponse(null, '保存成功'); return formatResponse(null, '保存成功');
} catch (error) { } catch (error) {
return formatError('保存本地配置失败'); return formatError('保存本地配置失败:'+error);
} }
} }
}, },
@ -145,7 +145,7 @@ const providers = {
async saveConfig(key, config) { async saveConfig(key, config) {
try { try {
const res = await axios.post(`${key}/config`, config); const res = await axios.put(`${key}/config`, config);
if (res.data?.status === false) { if (res.data?.status === false) {
return formatError(res.data.msg || '保存失败', 'SAVE_ERROR'); return formatError(res.data.msg || '保存失败', 'SAVE_ERROR');
} }