330 lines
7.7 KiB
Vue
330 lines
7.7 KiB
Vue
|
|
<template>
|
||
|
|
<div class="settings-view">
|
||
|
|
<div class="page-header">
|
||
|
|
<h1><SettingOutlined /> 系统设置</h1>
|
||
|
|
<p>配置学校基本信息和通知偏好</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<a-spin :spinning="loading">
|
||
|
|
<div class="settings-content">
|
||
|
|
<!-- 基本信息 -->
|
||
|
|
<div class="settings-card">
|
||
|
|
<div class="card-header">
|
||
|
|
<span class="card-icon"><HomeOutlined /></span>
|
||
|
|
<h3>基本信息</h3>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
<a-form
|
||
|
|
:model="formData"
|
||
|
|
:label-col="{ span: 4 }"
|
||
|
|
:wrapper-col="{ span: 16 }"
|
||
|
|
>
|
||
|
|
<a-form-item label="学校名称">
|
||
|
|
<a-input v-model:value="formData.schoolName" placeholder="请输入学校名称" />
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="学校Logo">
|
||
|
|
<div class="logo-upload">
|
||
|
|
<div class="logo-preview" v-if="formData.schoolLogo">
|
||
|
|
<img :src="formData.schoolLogo" alt="Logo" />
|
||
|
|
<div class="logo-actions">
|
||
|
|
<a-button type="link" size="small" @click="formData.schoolLogo = ''">
|
||
|
|
<DeleteOutlined /> 删除
|
||
|
|
</a-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<a-upload
|
||
|
|
v-else
|
||
|
|
:show-upload-list="false"
|
||
|
|
:before-upload="handleLogoUpload"
|
||
|
|
accept="image/*"
|
||
|
|
>
|
||
|
|
<div class="upload-placeholder">
|
||
|
|
<PlusOutlined />
|
||
|
|
<span>上传Logo</span>
|
||
|
|
</div>
|
||
|
|
</a-upload>
|
||
|
|
</div>
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="学校地址">
|
||
|
|
<a-textarea
|
||
|
|
v-model:value="formData.address"
|
||
|
|
placeholder="请输入学校地址"
|
||
|
|
:rows="2"
|
||
|
|
/>
|
||
|
|
</a-form-item>
|
||
|
|
</a-form>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 通知设置 -->
|
||
|
|
<div class="settings-card">
|
||
|
|
<div class="card-header">
|
||
|
|
<span class="card-icon"><BellOutlined /></span>
|
||
|
|
<h3>通知设置</h3>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
<a-form
|
||
|
|
:label-col="{ span: 8 }"
|
||
|
|
:wrapper-col="{ span: 12 }"
|
||
|
|
>
|
||
|
|
<a-form-item label="课程完成通知">
|
||
|
|
<a-switch
|
||
|
|
v-model:checked="formData.notifyOnLesson"
|
||
|
|
checked-children="开"
|
||
|
|
un-checked-children="关"
|
||
|
|
/>
|
||
|
|
<span class="switch-hint">当教师完成一次授课后发送通知</span>
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="任务提醒通知">
|
||
|
|
<a-switch
|
||
|
|
v-model:checked="formData.notifyOnTask"
|
||
|
|
checked-children="开"
|
||
|
|
un-checked-children="关"
|
||
|
|
/>
|
||
|
|
<span class="switch-hint">当有新的阅读任务时发送通知</span>
|
||
|
|
</a-form-item>
|
||
|
|
<a-form-item label="成长档案通知">
|
||
|
|
<a-switch
|
||
|
|
v-model:checked="formData.notifyOnGrowth"
|
||
|
|
checked-children="开"
|
||
|
|
un-checked-children="关"
|
||
|
|
/>
|
||
|
|
<span class="switch-hint">当更新学生成长档案时发送通知</span>
|
||
|
|
</a-form-item>
|
||
|
|
</a-form>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 保存按钮 -->
|
||
|
|
<div class="action-bar">
|
||
|
|
<a-button type="primary" size="large" :loading="saving" @click="handleSave">
|
||
|
|
<SaveOutlined /> 保存设置
|
||
|
|
</a-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</a-spin>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
import { ref, onMounted } from 'vue';
|
||
|
|
import { message } from 'ant-design-vue';
|
||
|
|
import {
|
||
|
|
SettingOutlined,
|
||
|
|
HomeOutlined,
|
||
|
|
BellOutlined,
|
||
|
|
SaveOutlined,
|
||
|
|
PlusOutlined,
|
||
|
|
DeleteOutlined,
|
||
|
|
} from '@ant-design/icons-vue';
|
||
|
|
import { getSettings, updateSettings, type SystemSettings, type UpdateSettingsDto } from '@/api/school';
|
||
|
|
import { fileApi } from '@/api/file';
|
||
|
|
|
||
|
|
const loading = ref(false);
|
||
|
|
const saving = ref(false);
|
||
|
|
|
||
|
|
const formData = ref<UpdateSettingsDto>({
|
||
|
|
schoolName: '',
|
||
|
|
schoolLogo: '',
|
||
|
|
address: '',
|
||
|
|
notifyOnLesson: true,
|
||
|
|
notifyOnTask: true,
|
||
|
|
notifyOnGrowth: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
const loadSettings = async () => {
|
||
|
|
loading.value = true;
|
||
|
|
try {
|
||
|
|
const data = await getSettings();
|
||
|
|
formData.value = {
|
||
|
|
schoolName: data.schoolName || '',
|
||
|
|
schoolLogo: data.schoolLogo || '',
|
||
|
|
address: data.address || '',
|
||
|
|
notifyOnLesson: data.notifyOnLesson,
|
||
|
|
notifyOnTask: data.notifyOnTask,
|
||
|
|
notifyOnGrowth: data.notifyOnGrowth,
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to load settings:', error);
|
||
|
|
message.error('加载设置失败');
|
||
|
|
} finally {
|
||
|
|
loading.value = false;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleLogoUpload = async (file: File) => {
|
||
|
|
try {
|
||
|
|
const result = await fileApi.uploadFile(file, 'cover');
|
||
|
|
formData.value.schoolLogo = result.filePath;
|
||
|
|
message.success('Logo上传成功');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to upload logo:', error);
|
||
|
|
message.error('Logo上传失败');
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSave = async () => {
|
||
|
|
saving.value = true;
|
||
|
|
try {
|
||
|
|
await updateSettings(formData.value);
|
||
|
|
message.success('设置保存成功');
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to save settings:', error);
|
||
|
|
message.error('保存设置失败');
|
||
|
|
} finally {
|
||
|
|
saving.value = false;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
loadSettings();
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.settings-view {
|
||
|
|
padding: 0;
|
||
|
|
min-height: 100vh;
|
||
|
|
background: linear-gradient(180deg, #FFF8F0 0%, #FFFFFF 100%);
|
||
|
|
}
|
||
|
|
|
||
|
|
.page-header {
|
||
|
|
margin-bottom: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.page-header h1 {
|
||
|
|
font-size: 24px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #2D3436;
|
||
|
|
margin: 0 0 8px 0;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.page-header p {
|
||
|
|
font-size: 14px;
|
||
|
|
color: #636E72;
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.settings-content {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.settings-card {
|
||
|
|
background: white;
|
||
|
|
border-radius: 16px;
|
||
|
|
overflow: hidden;
|
||
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-header {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 12px;
|
||
|
|
padding: 20px 24px;
|
||
|
|
border-bottom: 1px solid #F5F5F5;
|
||
|
|
background: #FAFAFA;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-icon {
|
||
|
|
font-size: 20px;
|
||
|
|
color: #FF8C42;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-header h3 {
|
||
|
|
margin: 0;
|
||
|
|
font-size: 16px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #2D3436;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-body {
|
||
|
|
padding: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.logo-upload {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.logo-preview {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.logo-preview img {
|
||
|
|
width: 80px;
|
||
|
|
height: 80px;
|
||
|
|
object-fit: contain;
|
||
|
|
border-radius: 8px;
|
||
|
|
border: 1px solid #E0E0E0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.upload-placeholder {
|
||
|
|
width: 80px;
|
||
|
|
height: 80px;
|
||
|
|
border: 2px dashed #D9D9D9;
|
||
|
|
border-radius: 8px;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all 0.3s;
|
||
|
|
color: #636E72;
|
||
|
|
}
|
||
|
|
|
||
|
|
.upload-placeholder:hover {
|
||
|
|
border-color: #FF8C42;
|
||
|
|
color: #FF8C42;
|
||
|
|
}
|
||
|
|
|
||
|
|
.upload-placeholder span {
|
||
|
|
font-size: 12px;
|
||
|
|
margin-top: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.switch-hint {
|
||
|
|
margin-left: 12px;
|
||
|
|
color: #636E72;
|
||
|
|
font-size: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.action-bar {
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
padding: 24px;
|
||
|
|
background: white;
|
||
|
|
border-radius: 16px;
|
||
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 768px) {
|
||
|
|
.card-body {
|
||
|
|
padding: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.ant-form-item-label) {
|
||
|
|
padding-bottom: 8px !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.ant-form-item) {
|
||
|
|
margin-bottom: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.switch-hint {
|
||
|
|
display: block;
|
||
|
|
margin-left: 0;
|
||
|
|
margin-top: 4px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|