feat: Add attendance management dialog and sidebar components
- Implemented AttendanceManagementDialog.vue for managing student attendance with search and filter functionalities. - Created AttendanceSidebar.vue to display attendance statistics and lists of students by status. - Introduced HomeActions.vue for various actions including upload and random picker. - Developed HomeworkGrid.vue to display homework and attendance cards in a masonry layout. - Added glow effect styles in glow.scss for enhanced UI interactions. - Updated index.scss to include glow styles.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="d-flex flex-wrap align-center mt-4">
|
||||
<v-btn
|
||||
v-if="!synced"
|
||||
:loading="loadingUpload"
|
||||
class="ml-2"
|
||||
color="error"
|
||||
size="large"
|
||||
@click="$emit('upload')"
|
||||
>
|
||||
上传
|
||||
</v-btn>
|
||||
<v-btn v-else color="success" size="large" @click="$emit('show-sync-message')">
|
||||
同步完成
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showRandomPickerButton"
|
||||
append-icon="mdi-dice-multiple"
|
||||
class="ml-2"
|
||||
color="amber"
|
||||
prepend-icon="mdi-account-question"
|
||||
size="large"
|
||||
@click="$emit('open-random-picker')"
|
||||
>
|
||||
随机点名
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showExamScheduleButton"
|
||||
class="ml-2"
|
||||
color="green"
|
||||
prepend-icon="mdi-calendar-check"
|
||||
size="large"
|
||||
@click="$router.push('/examschedule')"
|
||||
>
|
||||
考试看板
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showListCardButton"
|
||||
class="ml-2"
|
||||
color="primary-darken-1"
|
||||
prepend-icon="mdi-list-box"
|
||||
size="large"
|
||||
@click="$router.push('/list')"
|
||||
>
|
||||
列表
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showFullscreenButton"
|
||||
:color="isFullscreen ? 'blue-grey' : 'blue'"
|
||||
:prepend-icon="
|
||||
isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen'
|
||||
"
|
||||
class="ml-2"
|
||||
size="large"
|
||||
@click="$emit('toggle-fullscreen')"
|
||||
>
|
||||
{{ isFullscreen ? "退出全屏" : "全屏显示" }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showTestCardButton"
|
||||
class="ml-2"
|
||||
color="purple"
|
||||
prepend-icon="mdi-test-tube"
|
||||
size="large"
|
||||
@click="$emit('add-test-card')"
|
||||
>
|
||||
添加测试卡片
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-card
|
||||
v-if="showAntiScreenBurnCard"
|
||||
border
|
||||
class="mt-4 anti-burn-card"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<v-card-title class="text-subtitle-1">
|
||||
<v-icon icon="mdi-shield-check" size="small" start />
|
||||
屏幕保护技术已启用
|
||||
</v-card-title>
|
||||
<v-card-text class="text-body-2">
|
||||
<p>
|
||||
为防止OLED/LCD屏幕烧屏,界面元素会定期微调位置。
|
||||
</p>
|
||||
<p class="text-caption text-grey">
|
||||
此功能不会影响正常使用,仅在长时间静止显示时生效。
|
||||
</p>
|
||||
<p class="text-caption text-grey">
|
||||
建议在放学后关闭显示器以节约能源。
|
||||
</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "HomeActions",
|
||||
props: {
|
||||
synced: Boolean,
|
||||
loadingUpload: Boolean,
|
||||
showRandomPickerButton: Boolean,
|
||||
showExamScheduleButton: Boolean,
|
||||
showListCardButton: Boolean,
|
||||
showFullscreenButton: Boolean,
|
||||
isFullscreen: Boolean,
|
||||
showAntiScreenBurnCard: Boolean,
|
||||
showTestCardButton: Boolean,
|
||||
},
|
||||
emits: [
|
||||
"upload",
|
||||
"show-sync-message",
|
||||
"open-random-picker",
|
||||
"toggle-fullscreen",
|
||||
"add-test-card",
|
||||
],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div ref="gridContainer" class="grid-masonry">
|
||||
<TransitionGroup name="grid">
|
||||
<div
|
||||
v-for="item in sortedItems"
|
||||
:key="item.key"
|
||||
:style="{
|
||||
'grid-row-end': `span ${item.rowSpan}`,
|
||||
order: item.order,
|
||||
}"
|
||||
class="grid-item"
|
||||
>
|
||||
<!-- 出勤卡片 -->
|
||||
<v-card
|
||||
v-if="item.type === 'attendance'"
|
||||
:class="{ 'glow-highlight': highlightedCards[item.key] }"
|
||||
border
|
||||
class="glow-track"
|
||||
height="100%"
|
||||
@click="$emit('open-attendance')"
|
||||
@mousemove="handleMouseMove"
|
||||
@touchmove="handleTouchMove"
|
||||
>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon class="mr-2" color="primary" icon="mdi-account-group" />
|
||||
出勤统计
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span>应到/实到</span>
|
||||
<span class="text-h6">
|
||||
{{ item.data.total - item.data.exclude.length }}/{{
|
||||
item.data.total -
|
||||
item.data.absent.length -
|
||||
item.data.late.length -
|
||||
item.data.exclude.length
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<v-divider class="mb-2" />
|
||||
|
||||
<div v-if="item.data.absent.length > 0" class="mb-2">
|
||||
<div class="text-error text-caption mb-1">请假 ({{ item.data.absent.length }})</div>
|
||||
<div class="d-flex flex-wrap" style="gap: 4px">
|
||||
<v-chip v-for="name in item.data.absent" :key="name" color="error" size="x-small" variant="flat">
|
||||
{{ name }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.data.late.length > 0" class="mb-2">
|
||||
<div class="text-warning text-caption mb-1">迟到 ({{ item.data.late.length }})</div>
|
||||
<div class="d-flex flex-wrap" style="gap: 4px">
|
||||
<v-chip v-for="name in item.data.late" :key="name" color="warning" size="x-small" variant="flat">
|
||||
{{ name }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.data.exclude.length > 0" class="mb-2">
|
||||
<div class="text-grey text-caption mb-1">不参与 ({{ item.data.exclude.length }})</div>
|
||||
<div class="d-flex flex-wrap" style="gap: 4px">
|
||||
<v-chip v-for="name in item.data.exclude" :key="name" color="grey" size="x-small" variant="flat">
|
||||
{{ name }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
item.data.absent.length === 0 &&
|
||||
item.data.late.length === 0 &&
|
||||
item.data.exclude.length === 0
|
||||
"
|
||||
class="text-success text-center mt-2"
|
||||
>
|
||||
全勤
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 自定义/测试卡片 -->
|
||||
<v-card
|
||||
v-else-if="item.type === 'custom'"
|
||||
:class="{ 'glow-highlight': highlightedCards[item.key] }"
|
||||
border
|
||||
class="glow-track"
|
||||
height="100%"
|
||||
@click="!isEditingDisabled && $emit('open-dialog', item.key)"
|
||||
@mousemove="handleMouseMove"
|
||||
@touchmove="handleTouchMove"
|
||||
>
|
||||
<v-card-title class="text-primary">
|
||||
<v-icon class="mr-2" icon="mdi-card-text-outline" size="small" />
|
||||
{{ item.name }}
|
||||
</v-card-title>
|
||||
<v-card-text :style="contentStyle">
|
||||
{{ item.content }}
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 普通作业卡片 -->
|
||||
<v-card
|
||||
v-else
|
||||
:class="{ 'glow-highlight': highlightedCards[item.key] }"
|
||||
border
|
||||
class="glow-track"
|
||||
height="100%"
|
||||
@click="!isEditingDisabled && $emit('open-dialog', item.key)"
|
||||
@mousemove="handleMouseMove"
|
||||
@touchmove="handleTouchMove"
|
||||
>
|
||||
<v-card-title>{{ item.name }}</v-card-title>
|
||||
<v-card-text :style="contentStyle">
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="text in splitPoint(item.content)"
|
||||
:key="text"
|
||||
>
|
||||
{{ text }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<!-- 单独显示空科目 -->
|
||||
<div class="empty-subjects mt-4">
|
||||
<template v-if="emptySubjectDisplay === 'button'">
|
||||
<v-btn-group divided variant="tonal">
|
||||
<v-btn
|
||||
v-for="subject in unusedSubjects"
|
||||
:key="subject.name"
|
||||
:disabled="isEditingDisabled"
|
||||
@click="$emit('open-dialog', subject.name)"
|
||||
>
|
||||
<v-icon start> mdi-plus</v-icon>
|
||||
{{ subject.name }}
|
||||
</v-btn>
|
||||
</v-btn-group>
|
||||
</template>
|
||||
<div v-else class="empty-subjects-grid">
|
||||
<TransitionGroup name="v-list">
|
||||
<v-card
|
||||
v-for="subject in unusedSubjects"
|
||||
:key="subject.name"
|
||||
:disabled="isEditingDisabled"
|
||||
border
|
||||
class="empty-subject-card"
|
||||
@click="$emit('open-dialog', subject.name)"
|
||||
>
|
||||
<v-card-title class="text-subtitle-1">
|
||||
{{ subject.name }}
|
||||
</v-card-title>
|
||||
<v-card-text class="text-center">
|
||||
<v-icon color="grey" size="small"> mdi-plus</v-icon>
|
||||
<div class="text-caption text-grey">点击添加作业</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "HomeworkGrid",
|
||||
props: {
|
||||
sortedItems: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
unusedSubjects: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
emptySubjectDisplay: {
|
||||
type: String,
|
||||
default: "button",
|
||||
},
|
||||
isEditingDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
contentStyle: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
highlightedCards: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ["open-dialog", "open-attendance"],
|
||||
methods: {
|
||||
splitPoint(content) {
|
||||
return content.split("\n").filter((text) => text.trim());
|
||||
},
|
||||
handleMouseMove(e) {
|
||||
const card = e.currentTarget;
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
const y = ((e.clientY - rect.top) / rect.height) * 100;
|
||||
card.style.setProperty("--x", `${x}%`);
|
||||
card.style.setProperty("--y", `${y}%`);
|
||||
},
|
||||
handleTouchMove(e) {
|
||||
if (e.touches.length === 1) {
|
||||
const touch = e.touches[0];
|
||||
const card = e.currentTarget;
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = ((touch.clientX - rect.left) / rect.width) * 100;
|
||||
const y = ((touch.clientY - rect.top) / rect.height) * 100;
|
||||
card.style.setProperty("--x", `${x}%`);
|
||||
card.style.setProperty("--y", `${y}%`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user