规范代码格式
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<error404 />
|
||||
<error404/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import error404 from "@/components/error/404.vue";
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -3,38 +3,39 @@
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex align-center mb-6">
|
||||
<v-icon size="x-large" color="primary" class="mr-3">mdi-database-cog-outline</v-icon>
|
||||
<v-icon class="mr-3" color="primary" size="x-large">mdi-database-cog-outline</v-icon>
|
||||
<div>
|
||||
<h1 class="text-h4 ">缓存管理</h1>
|
||||
<div class="text-subtitle-1 text-grey">管理应用的本地缓存资源</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-card class="mb-6" variant="tonal" color="info" density="compact">
|
||||
|
||||
<v-card class="mb-6" color="info" density="compact" variant="tonal">
|
||||
<v-card-text class="d-flex align-center">
|
||||
<v-icon color="info" class="mr-2">mdi-information-outline</v-icon>
|
||||
<v-icon class="mr-2" color="info">mdi-information-outline</v-icon>
|
||||
<span>在这里您可以查看和管理应用的缓存文件。清除缓存可能会导致应用需要重新下载资源,但有助于解决某些显示问题。</span>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="8">
|
||||
<v-card class="mb-4" variant="tonal">
|
||||
<v-card-text>
|
||||
<div class="d-flex align-center mb-2">
|
||||
<v-icon color="primary" class="mr-2">mdi-information</v-icon>
|
||||
<v-icon class="mr-2" color="primary">mdi-information</v-icon>
|
||||
<span class="text-h6">什么是缓存?</span>
|
||||
</div>
|
||||
<p>缓存是浏览器在本地存储的网站资源副本,如图片、脚本和样式表等。这些缓存可以加快页面加载速度,减少数据使用,并在离线时提供基本功能。</p>
|
||||
<p>
|
||||
缓存是浏览器在本地存储的网站资源副本,如图片、脚本和样式表等。这些缓存可以加快页面加载速度,减少数据使用,并在离线时提供基本功能。</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
|
||||
<v-col cols="12" md="4">
|
||||
<v-card class="mb-4" variant="tonal">
|
||||
<v-card-text>
|
||||
<div class="d-flex align-center mb-2">
|
||||
<v-icon color="warning" class="mr-2">mdi-lightbulb-outline</v-icon>
|
||||
<v-icon class="mr-2" color="warning">mdi-lightbulb-outline</v-icon>
|
||||
<span class="text-h6">何时清除缓存?</span>
|
||||
</div>
|
||||
<ul class="pl-4">
|
||||
@@ -46,8 +47,8 @@
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<CacheManager />
|
||||
|
||||
<CacheManager/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
@@ -65,4 +66,4 @@ export default {
|
||||
title: '缓存管理'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
+13
-13
@@ -4,9 +4,9 @@
|
||||
<v-col cols="12">
|
||||
<div class="d-flex align-center mb-6">
|
||||
<v-icon
|
||||
size="x-large"
|
||||
color="primary"
|
||||
class="mr-3"
|
||||
color="primary"
|
||||
size="x-large"
|
||||
>
|
||||
mdi-database-sync
|
||||
</v-icon>
|
||||
@@ -22,13 +22,13 @@
|
||||
|
||||
<v-card
|
||||
class="mb-6"
|
||||
variant="tonal"
|
||||
color="info"
|
||||
variant="tonal"
|
||||
>
|
||||
<v-card-text class="d-flex align-center">
|
||||
<v-icon
|
||||
color="info"
|
||||
class="mr-2"
|
||||
color="info"
|
||||
>
|
||||
mdi-information-outline
|
||||
</v-icon>
|
||||
@@ -38,7 +38,7 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<MigrationTool ref="migrationTool" />
|
||||
<MigrationTool ref="migrationTool"/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
<v-card>
|
||||
<v-card-title class="text-h5 d-flex align-center">
|
||||
<v-icon
|
||||
class="mr-3"
|
||||
color="primary"
|
||||
size="large"
|
||||
class="mr-3"
|
||||
>
|
||||
mdi-database-sync
|
||||
</v-icon>
|
||||
@@ -66,10 +66,10 @@
|
||||
</p>
|
||||
|
||||
<v-alert
|
||||
color="info"
|
||||
variant="tonal"
|
||||
class="mt-4"
|
||||
color="info"
|
||||
icon="mdi-information-outline"
|
||||
variant="tonal"
|
||||
>
|
||||
<ul class="ml-3 mt-1">
|
||||
<li>数据源: {{ dataSourceText }}</li>
|
||||
@@ -83,7 +83,7 @@
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
<v-btn
|
||||
color="grey-darken-1"
|
||||
variant="text"
|
||||
@@ -92,16 +92,16 @@
|
||||
稍后再说
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="isAutoMigrating"
|
||||
:loading="isAutoMigrating"
|
||||
color="primary"
|
||||
size="large"
|
||||
variant="elevated"
|
||||
:loading="isAutoMigrating"
|
||||
:disabled="isAutoMigrating"
|
||||
@click="startAutoMigration"
|
||||
>
|
||||
<v-icon
|
||||
left
|
||||
class="mr-2"
|
||||
left
|
||||
>
|
||||
mdi-database-export
|
||||
</v-icon>
|
||||
@@ -115,7 +115,7 @@
|
||||
|
||||
<script>
|
||||
import MigrationTool from "@/components/MigrationTool.vue";
|
||||
import { getSetting, setSetting } from "@/utils/settings";
|
||||
import {getSetting, setSetting} from "@/utils/settings";
|
||||
|
||||
export default {
|
||||
name: "DataMigrationPage",
|
||||
|
||||
+2
-1
@@ -2,4 +2,5 @@
|
||||
|
||||
Vue components created in this folder will automatically be converted to navigatable routes.
|
||||
|
||||
Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository.
|
||||
Full documentation for this feature can be found in the
|
||||
Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-container class="fill-height" fluid>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col cols="12" sm="8" md="6">
|
||||
<v-col cols="12" md="6" sm="8">
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">
|
||||
{{ status === 'processing' ? '正在处理授权...' : status === 'success' ? '授权成功' : '授权失败' }}
|
||||
@@ -9,9 +9,9 @@
|
||||
<v-card-text>
|
||||
<v-progress-linear
|
||||
v-if="status === 'processing'"
|
||||
indeterminate
|
||||
color="primary"
|
||||
class="mb-4"
|
||||
color="primary"
|
||||
indeterminate
|
||||
></v-progress-linear>
|
||||
<p>{{ message }}</p>
|
||||
</v-card-text>
|
||||
@@ -26,9 +26,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getSetting, setSetting } from '@/utils/settings';
|
||||
import {ref, onMounted} from 'vue';
|
||||
import {useRoute, useRouter} from 'vue-router';
|
||||
import {getSetting, setSetting} from '@/utils/settings';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -59,7 +59,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
status.value = 'success';
|
||||
router.push('/');
|
||||
router.push('/');
|
||||
|
||||
} catch (error) {
|
||||
console.error('授权处理失败:', error);
|
||||
|
||||
+102
-92
@@ -45,12 +45,19 @@
|
||||
|
||||
<!-- 输入方式选择 -->
|
||||
<v-tabs v-model="activeTab" class="mb-4 mx-2" color="primary" rounded>
|
||||
<v-tab value="text" class="px-5"><v-icon start>mdi-text-box</v-icon> 文本粘贴</v-tab>
|
||||
<v-tab value="file" class="px-5"><v-icon start>mdi-file-upload</v-icon> 文件上传</v-tab>
|
||||
<v-tab value="text" class="px-5">
|
||||
<v-icon start>mdi-text-box</v-icon>
|
||||
文本粘贴
|
||||
</v-tab>
|
||||
<v-tab value="file" class="px-5">
|
||||
<v-icon start>mdi-file-upload</v-icon>
|
||||
文件上传
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<!-- 格式选择 -->
|
||||
<v-btn-toggle v-model="formatMode" color="primary" class="mb-4 mx-2" mandatory density="comfortable" border rounded>
|
||||
<v-btn-toggle v-model="formatMode" color="primary" class="mb-4 mx-2" mandatory density="comfortable" border
|
||||
rounded>
|
||||
<v-btn value="auto">自动检测</v-btn>
|
||||
<v-btn value="json">JSON</v-btn>
|
||||
<v-btn value="yaml" :disabled="!yamlLibLoaded">
|
||||
@@ -268,12 +275,12 @@
|
||||
<span
|
||||
v-if="!settings.hideTeacherName && course.teacher"
|
||||
>
|
||||
<br />{{ course.teacher }}
|
||||
<br/>{{ course.teacher }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!settings.hideRoom && course.room"
|
||||
>
|
||||
<br />{{ course.room }}
|
||||
<br/>{{ course.room }}
|
||||
</span>
|
||||
<span
|
||||
v-if="course.weekType"
|
||||
@@ -288,17 +295,17 @@
|
||||
<span
|
||||
v-if="!settings.hideTeacherName && item[day].teacher"
|
||||
>
|
||||
<br />{{ item[day].teacher }}
|
||||
<br/>{{ item[day].teacher }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!settings.hideRoom && item[day].room"
|
||||
>
|
||||
<br />{{ item[day].room }}
|
||||
<br/>{{ item[day].room }}
|
||||
</span>
|
||||
<span
|
||||
<span
|
||||
v-if="item[day].weekType"
|
||||
class="week-type"
|
||||
>
|
||||
class="week-type"
|
||||
>
|
||||
{{ item[day].weekType }}周
|
||||
</span>
|
||||
</template>
|
||||
@@ -329,7 +336,8 @@
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<!-- 美化日期导航标签 -->
|
||||
<v-tabs v-if="daysWithSchedule.length > 0" v-model="activeDay" class="mb-4" color="primary" grow align-tabs="center">
|
||||
<v-tabs v-if="daysWithSchedule.length > 0" v-model="activeDay" class="mb-4" color="primary" grow
|
||||
align-tabs="center">
|
||||
<v-tab v-for="day in daysWithSchedule" :key="day" :value="day" class="px-2 font-weight-medium">
|
||||
{{ dayNames[day] }}
|
||||
<v-badge
|
||||
@@ -345,67 +353,69 @@
|
||||
<v-window-item v-for="day in daysWithSchedule" :key="day" :value="day">
|
||||
<v-table density="compact" class="rounded" :headers-length="6" disable-sort>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">节次</th>
|
||||
<th>课程</th>
|
||||
<th>时间</th>
|
||||
<th>教师</th>
|
||||
<th>教室</th>
|
||||
<th>周次</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-center">节次</th>
|
||||
<th>课程</th>
|
||||
<th>时间</th>
|
||||
<th>教师</th>
|
||||
<th>教室</th>
|
||||
<th>周次</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(group, index) in groupByPeriod(getDaySchedule(day))" :key="index">
|
||||
<tr>
|
||||
<td class="text-center font-weight-bold">
|
||||
{{ group.period }}
|
||||
<v-tooltip v-if="group.originalPeriod !== group.period">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon size="x-small" v-bind="props" color="info" class="ml-1">mdi-sync</v-icon>
|
||||
</template>
|
||||
原节次: {{ group.originalPeriod }}
|
||||
</v-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
|
||||
<v-chip size="small" :color="getSubjectColor(item.subject)" label text-color="white" class="mr-1">
|
||||
{{ item.subject }}
|
||||
</v-chip>
|
||||
<v-chip v-if="group.items.length > 1" size="x-small" class="ml-1" :color="item.weekType === '单' ? 'warning' : 'success'">
|
||||
{{ item.weekType }}周
|
||||
</v-chip>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-for="(timeSlot, i) in group.uniqueTimeSlots" :key="i" class="mb-1">
|
||||
<v-chip size="x-small" class="time-chip">
|
||||
{{ formatTime(timeSlot.startTime) }} - {{ formatTime(timeSlot.endTime) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="!settings.hideTeacherName">
|
||||
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
|
||||
{{ item.teacher || '-' }}
|
||||
</div>
|
||||
<template v-for="(group, index) in groupByPeriod(getDaySchedule(day))" :key="index">
|
||||
<tr>
|
||||
<td class="text-center font-weight-bold">
|
||||
{{ group.period }}
|
||||
<v-tooltip v-if="group.originalPeriod !== group.period">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon size="x-small" v-bind="props" color="info" class="ml-1">mdi-sync</v-icon>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="!settings.hideRoom">
|
||||
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
|
||||
{{ item.room || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</td>
|
||||
<td>
|
||||
原节次: {{ group.originalPeriod }}
|
||||
</v-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
|
||||
<v-chip size="small" :color="getSubjectColor(item.subject)" label text-color="white"
|
||||
class="mr-1">
|
||||
{{ item.subject }}
|
||||
</v-chip>
|
||||
<v-chip v-if="group.items.length > 1" size="x-small" class="ml-1"
|
||||
:color="item.weekType === '单' ? 'warning' : 'success'">
|
||||
{{ item.weekType }}周
|
||||
</v-chip>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-for="(timeSlot, i) in group.uniqueTimeSlots" :key="i" class="mb-1">
|
||||
<v-chip size="x-small" class="time-chip">
|
||||
{{ formatTime(timeSlot.startTime) }} - {{ formatTime(timeSlot.endTime) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="!settings.hideTeacherName">
|
||||
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
|
||||
{{ item.weeks }}
|
||||
{{ item.teacher || '-' }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="!settings.hideRoom">
|
||||
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
|
||||
{{ item.room || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</td>
|
||||
<td>
|
||||
<div v-for="(item, i) in group.items" :key="i" class="mb-1">
|
||||
{{ item.weeks }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-window-item>
|
||||
@@ -600,25 +610,25 @@ export default {
|
||||
totalWeeks: 30
|
||||
},
|
||||
tableHeaders: [
|
||||
{ title: "", key: "data-table-select" },
|
||||
{ title: "节次", key: "period" },
|
||||
{ title: "周一", key: "1" },
|
||||
{ title: "周二", key: "2" },
|
||||
{ title: "周三", key: "3" },
|
||||
{ title: "周四", key: "4" },
|
||||
{ title: "周五", key: "5" },
|
||||
{ title: "周六", key: "6" },
|
||||
{ title: "周日", key: "7" },
|
||||
{title: "", key: "data-table-select"},
|
||||
{title: "节次", key: "period"},
|
||||
{title: "周一", key: "1"},
|
||||
{title: "周二", key: "2"},
|
||||
{title: "周三", key: "3"},
|
||||
{title: "周四", key: "4"},
|
||||
{title: "周五", key: "5"},
|
||||
{title: "周六", key: "6"},
|
||||
{title: "周日", key: "7"},
|
||||
],
|
||||
timeTableHeaders: [
|
||||
{ title: "节次", key: "period" },
|
||||
{ title: "课程", key: "subject" },
|
||||
{ title: "星期", key: "day" },
|
||||
{ title: "开始时间", key: "startTime" },
|
||||
{ title: "结束时间", key: "endTime" },
|
||||
{ title: "教师", key: "teacher" },
|
||||
{ title: "教室", key: "room" },
|
||||
{ title: "周次", key: "weeks" },
|
||||
{title: "节次", key: "period"},
|
||||
{title: "课程", key: "subject"},
|
||||
{title: "星期", key: "day"},
|
||||
{title: "开始时间", key: "startTime"},
|
||||
{title: "结束时间", key: "endTime"},
|
||||
{title: "教师", key: "teacher"},
|
||||
{title: "教室", key: "room"},
|
||||
{title: "周次", key: "weeks"},
|
||||
],
|
||||
dayNames: {
|
||||
1: "周一",
|
||||
@@ -700,7 +710,7 @@ export default {
|
||||
// 先按节次排序
|
||||
if (a.period !== b.period) return a.period - b.period;
|
||||
// 再按星期排序
|
||||
const dayOrder = { "周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7 };
|
||||
const dayOrder = {"周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7};
|
||||
return dayOrder[a.day] - dayOrder[b.day];
|
||||
});
|
||||
},
|
||||
@@ -845,8 +855,8 @@ export default {
|
||||
// 如果数据是通过CSESParser解析的
|
||||
if (data instanceof CSESParser) {
|
||||
return data.version === 1 &&
|
||||
Array.isArray(data.subjects) &&
|
||||
Array.isArray(data.schedules);
|
||||
Array.isArray(data.subjects) &&
|
||||
Array.isArray(data.schedules);
|
||||
}
|
||||
|
||||
// 基本验证
|
||||
@@ -865,7 +875,7 @@ export default {
|
||||
},
|
||||
|
||||
processCsesData(data) {
|
||||
const { schedules, subjects } = data;
|
||||
const {schedules, subjects} = data;
|
||||
|
||||
// 使用对象引用优化内存使用
|
||||
const subjectMap = Object.fromEntries(
|
||||
@@ -1004,7 +1014,7 @@ export default {
|
||||
for (const group of periodGroups) {
|
||||
// 遍历该节次的每个课程
|
||||
for (const item of group.items) {
|
||||
const dayNumber = { "周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7 }[item.day];
|
||||
const dayNumber = {"周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7}[item.day];
|
||||
const teacher = this.settings.hideTeacherName ? "" : (item.teacher || "");
|
||||
const room = this.settings.hideRoom ? "" : (item.room || "");
|
||||
|
||||
@@ -1072,7 +1082,7 @@ export default {
|
||||
}
|
||||
// 检查是否含有YAML典型的格式如键值对 key: value
|
||||
return /^\s*[a-zA-Z0-9_-]+\s*:/.test(trimmed) ||
|
||||
/\n\s*[a-zA-Z0-9_-]+\s*:/.test(trimmed);
|
||||
/\n\s*[a-zA-Z0-9_-]+\s*:/.test(trimmed);
|
||||
},
|
||||
|
||||
parseYaml(text) {
|
||||
@@ -1094,7 +1104,7 @@ export default {
|
||||
// 获取原始数据并过滤
|
||||
const allData = this.getUnfilteredTimeTableData();
|
||||
return allData.filter(item => {
|
||||
const dayNum = { "周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7 }[item.day];
|
||||
const dayNum = {"周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7}[item.day];
|
||||
return dayNum === day;
|
||||
});
|
||||
},
|
||||
@@ -1165,7 +1175,7 @@ export default {
|
||||
// 先按节次排序
|
||||
if (a.period !== b.period) return a.period - b.period;
|
||||
// 再按星期排序
|
||||
const dayOrder = { "周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7 };
|
||||
const dayOrder = {"周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7};
|
||||
return dayOrder[a.day] - dayOrder[b.day];
|
||||
});
|
||||
},
|
||||
|
||||
@@ -26,18 +26,18 @@
|
||||
label="server.authDomain"
|
||||
/>
|
||||
</v-form>
|
||||
<v-divider class="my-4" />
|
||||
<v-divider class="my-4"/>
|
||||
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="me-2"
|
||||
color="primary"
|
||||
@click="applySettings"
|
||||
>
|
||||
应用设置
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
class="me-2"
|
||||
color="secondary"
|
||||
@click="clearGuard"
|
||||
>
|
||||
清除重定向守卫
|
||||
@@ -83,9 +83,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { getSetting, setSetting } from '@/utils/settings'
|
||||
import { kvServerProvider } from '@/utils/providers/kvServerProvider'
|
||||
import {ref, computed} from 'vue'
|
||||
import {getSetting, setSetting} from '@/utils/settings'
|
||||
import {kvServerProvider} from '@/utils/providers/kvServerProvider'
|
||||
|
||||
const REDIRECT_GUARD_KEY = 'kvinit.redirecting'
|
||||
|
||||
@@ -104,7 +104,11 @@ const applySettings = () => {
|
||||
}
|
||||
|
||||
const clearGuard = () => {
|
||||
try { sessionStorage.removeItem(REDIRECT_GUARD_KEY) } catch (e) { console.debug(e) }
|
||||
try {
|
||||
sessionStorage.removeItem(REDIRECT_GUARD_KEY)
|
||||
} catch (e) {
|
||||
console.debug(e)
|
||||
}
|
||||
}
|
||||
|
||||
const simulateLoadError = () => {
|
||||
@@ -117,7 +121,11 @@ const simulateLoadError = () => {
|
||||
}
|
||||
|
||||
const guardRaw = computed(() => {
|
||||
try { return sessionStorage.getItem(REDIRECT_GUARD_KEY) } catch (e) { return String(e) }
|
||||
try {
|
||||
return sessionStorage.getItem(REDIRECT_GUARD_KEY)
|
||||
} catch (e) {
|
||||
return String(e)
|
||||
}
|
||||
})
|
||||
|
||||
const settingsDump = computed(() => {
|
||||
|
||||
+27
-27
@@ -25,8 +25,8 @@
|
||||
<v-list-item-subtitle>
|
||||
<v-chip
|
||||
:color="connected ? 'success' : 'error'"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
size="small"
|
||||
>
|
||||
{{ connected ? 'connected' : 'disconnected' }}
|
||||
</v-chip>
|
||||
@@ -42,7 +42,7 @@
|
||||
<v-list-item-subtitle>{{ currentDataKey }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider class="my-4" />
|
||||
<v-divider class="my-4"/>
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
@@ -50,26 +50,26 @@
|
||||
>
|
||||
<v-text-field
|
||||
v-model="manualToken"
|
||||
label="手动加入 Token (留空使用配置的 Token)"
|
||||
clearable
|
||||
label="手动加入 Token (留空使用配置的 Token)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
class="d-flex align-center"
|
||||
cols="12"
|
||||
md="4"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="mr-2"
|
||||
color="primary"
|
||||
@click="handleJoinToken(manualToken || currentToken)"
|
||||
>
|
||||
加入
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
class="mr-2"
|
||||
:disabled="!joinedToken"
|
||||
class="mr-2"
|
||||
color="warning"
|
||||
@click="handleLeaveToken(joinedToken)"
|
||||
>
|
||||
离开当前
|
||||
@@ -83,24 +83,24 @@
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider class="my-4" />
|
||||
<v-divider class="my-4"/>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card variant="tonal" color="primary" border>
|
||||
<v-card border color="primary" variant="tonal">
|
||||
<v-card-title class="text-subtitle-1">聊天室消息</v-card-title>
|
||||
<v-card-text>
|
||||
<v-textarea
|
||||
v-model="chatInput"
|
||||
label="发送到当前已加入的设备频道"
|
||||
rows="2"
|
||||
auto-grow
|
||||
clearable
|
||||
label="发送到当前已加入的设备频道"
|
||||
rows="2"
|
||||
/>
|
||||
<div class="d-flex">
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:disabled="!canSendChat"
|
||||
color="primary"
|
||||
@click="sendChat"
|
||||
>
|
||||
发送聊天
|
||||
@@ -128,8 +128,8 @@
|
||||
<v-card-title>在线设备</v-card-title>
|
||||
<v-card-text>
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="mb-3"
|
||||
color="primary"
|
||||
@click="fetchOnline"
|
||||
>
|
||||
刷新在线列表
|
||||
@@ -178,11 +178,11 @@
|
||||
<v-card border>
|
||||
<v-card-title class="d-flex align-center">
|
||||
事件日志
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
<v-btn
|
||||
color="error"
|
||||
size="small"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click="clearLogs"
|
||||
>
|
||||
清空
|
||||
@@ -214,8 +214,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
import { getSetting } from '@/utils/settings'
|
||||
import {ref, onMounted, onBeforeUnmount, computed} from 'vue'
|
||||
import {getSetting} from '@/utils/settings'
|
||||
import {
|
||||
getSocket,
|
||||
on as socketOn,
|
||||
@@ -262,17 +262,17 @@ function wireSocketBaseEvents() {
|
||||
s.on('connect', () => {
|
||||
connected.value = true
|
||||
socketId.value = s.id || ''
|
||||
pushLog('connect', { id: s.id })
|
||||
pushLog('connect', {id: s.id})
|
||||
// re-join with token if set
|
||||
if (joinedToken.value) joinToken(joinedToken.value)
|
||||
})
|
||||
s.on('disconnect', (reason) => {
|
||||
connected.value = false
|
||||
pushLog('disconnect', { reason })
|
||||
pushLog('disconnect', {reason})
|
||||
})
|
||||
s.on('connect_error', (err) => pushLog('connect_error', { message: err?.message }))
|
||||
s.on('reconnect_attempt', (n) => pushLog('reconnect_attempt', { attempt: n }))
|
||||
s.on('reconnect', (n) => pushLog('reconnect', { attempt: n }))
|
||||
s.on('connect_error', (err) => pushLog('connect_error', {message: err?.message}))
|
||||
s.on('reconnect_attempt', (n) => pushLog('reconnect_attempt', {attempt: n}))
|
||||
s.on('reconnect', (n) => pushLog('reconnect', {attempt: n}))
|
||||
}
|
||||
|
||||
function wireBusinessEvents() {
|
||||
@@ -306,7 +306,7 @@ function handleJoinToken(token) {
|
||||
}
|
||||
joinToken(token)
|
||||
joinedToken.value = token
|
||||
pushLog('join-token', { token })
|
||||
pushLog('join-token', {token})
|
||||
} catch (e) {
|
||||
pushLog('join-token-error', String(e))
|
||||
}
|
||||
@@ -316,7 +316,7 @@ function handleLeaveToken(token) {
|
||||
try {
|
||||
leaveToken(token)
|
||||
if (joinedToken.value === token) joinedToken.value = ''
|
||||
pushLog('leave-token', { token })
|
||||
pushLog('leave-token', {token})
|
||||
} catch (e) {
|
||||
pushLog('leave-token-error', String(e))
|
||||
}
|
||||
@@ -353,7 +353,7 @@ function sendChat() {
|
||||
const s = getSocket()
|
||||
// send as plain string per server contract
|
||||
s.emit('chat:send', text)
|
||||
pushLog('chat:send', { text })
|
||||
pushLog('chat:send', {text})
|
||||
chatInput.value = ''
|
||||
} catch (e) {
|
||||
pushLog('chat:error', String(e))
|
||||
@@ -373,7 +373,7 @@ async function fetchOnline() {
|
||||
const resp = await fetch(`${serverUrl.value}/devices/online`)
|
||||
const data = await resp.json()
|
||||
onlineDevices.value = Array.isArray(data?.devices) ? data.devices : []
|
||||
pushLog('fetch-online', { count: onlineDevices.value.length })
|
||||
pushLog('fetch-online', {count: onlineDevices.value.length})
|
||||
} catch (e) {
|
||||
pushLog('fetch-online-error', String(e))
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
<v-app-bar-title class="text-h6">
|
||||
编辑考试配置
|
||||
</v-app-bar-title>
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-content-save"
|
||||
:loading="saving"
|
||||
color="success"
|
||||
prepend-icon="mdi-content-save"
|
||||
variant="outlined"
|
||||
@click="save"
|
||||
>
|
||||
保存
|
||||
@@ -31,8 +31,8 @@
|
||||
v-if="id"
|
||||
ref="editor"
|
||||
:config-id="id"
|
||||
@saved="onSaved"
|
||||
@error="onError"
|
||||
@saved="onSaved"
|
||||
/>
|
||||
</v-container>
|
||||
</v-container>
|
||||
@@ -43,7 +43,7 @@ import ExamConfigEditor from '@/components/ExamConfigEditor.vue'
|
||||
|
||||
export default {
|
||||
name: 'ExamEditorPage',
|
||||
components: { ExamConfigEditor },
|
||||
components: {ExamConfigEditor},
|
||||
data() {
|
||||
return {
|
||||
id: this.$route.params.id,
|
||||
|
||||
+18
-18
@@ -1,30 +1,30 @@
|
||||
<template>
|
||||
<v-alert
|
||||
v-if="error"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
border="start"
|
||||
class="mb-4"
|
||||
closable
|
||||
type="error"
|
||||
variant="tonal"
|
||||
@click:close="error = ''"
|
||||
>
|
||||
{{ error }}
|
||||
</v-alert>
|
||||
|
||||
<v-skeleton-loader v-if="loading" type="article" />
|
||||
<v-skeleton-loader v-if="loading" type="article"/>
|
||||
|
||||
<div v-else-if="!config">
|
||||
<v-alert type="warning" variant="tonal" border="start">
|
||||
<v-alert border="start" type="warning" variant="tonal">
|
||||
缺少配置,请通过 URL 参数 id 或 url 传入配置。
|
||||
</v-alert>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="player" ref="playerRef">
|
||||
<div ref="playerRef" class="player">
|
||||
<ExamPlayer
|
||||
v-model:room-number="roomNumberLocal"
|
||||
:exam-config="config"
|
||||
:config="playerConfigObj"
|
||||
:exam-config="config"
|
||||
:show-action-bar="true"
|
||||
:time-sync-status="'电脑时间'"
|
||||
@exit="exit()"
|
||||
@@ -34,14 +34,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {ref, computed, onMounted, watch} from "vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import dataProvider from "@/utils/dataProvider";
|
||||
import { ExamPlayer } from "@examaware-cs/player";
|
||||
import {ExamPlayer} from "@examaware-cs/player";
|
||||
|
||||
export default {
|
||||
name: "ExamPlayerPage",
|
||||
components: { ExamPlayer },
|
||||
components: {ExamPlayer},
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -103,11 +103,11 @@ export default {
|
||||
// ExamAware 需要 examInfos: { name, start, end, alertTime? }
|
||||
examInfos: Array.isArray(raw?.examInfos)
|
||||
? raw.examInfos.map((i) => ({
|
||||
name: i?.name || "未命名科目",
|
||||
start: i?.start || "",
|
||||
end: i?.end || "",
|
||||
alertTime: typeof i?.alertTime === "number" ? i.alertTime : 15,
|
||||
}))
|
||||
name: i?.name || "未命名科目",
|
||||
start: i?.start || "",
|
||||
end: i?.end || "",
|
||||
alertTime: typeof i?.alertTime === "number" ? i.alertTime : 15,
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
}
|
||||
@@ -143,7 +143,7 @@ export default {
|
||||
@font-face {
|
||||
font-family: "TCloudNumber";
|
||||
src: url("../assets/fonts/TCloudNumberVF.ttf") format("truetype-variations"),
|
||||
url("../assets/fonts/TCloudNumberVF.ttf") format("truetype");
|
||||
url("../assets/fonts/TCloudNumberVF.ttf") format("truetype");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -156,8 +156,8 @@ body,
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "MiSans", MiSans, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans",
|
||||
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans",
|
||||
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
/* 设置主题为深色 */
|
||||
|
||||
+41
-47
@@ -2,9 +2,9 @@
|
||||
<v-container class="fill-height">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card class="elevation-12" border>
|
||||
<v-card border class="elevation-12">
|
||||
<v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4">
|
||||
<v-icon color="white" class="mr-2">mdi-calendar-check</v-icon>
|
||||
<v-icon class="mr-2" color="white">mdi-calendar-check</v-icon>
|
||||
考试看板
|
||||
</v-card-title>
|
||||
<v-card-subtitle>
|
||||
@@ -14,11 +14,11 @@
|
||||
<!-- 错误提示 -->
|
||||
<v-alert
|
||||
v-if="error"
|
||||
type="error"
|
||||
class="mb-4 mt-3 mx-2"
|
||||
variant="tonal"
|
||||
border="start"
|
||||
class="mb-4 mt-3 mx-2"
|
||||
closable
|
||||
type="error"
|
||||
variant="tonal"
|
||||
@click:close="error = ''"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
@@ -30,11 +30,11 @@
|
||||
<!-- 成功提示 -->
|
||||
<v-alert
|
||||
v-if="success"
|
||||
type="success"
|
||||
class="mb-4 mt-3 mx-2"
|
||||
variant="tonal"
|
||||
border="start"
|
||||
class="mb-4 mt-3 mx-2"
|
||||
closable
|
||||
type="success"
|
||||
variant="tonal"
|
||||
@click:close="success = ''"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
@@ -47,19 +47,19 @@
|
||||
<div class="d-flex justify-space-between align-center mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
color="primary"
|
||||
prepend-icon="mdi-plus"
|
||||
class="mr-2"
|
||||
@click="createNewConfig"
|
||||
>
|
||||
新建配置
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:loading="loading"
|
||||
color="info"
|
||||
prepend-icon="mdi-refresh"
|
||||
:loading="loading"
|
||||
@click="loadConfigs"
|
||||
variant="outlined"
|
||||
@click="loadConfigs"
|
||||
>
|
||||
刷新
|
||||
</v-btn>
|
||||
@@ -77,8 +77,8 @@
|
||||
<v-card v-if="loading" class="my-4" outlined>
|
||||
<v-card-text>
|
||||
<v-skeleton-loader
|
||||
type="list-item-avatar-two-line@3"
|
||||
class="mx-auto"
|
||||
type="list-item-avatar-two-line@3"
|
||||
></v-skeleton-loader>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -98,7 +98,7 @@
|
||||
@click="showEditDialog(config)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-avatar color="primary" class="mr-2">
|
||||
<v-avatar class="mr-2" color="primary">
|
||||
<v-icon color="white">mdi-calendar-text</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
@@ -108,11 +108,11 @@
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-caption mt-1">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="small" class="mr-1">mdi-information-outline</v-icon>
|
||||
<v-icon class="mr-1" size="small">mdi-information-outline</v-icon>
|
||||
{{ config.message || '无描述' }}
|
||||
</div>
|
||||
<div class="d-flex align-center mt-1">
|
||||
<v-icon size="small" class="mr-1">mdi-book-multiple</v-icon>
|
||||
<v-icon class="mr-1" size="small">mdi-book-multiple</v-icon>
|
||||
{{ config.examInfos ? config.examInfos.length : 0 }} 堂考试
|
||||
</div>
|
||||
</v-list-item-subtitle>
|
||||
@@ -120,22 +120,22 @@
|
||||
<template #append>
|
||||
<div class="d-flex align-center">
|
||||
<v-btn
|
||||
class="mr-1"
|
||||
color="primary"
|
||||
icon="mdi-pencil"
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="text"
|
||||
class="mr-1"
|
||||
@click="showEditDialog(config)"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
class="mr-1"
|
||||
color="info"
|
||||
icon="mdi-eye"
|
||||
size="small"
|
||||
color="info"
|
||||
variant="text"
|
||||
class="mr-1"
|
||||
@click="showEditDialog(config)"
|
||||
>
|
||||
<v-icon>mdi-eye</v-icon>
|
||||
@@ -151,7 +151,7 @@
|
||||
<!-- 空状态 -->
|
||||
<v-card v-if="!loading && configs.length === 0" class="my-4" elevation="1">
|
||||
<v-card-text class="text-center py-8">
|
||||
<v-icon size="64" color="grey-lighten-1" class="mb-4">
|
||||
<v-icon class="mb-4" color="grey-lighten-1" size="64">
|
||||
mdi-calendar-blank
|
||||
</v-icon>
|
||||
<h3 class="text-h6 mb-2 text-grey-darken-1">暂无配置</h3>
|
||||
@@ -176,16 +176,16 @@
|
||||
<v-dialog v-model="renameDialog" max-width="500">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon color="primary" class="mr-2">mdi-rename-box</v-icon>
|
||||
<v-icon class="mr-2" color="primary">mdi-rename-box</v-icon>
|
||||
重命名配置
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="newConfigName"
|
||||
:rules="[v => !!v || '配置名称不能为空']"
|
||||
label="配置名称"
|
||||
prepend-inner-icon="mdi-calendar-text"
|
||||
variant="outlined"
|
||||
:rules="[v => !!v || '配置名称不能为空']"
|
||||
@keyup.enter="renameConfig"
|
||||
></v-text-field>
|
||||
</v-card-text>
|
||||
@@ -199,11 +199,11 @@
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!newConfigName"
|
||||
:loading="renaming"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
:loading="renaming"
|
||||
@click="renameConfig"
|
||||
:disabled="!newConfigName"
|
||||
>
|
||||
确认
|
||||
</v-btn>
|
||||
@@ -215,22 +215,22 @@
|
||||
<v-dialog v-model="editDialog" max-width="1200" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center primary lighten-1 white--text py-3 px-4">
|
||||
<v-icon color="white" class="mr-2">mdi-pencil</v-icon>
|
||||
<v-icon class="mr-2" color="white">mdi-pencil</v-icon>
|
||||
编辑考试配置
|
||||
<v-spacer></v-spacer>
|
||||
<v-chip
|
||||
v-if="editingConfig"
|
||||
color="white"
|
||||
text-color="primary"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
color="white"
|
||||
size="small"
|
||||
text-color="primary"
|
||||
>
|
||||
ID: {{ editingConfig.id }}
|
||||
</v-chip>
|
||||
|
||||
<v-btn
|
||||
icon="mdi-close"
|
||||
color="white"
|
||||
icon="mdi-close"
|
||||
variant="text"
|
||||
@click="closeEditDialog"
|
||||
>
|
||||
@@ -238,33 +238,33 @@
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-4"
|
||||
style="max-height: 70vh; overflow-y: auto;">
|
||||
style="max-height: 70vh; overflow-y: auto;">
|
||||
<ExamConfigEditor
|
||||
v-if="editingConfig"
|
||||
:config-id="editingConfig.id"
|
||||
ref="configEditor"
|
||||
:config-id="editingConfig.id"
|
||||
:dialog-mode="true"
|
||||
@saved="onConfigSaved"
|
||||
@deleted="onConfigDeleted"
|
||||
@error="onConfigError"
|
||||
@opened="onConfigOpened"
|
||||
@deleted="onConfigDeleted"
|
||||
@saved="onConfigSaved"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-btn
|
||||
color="grey"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-close"
|
||||
variant="outlined"
|
||||
@click="closeEditDialog"
|
||||
>
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-content-save"
|
||||
:loading="saving"
|
||||
color="success"
|
||||
prepend-icon="mdi-content-save"
|
||||
variant="outlined"
|
||||
@click="saveConfigInDialog"
|
||||
>
|
||||
保存配置
|
||||
@@ -277,7 +277,7 @@ style="max-height: 70vh; overflow-y: auto;">
|
||||
|
||||
<script>
|
||||
import dataProvider from '@/utils/dataProvider'
|
||||
import { getSetting } from '@/utils/settings'
|
||||
import {getSetting} from '@/utils/settings'
|
||||
import ExamConfigEditor from '@/components/ExamConfigEditor.vue'
|
||||
|
||||
export default {
|
||||
@@ -358,12 +358,12 @@ export default {
|
||||
]
|
||||
|
||||
// 保存配置列表
|
||||
const configList = exampleConfigs.map(c => ({ id: c.id }))
|
||||
const configList = exampleConfigs.map(c => ({id: c.id}))
|
||||
await dataProvider.saveData('es_list', configList)
|
||||
|
||||
// 保存每个配置的详细信息
|
||||
for (let config of exampleConfigs) {
|
||||
const configData = { ...config }
|
||||
const configData = {...config}
|
||||
delete configData.id
|
||||
await dataProvider.saveData(`es_${config.id}`, configData)
|
||||
}
|
||||
@@ -444,7 +444,7 @@ export default {
|
||||
})
|
||||
|
||||
// 更新存储的配置列表
|
||||
const currentList = this.configs.map(c => ({ id: c.id }))
|
||||
const currentList = this.configs.map(c => ({id: c.id}))
|
||||
const listResponse = await dataProvider.saveData('es_list', currentList)
|
||||
if (!listResponse) {
|
||||
throw new Error(listResponse.error?.message || '更新列表失败')
|
||||
@@ -464,11 +464,6 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 显示重命名对话框
|
||||
*/
|
||||
@@ -534,7 +529,6 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 在弹框中保存配置
|
||||
*/
|
||||
|
||||
+147
-136
@@ -4,16 +4,16 @@
|
||||
{{ titleText }}
|
||||
</v-app-bar-title>
|
||||
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
|
||||
<template #append>
|
||||
<!-- 只读 Token 警告 -->
|
||||
<v-chip
|
||||
v-if="tokenDisplayInfo.readonly"
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
class="mx-2"
|
||||
color="warning"
|
||||
prepend-icon="mdi-lock-alert"
|
||||
variant="tonal"
|
||||
>
|
||||
只读
|
||||
</v-chip>
|
||||
@@ -21,25 +21,25 @@
|
||||
<!-- 学生名称显示 chip(始终蓝色) -->
|
||||
<v-chip
|
||||
v-if="tokenDisplayInfo.show"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
class="mx-2"
|
||||
prepend-icon="mdi-account"
|
||||
:style="{ cursor: tokenDisplayInfo.disabled ? 'default' : 'pointer' }"
|
||||
class="mx-2"
|
||||
color="primary"
|
||||
prepend-icon="mdi-account"
|
||||
variant="tonal"
|
||||
@click="handleTokenChipClick"
|
||||
>
|
||||
{{ tokenDisplayInfo.text }}
|
||||
</v-chip>
|
||||
|
||||
<v-btn icon="mdi-chat" variant="text" @click="isChatOpen = true" />
|
||||
<v-btn icon="mdi-chat" variant="text" @click="isChatOpen = true"/>
|
||||
<v-btn
|
||||
icon="mdi-bell"
|
||||
variant="text"
|
||||
:badge="unreadCount || undefined"
|
||||
:badge-color="unreadCount ? 'error' : undefined"
|
||||
icon="mdi-bell"
|
||||
variant="text"
|
||||
@click="$refs.messageLog.drawer = true"
|
||||
/>
|
||||
<v-btn icon="mdi-cog" variant="text" @click="$router.push('/settings')" />
|
||||
<v-btn icon="mdi-cog" variant="text" @click="$router.push('/settings')"/>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<!-- 初始化选择卡片,仅在首页且需要授权时显示;不影响顶栏 -->
|
||||
@@ -65,17 +65,17 @@
|
||||
<div
|
||||
v-for="item in sortedItems"
|
||||
:key="item.key"
|
||||
class="grid-item"
|
||||
:style="{
|
||||
'grid-row-end': `span ${item.rowSpan}`,
|
||||
order: item.order,
|
||||
}"
|
||||
class="grid-item"
|
||||
>
|
||||
<v-card
|
||||
border
|
||||
height="100%"
|
||||
class="glow-track"
|
||||
:class="{ 'glow-highlight': highlightedCards[item.key] }"
|
||||
border
|
||||
class="glow-track"
|
||||
height="100%"
|
||||
@click="!isEditingDisabled && openDialog(item.key)"
|
||||
@mousemove="handleMouseMove"
|
||||
@touchmove="handleTouchMove"
|
||||
@@ -106,7 +106,7 @@
|
||||
:disabled="isEditingDisabled"
|
||||
@click="openDialog(subject.name)"
|
||||
>
|
||||
<v-icon start> mdi-plus </v-icon>
|
||||
<v-icon start> mdi-plus</v-icon>
|
||||
{{ subject.name }}
|
||||
</v-btn>
|
||||
</v-btn-group>
|
||||
@@ -116,16 +116,16 @@
|
||||
<v-card
|
||||
v-for="subject in unusedSubjects"
|
||||
:key="subject.name"
|
||||
:disabled="isEditingDisabled"
|
||||
border
|
||||
class="empty-subject-card"
|
||||
:disabled="isEditingDisabled"
|
||||
@click="openDialog(subject.name)"
|
||||
>
|
||||
<v-card-title class="text-subtitle-1">
|
||||
{{ subject.name }}
|
||||
</v-card-title>
|
||||
<v-card-text class="text-center">
|
||||
<v-icon size="small" color="grey"> mdi-plus </v-icon>
|
||||
<v-icon color="grey" size="small"> mdi-plus</v-icon>
|
||||
<div class="text-caption text-grey">点击添加作业</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -134,10 +134,10 @@
|
||||
</div>
|
||||
<v-btn
|
||||
v-if="!state.synced"
|
||||
color="error"
|
||||
size="large"
|
||||
:loading="loading.upload"
|
||||
class="ml-2"
|
||||
color="error"
|
||||
size="large"
|
||||
@click="manualUpload"
|
||||
>
|
||||
上传
|
||||
@@ -147,31 +147,31 @@
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showRandomPickerButton"
|
||||
append-icon="mdi-dice-multiple"
|
||||
class="ml-2"
|
||||
color="amber"
|
||||
prepend-icon="mdi-account-question"
|
||||
append-icon="mdi-dice-multiple"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
@click="openRandomPicker"
|
||||
>
|
||||
随机点名
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showExamScheduleButton"
|
||||
class="ml-2"
|
||||
color="green"
|
||||
prepend-icon="mdi-calendar-check"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
@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"
|
||||
class="ml-2"
|
||||
@click="$router.push('/list')"
|
||||
>
|
||||
列表
|
||||
@@ -182,8 +182,8 @@
|
||||
:prepend-icon="
|
||||
state.isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen'
|
||||
"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
size="large"
|
||||
@click="toggleFullscreen"
|
||||
>
|
||||
{{ state.isFullscreen ? "退出全屏" : "全屏显示"
|
||||
@@ -197,7 +197,7 @@
|
||||
variant="tonal"
|
||||
>
|
||||
<v-card-title class="text-subtitle-1">
|
||||
<v-icon start icon="mdi-shield-check" size="small" />
|
||||
<v-icon icon="mdi-shield-check" size="small" start/>
|
||||
屏幕保护技术已启用
|
||||
</v-card-title>
|
||||
<v-card-text class="text-body-2">
|
||||
@@ -206,10 +206,10 @@
|
||||
</p>
|
||||
<p class="text-caption text-grey">
|
||||
*研究显示动态像素偏移技术可以修复屏幕坏点,起到保护屏幕的作用,数据来自实验室。<a
|
||||
href="https://patentscope.wipo.int/search/zh/detail.jsf?docId=CN232281523&_cid=P20-M8L0YX-67061-1"
|
||||
target="_blank"
|
||||
>专利号CN108648692
|
||||
</a>
|
||||
href="https://patentscope.wipo.int/search/zh/detail.jsf?docId=CN232281523&_cid=P20-M8L0YX-67061-1"
|
||||
target="_blank"
|
||||
>专利号CN108648692
|
||||
</a>
|
||||
</p>
|
||||
<p class="text-caption text-grey">
|
||||
*技术已自动适配您的设备,无需手动调整
|
||||
@@ -220,71 +220,76 @@
|
||||
|
||||
<!-- 出勤统计区域 -->
|
||||
<v-col
|
||||
v-ripple="{ class: `text-${['primary','secondary','info','success','warning','error'][Math.floor(Math.random()*6)]}` }"
|
||||
v-if="state.studentList && state.studentList.length"
|
||||
v-ripple="{ class: `text-${['primary','secondary','info','success','warning','error'][Math.floor(Math.random()*6)]}` }"
|
||||
class="attendance-area no-select"
|
||||
cols="1"
|
||||
@click="setAttendanceArea()"
|
||||
>
|
||||
<h1>出勤</h1>
|
||||
<h2>
|
||||
<snap style="white-space: nowrap"> 应到 </snap>:
|
||||
<snap style="white-space: nowrap"> 应到</snap>
|
||||
:
|
||||
<snap style="white-space: nowrap">
|
||||
{{
|
||||
state.studentList.length -
|
||||
state.boardData.attendance.exclude.length
|
||||
state.studentList.length -
|
||||
state.boardData.attendance.exclude.length
|
||||
}}人
|
||||
</snap>
|
||||
</h2>
|
||||
<h2>
|
||||
<snap style="white-space: nowrap"> 实到 </snap>:
|
||||
<snap style="white-space: nowrap"> 实到</snap>
|
||||
:
|
||||
<snap style="white-space: nowrap">
|
||||
{{
|
||||
state.studentList.length -
|
||||
state.boardData.attendance.absent.length -
|
||||
state.boardData.attendance.late.length -
|
||||
state.boardData.attendance.exclude.length
|
||||
state.studentList.length -
|
||||
state.boardData.attendance.absent.length -
|
||||
state.boardData.attendance.late.length -
|
||||
state.boardData.attendance.exclude.length
|
||||
}}人
|
||||
</snap>
|
||||
</h2>
|
||||
<h2>
|
||||
<snap style="white-space: nowrap"> 请假 </snap>:
|
||||
<snap style="white-space: nowrap"> 请假</snap>
|
||||
:
|
||||
<snap style="white-space: nowrap">
|
||||
{{ state.boardData.attendance.absent.length }}人
|
||||
</snap>
|
||||
</h2>
|
||||
<h3
|
||||
class="gray-text"
|
||||
v-for="(name, index) in state.boardData.attendance.absent"
|
||||
:key="'absent-' + index"
|
||||
class="gray-text"
|
||||
>
|
||||
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
|
||||
><span style="white-space: nowrap">{{ name }}</span>
|
||||
</h3>
|
||||
<h2>
|
||||
<snap style="white-space: nowrap">迟到</snap>:
|
||||
<snap style="white-space: nowrap">迟到</snap>
|
||||
:
|
||||
<snap style="white-space: nowrap">
|
||||
{{ state.boardData.attendance.late.length }}人
|
||||
</snap>
|
||||
</h2>
|
||||
<h3
|
||||
class="gray-text"
|
||||
v-for="(name, index) in state.boardData.attendance.late"
|
||||
:key="'late-' + index"
|
||||
class="gray-text"
|
||||
>
|
||||
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
|
||||
><span style="white-space: nowrap">{{ name }}</span>
|
||||
</h3>
|
||||
<h2>
|
||||
<snap style="white-space: nowrap">不参与</snap>:
|
||||
<snap style="white-space: nowrap">不参与</snap>
|
||||
:
|
||||
<snap style="white-space: nowrap">
|
||||
{{ state.boardData.attendance.exclude.length }}人
|
||||
</snap>
|
||||
</h2>
|
||||
<h3
|
||||
class="gray-text"
|
||||
v-for="(name, index) in state.boardData.attendance.exclude"
|
||||
:key="'exclude-' + index"
|
||||
class="gray-text"
|
||||
>
|
||||
<span v-if="useDisplay().lgAndUp.value">{{ `${index + 1}. ` }}</span
|
||||
><span style="white-space: nowrap">{{ name }}</span>
|
||||
@@ -294,9 +299,9 @@
|
||||
|
||||
<homework-edit-dialog
|
||||
v-model="state.dialogVisible"
|
||||
:title="state.dialogTitle"
|
||||
:initial-content="state.textarea"
|
||||
:auto-save="autoSave"
|
||||
:initial-content="state.textarea"
|
||||
:title="state.dialogTitle"
|
||||
@save="handleHomeworkSave"
|
||||
/>
|
||||
|
||||
@@ -306,16 +311,16 @@
|
||||
|
||||
<v-dialog
|
||||
v-model="state.attendanceDialog"
|
||||
max-width="900"
|
||||
fullscreen-breakpoint="sm"
|
||||
max-width="900"
|
||||
@update:model-value="handleAttendanceDialogClose"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon icon="mdi-account-group" class="mr-2" />
|
||||
<v-icon class="mr-2" icon="mdi-account-group"/>
|
||||
出勤状态管理
|
||||
<v-spacer />
|
||||
<v-chip color="primary" size="small" class="ml-2">
|
||||
<v-spacer/>
|
||||
<v-chip class="ml-2" color="primary" size="small">
|
||||
{{ state.dateString }}
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
@@ -326,11 +331,11 @@
|
||||
<v-col cols="12" md="12">
|
||||
<v-text-field
|
||||
v-model="attendanceSearch"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="搜索学生"
|
||||
hint="支持筛选姓氏,如输入'孙'可筛选所有姓孙的学生"
|
||||
variant="outlined"
|
||||
clearable
|
||||
hint="支持筛选姓氏,如输入'孙'可筛选所有姓孙的学生"
|
||||
label="搜索学生"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
@update:model-value="handleSearchChange"
|
||||
/>
|
||||
|
||||
@@ -339,10 +344,10 @@
|
||||
<v-btn
|
||||
v-for="surname in extractedSurnames"
|
||||
:key="surname.name"
|
||||
:color="attendanceSearch === surname.name ? 'primary' : ''"
|
||||
:variant="
|
||||
attendanceSearch === surname.name ? 'elevated' : 'text'
|
||||
"
|
||||
:color="attendanceSearch === surname.name ? 'primary' : ''"
|
||||
@click="
|
||||
attendanceSearch =
|
||||
attendanceSearch === surname.name ? '' : surname.name
|
||||
@@ -359,63 +364,63 @@
|
||||
<div class="d-flex flex-wrap mb-4 gap-2">
|
||||
<div>
|
||||
<v-chip
|
||||
value="present"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('present') ? 'mdi-check' : ''
|
||||
"
|
||||
:color="attendanceFilter.includes('present') ? 'success' : ''"
|
||||
:variant="
|
||||
attendanceFilter.includes('present') ? 'elevated' : 'tonal'
|
||||
"
|
||||
class="px-2 filter-chip"
|
||||
@click="toggleFilter('present')"
|
||||
prepend-icon="mdi-account-check"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('present') ? 'mdi-check' : ''
|
||||
"
|
||||
value="present"
|
||||
@click="toggleFilter('present')"
|
||||
>
|
||||
到课
|
||||
</v-chip>
|
||||
|
||||
<v-chip
|
||||
value="absent"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('absent') ? 'mdi-check' : ''
|
||||
"
|
||||
:color="attendanceFilter.includes('absent') ? 'error' : ''"
|
||||
:variant="
|
||||
attendanceFilter.includes('absent') ? 'elevated' : 'tonal'
|
||||
"
|
||||
class="px-2 filter-chip"
|
||||
@click="toggleFilter('absent')"
|
||||
prepend-icon="mdi-account-off"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('absent') ? 'mdi-check' : ''
|
||||
"
|
||||
value="absent"
|
||||
@click="toggleFilter('absent')"
|
||||
>
|
||||
请假
|
||||
</v-chip>
|
||||
<v-chip
|
||||
value="late"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('late') ? 'mdi-check' : ''
|
||||
"
|
||||
:color="attendanceFilter.includes('late') ? 'warning' : ''"
|
||||
:variant="
|
||||
attendanceFilter.includes('late') ? 'elevated' : 'tonal'
|
||||
"
|
||||
class="px-2 filter-chip"
|
||||
@click="toggleFilter('late')"
|
||||
prepend-icon="mdi-clock-alert"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('late') ? 'mdi-check' : ''
|
||||
"
|
||||
value="late"
|
||||
@click="toggleFilter('late')"
|
||||
>
|
||||
迟到
|
||||
</v-chip>
|
||||
<v-chip
|
||||
value="exclude"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('exclude') ? 'mdi-check' : ''
|
||||
"
|
||||
:color="attendanceFilter.includes('exclude') ? 'grey' : ''"
|
||||
:variant="
|
||||
attendanceFilter.includes('exclude') ? 'elevated' : 'tonal'
|
||||
"
|
||||
class="px-2 filter-chip"
|
||||
@click="toggleFilter('exclude')"
|
||||
prepend-icon="mdi-account-cancel"
|
||||
:append-icon="
|
||||
attendanceFilter.includes('exclude') ? 'mdi-check' : ''
|
||||
"
|
||||
value="exclude"
|
||||
@click="toggleFilter('exclude')"
|
||||
>
|
||||
不参与
|
||||
</v-chip>
|
||||
@@ -428,11 +433,11 @@
|
||||
v-for="student in filteredStudents"
|
||||
:key="student"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="6"
|
||||
lg="4"
|
||||
md="6"
|
||||
sm="6"
|
||||
>
|
||||
<v-card class="student-card" border>
|
||||
<v-card border class="student-card">
|
||||
<v-card-text class="d-flex align-center pa-2">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-center">
|
||||
@@ -442,12 +447,13 @@
|
||||
state.studentList.indexOf(student)
|
||||
)
|
||||
"
|
||||
size="24"
|
||||
class="mr-2"
|
||||
size="24"
|
||||
>
|
||||
<v-icon size="small">{{
|
||||
getStudentStatusIcon(state.studentList.indexOf(student))
|
||||
}}</v-icon>
|
||||
}}
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
<div class="text-subtitle-1">{{ student }}</div>
|
||||
</div>
|
||||
@@ -459,11 +465,11 @@
|
||||
? 'success'
|
||||
: ''
|
||||
"
|
||||
:title="'设为到课'"
|
||||
icon="mdi-account-check"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="setPresent(state.studentList.indexOf(student))"
|
||||
:title="'设为到课'"
|
||||
/>
|
||||
<v-btn
|
||||
:color="
|
||||
@@ -471,11 +477,11 @@
|
||||
? 'error'
|
||||
: ''
|
||||
"
|
||||
:title="'设为请假'"
|
||||
icon="mdi-account-off"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="setAbsent(state.studentList.indexOf(student))"
|
||||
:title="'设为请假'"
|
||||
/>
|
||||
<v-btn
|
||||
:color="
|
||||
@@ -483,11 +489,11 @@
|
||||
? 'warning'
|
||||
: ''
|
||||
"
|
||||
:title="'设为迟到'"
|
||||
icon="mdi-clock-alert"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="setLate(state.studentList.indexOf(student))"
|
||||
:title="'设为迟到'"
|
||||
/>
|
||||
<v-btn
|
||||
:color="
|
||||
@@ -495,11 +501,11 @@
|
||||
? 'grey'
|
||||
: ''
|
||||
"
|
||||
:title="'设为不参与'"
|
||||
icon="mdi-account-cancel"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="setExclude(state.studentList.indexOf(student))"
|
||||
:title="'设为不参与'"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
@@ -508,7 +514,7 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" md="12">
|
||||
<v-card variant="tonal" color="primary" class="mb-4">
|
||||
<v-card class="mb-4" color="primary" variant="tonal">
|
||||
<v-card-text>
|
||||
<div class="text-subtitle-2 mb-2">批量操作</div>
|
||||
<v-btn-group>
|
||||
@@ -545,14 +551,15 @@
|
||||
</v-btn-group>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col></v-row
|
||||
</v-col>
|
||||
</v-row
|
||||
>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
<v-divider/>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
|
||||
<v-btn color="primary" @click="saveAttendance">
|
||||
<v-icon start>mdi-content-save</v-icon>
|
||||
@@ -562,16 +569,16 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<message-log ref="messageLog" />
|
||||
<message-log ref="messageLog"/>
|
||||
|
||||
<!-- 添加悬浮工具栏 -->
|
||||
<floating-toolbar
|
||||
:loading="loading.download"
|
||||
:unread-count="unreadCount"
|
||||
:selected-date="state.selectedDateObj"
|
||||
:is-today="isToday"
|
||||
@zoom="zoom"
|
||||
:loading="loading.download"
|
||||
:selected-date="state.selectedDateObj"
|
||||
:unread-count="unreadCount"
|
||||
@refresh="downloadData"
|
||||
@zoom="zoom"
|
||||
@open-messages="$refs.messageLog.drawer = true"
|
||||
@open-settings="$router.push('/settings')"
|
||||
@date-select="handleDateSelect"
|
||||
@@ -580,24 +587,24 @@
|
||||
/>
|
||||
|
||||
<!-- 添加ICP备案悬浮组件 -->
|
||||
<FloatingICP />
|
||||
<FloatingICP/>
|
||||
|
||||
<!-- 设备聊天室(右下角浮窗) -->
|
||||
<ChatWidget v-model="isChatOpen" :show-button="false" />
|
||||
<ChatWidget v-model="isChatOpen" :show-button="false"/>
|
||||
|
||||
<!-- 添加确认对话框 -->
|
||||
<v-dialog v-model="confirmDialog.show" max-width="400">
|
||||
<v-card>
|
||||
<v-card-title class="text-h6"> 确认保存 </v-card-title>
|
||||
<v-card-title class="text-h6"> 确认保存</v-card-title>
|
||||
<v-card-text>
|
||||
您正在修改 {{ state.dateString }} 的数据,确定要保存吗?
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
<v-btn color="grey" variant="text" @click="confirmDialog.reject">
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="confirmDialog.resolve"> 确认保存 </v-btn>
|
||||
<v-btn color="primary" @click="confirmDialog.resolve"> 确认保存</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
@@ -605,14 +612,14 @@
|
||||
<!-- 添加随机点名组件 -->
|
||||
<random-picker
|
||||
ref="randomPicker"
|
||||
:student-list="state.studentList"
|
||||
:attendance="state.boardData.attendance"
|
||||
:student-list="state.studentList"
|
||||
/>
|
||||
|
||||
<!-- 添加URL配置确认对话框 -->
|
||||
<v-dialog v-model="urlConfigDialog.show" max-width="500">
|
||||
<v-card>
|
||||
<v-card-title class="text-h6"> 确认应用URL配置 </v-card-title>
|
||||
<v-card-title class="text-h6"> 确认应用URL配置</v-card-title>
|
||||
<v-card-text>
|
||||
<p>以下配置将应用于当前班级:</p>
|
||||
<v-list density="compact">
|
||||
@@ -621,17 +628,18 @@
|
||||
:key="change.key"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon :icon="change.icon" size="small" class="mr-2" />
|
||||
<v-icon :icon="change.icon" class="mr-2" size="small"/>
|
||||
</template>
|
||||
<v-list-item-title class="d-flex align-center">
|
||||
<span class="text-subtitle-1">{{ change.name }}</span>
|
||||
<v-tooltip activator="parent" location="top">{{
|
||||
change.description || change.key
|
||||
}}</v-tooltip>
|
||||
}}
|
||||
</v-tooltip>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<span class="text-grey-darken-1">{{ change.oldValue }}</span>
|
||||
<v-icon icon="mdi-arrow-right" size="small" class="mx-1" />
|
||||
<v-icon class="mx-1" icon="mdi-arrow-right" size="small"/>
|
||||
<span class="text-primary font-weight-medium">{{
|
||||
change.newValue
|
||||
}}</span>
|
||||
@@ -640,7 +648,7 @@
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-spacer/>
|
||||
<v-btn
|
||||
color="grey"
|
||||
variant="text"
|
||||
@@ -652,8 +660,10 @@
|
||||
确认应用
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card> </v-dialog
|
||||
><br /><br /><br />
|
||||
</v-card>
|
||||
</v-dialog
|
||||
>
|
||||
<br/><br/><br/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -672,14 +682,14 @@ import {
|
||||
setSetting,
|
||||
settingsDefinitions,
|
||||
} from "@/utils/settings";
|
||||
import { kvServerProvider } from "@/utils/providers/kvServerProvider";
|
||||
import { useDisplay } from "vuetify";
|
||||
import {kvServerProvider} from "@/utils/providers/kvServerProvider";
|
||||
import {useDisplay} from "vuetify";
|
||||
import "../styles/index.scss";
|
||||
import "../styles/transitions.scss";
|
||||
import "../styles/global.scss";
|
||||
import { pinyin } from "pinyin-pro";
|
||||
import { debounce, throttle } from "@/utils/debounce";
|
||||
import { Base64 } from "js-base64";
|
||||
import {pinyin} from "pinyin-pro";
|
||||
import {debounce, throttle} from "@/utils/debounce";
|
||||
import {Base64} from "js-base64";
|
||||
import {
|
||||
getSocket,
|
||||
on as socketOn,
|
||||
@@ -702,16 +712,16 @@ export default {
|
||||
},
|
||||
data() {
|
||||
const defaultSubjects = [
|
||||
{ name: "语文", order: 0 },
|
||||
{ name: "数学", order: 1 },
|
||||
{ name: "英语", order: 2 },
|
||||
{ name: "物理", order: 3 },
|
||||
{ name: "化学", order: 4 },
|
||||
{ name: "生物", order: 5 },
|
||||
{ name: "政治", order: 6 },
|
||||
{ name: "历史", order: 7 },
|
||||
{ name: "地理", order: 8 },
|
||||
{ name: "其他", order: 9 },
|
||||
{name: "语文", order: 0},
|
||||
{name: "数学", order: 1},
|
||||
{name: "英语", order: 2},
|
||||
{name: "物理", order: 3},
|
||||
{name: "化学", order: 4},
|
||||
{name: "生物", order: 5},
|
||||
{name: "政治", order: 6},
|
||||
{name: "历史", order: 7},
|
||||
{name: "地理", order: 8},
|
||||
{name: "其他", order: 9},
|
||||
];
|
||||
|
||||
return {
|
||||
@@ -738,7 +748,7 @@ export default {
|
||||
dateString: "",
|
||||
synced: false,
|
||||
attendDialogVisible: false,
|
||||
contentStyle: { "font-size": `${getSetting("font.size")}px` },
|
||||
contentStyle: {"font-size": `${getSetting("font.size")}px`},
|
||||
uploadLoading: false,
|
||||
downloadLoading: false,
|
||||
snackbar: false,
|
||||
@@ -822,7 +832,7 @@ export default {
|
||||
// 优先展示当前设备名称(如果已从云端获取)
|
||||
const deviceName =
|
||||
this.state.namespaceInfo?.device?.name ||
|
||||
this.state.classNumber ||
|
||||
this.state.classNumber ||
|
||||
"高三八班";
|
||||
|
||||
const today = this.getToday();
|
||||
@@ -834,7 +844,7 @@ export default {
|
||||
const yesterdayStr = this.formatDate(yesterday);
|
||||
|
||||
if (currentDateStr === todayStr) {
|
||||
return deviceName +" - 今天的作业";
|
||||
return deviceName + " - 今天的作业";
|
||||
} else if (currentDateStr === yesterdayStr) {
|
||||
return deviceName + " - 昨天的作业";
|
||||
} else {
|
||||
@@ -861,7 +871,7 @@ export default {
|
||||
rowSpan: Math.ceil(
|
||||
(value.content.split("\n").filter((line) => line.trim()).length +
|
||||
1) *
|
||||
0.8
|
||||
0.8
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -1011,10 +1021,10 @@ export default {
|
||||
});
|
||||
|
||||
return Array.from(surnameMap.entries())
|
||||
.map(([name, count]) => ({ name, count }))
|
||||
.map(([name, count]) => ({name, count}))
|
||||
.sort((a, b) => {
|
||||
const pinyinA = pinyin(a.name, { toneType: "none", mode: "surname" });
|
||||
const pinyinB = pinyin(b.name, { toneType: "none", mode: "surname" });
|
||||
const pinyinA = pinyin(a.name, {toneType: "none", mode: "surname"});
|
||||
const pinyinB = pinyin(b.name, {toneType: "none", mode: "surname"});
|
||||
return pinyinA.localeCompare(pinyinB);
|
||||
});
|
||||
},
|
||||
@@ -1300,7 +1310,7 @@ export default {
|
||||
if (forceClear || !this.state.boardData || (!this.state.boardData.homework && !this.state.boardData.attendance)) {
|
||||
this.state.boardData = {
|
||||
homework: {},
|
||||
attendance: { absent: [], late: [], exclude: [] },
|
||||
attendance: {absent: [], late: [], exclude: []},
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -1327,7 +1337,7 @@ export default {
|
||||
if (forceClear || !this.state.boardData || (!this.state.boardData.homework && !this.state.boardData.attendance)) {
|
||||
this.state.boardData = {
|
||||
homework: {},
|
||||
attendance: { absent: [], late: [], exclude: [] },
|
||||
attendance: {absent: [], late: [], exclude: []},
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
@@ -1586,7 +1596,7 @@ export default {
|
||||
|
||||
updateSettings() {
|
||||
this.state.fontSize = getSetting("font.size");
|
||||
this.state.contentStyle = { "font-size": `${this.state.fontSize}px` };
|
||||
this.state.contentStyle = {"font-size": `${this.state.fontSize}px`};
|
||||
this.setupAutoRefresh();
|
||||
this.updateBackendUrl();
|
||||
// 设置更新时尝试刷新设备名称(例如 Token 或域名变更)
|
||||
@@ -1611,9 +1621,10 @@ export default {
|
||||
|
||||
this.$router
|
||||
.replace({
|
||||
query: { date: formattedDate },
|
||||
query: {date: formattedDate},
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => {
|
||||
});
|
||||
|
||||
// Load both data and subjects in parallel, force clear data when switching dates
|
||||
await Promise.all([this.downloadData(true), this.loadSubjects()]);
|
||||
@@ -1628,7 +1639,7 @@ export default {
|
||||
const maxColumns = Math.min(3, Math.floor(window.innerWidth / 300));
|
||||
if (maxColumns <= 1) return items;
|
||||
|
||||
const columns = Array.from({ length: maxColumns }, () => ({
|
||||
const columns = Array.from({length: maxColumns}, () => ({
|
||||
height: 0,
|
||||
items: [],
|
||||
}));
|
||||
@@ -1756,7 +1767,7 @@ export default {
|
||||
|
||||
isPresent(index) {
|
||||
const student = this.state.studentList[index];
|
||||
const { absent, late, exclude } = this.state.boardData.attendance;
|
||||
const {absent, late, exclude} = this.state.boardData.attendance;
|
||||
return (
|
||||
!absent.includes(student) &&
|
||||
!late.includes(student) &&
|
||||
|
||||
+245
-242
@@ -1,260 +1,263 @@
|
||||
<template><v-app-bar elevation="1">
|
||||
<template #prepend>
|
||||
<v-btn
|
||||
icon="mdi-arrow-left"
|
||||
variant="text"
|
||||
@click="$router.push('/')"
|
||||
/>
|
||||
</template>
|
||||
<v-app-bar-title class="text-h6" v-if="list && !isRenaming">{{ list.name }}</v-app-bar-title>
|
||||
<v-app-bar-title class="text-h6" v-else>列表</v-app-bar-title>
|
||||
</v-app-bar>
|
||||
<template>
|
||||
<v-app-bar elevation="1">
|
||||
<template #prepend>
|
||||
<v-btn
|
||||
icon="mdi-arrow-left"
|
||||
variant="text"
|
||||
@click="$router.push('/')"
|
||||
/>
|
||||
</template>
|
||||
<v-app-bar-title v-if="list && !isRenaming" class="text-h6">{{ list.name }}</v-app-bar-title>
|
||||
<v-app-bar-title v-else class="text-h6">列表</v-app-bar-title>
|
||||
</v-app-bar>
|
||||
<v-container>
|
||||
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-btn
|
||||
icon
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-btn
|
||||
border
|
||||
class="mr-2"
|
||||
icon
|
||||
to="/list"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<h1 v-if="list && !isRenaming">
|
||||
{{ list.name }}
|
||||
<v-btn border icon size="small" @click="startRenaming">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</h1>
|
||||
<div v-else-if="list && isRenaming" class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="newListName"
|
||||
autofocus
|
||||
class="mr-2"
|
||||
to="/list"
|
||||
border
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
density="compact"
|
||||
hide-details
|
||||
label="列表名称"
|
||||
style="min-width: 200px;"
|
||||
@keyup.enter="saveListName"
|
||||
></v-text-field>
|
||||
<v-btn class="mr-2" color="primary" size="small" @click="saveListName">
|
||||
<v-icon>mdi-check</v-icon>
|
||||
</v-btn>
|
||||
<v-btn color="error" size="small" @click="cancelRenaming">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<h1 v-if="list && !isRenaming">
|
||||
{{ list.name }}
|
||||
<v-btn icon size="small" @click="startRenaming" border>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
</h1>
|
||||
<div v-else-if="list && isRenaming" class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="newListName"
|
||||
label="列表名称"
|
||||
hide-details
|
||||
density="compact"
|
||||
class="mr-2"
|
||||
style="min-width: 200px;"
|
||||
autofocus
|
||||
@keyup.enter="saveListName"
|
||||
></v-text-field>
|
||||
<v-btn color="primary" size="small" class="mr-2" @click="saveListName">
|
||||
<v-icon>mdi-check</v-icon>
|
||||
</v-btn>
|
||||
<v-btn color="error" size="small" @click="cancelRenaming">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<h1 v-else>
|
||||
加载中...
|
||||
</h1>
|
||||
</div>
|
||||
<h1 v-else>
|
||||
加载中...
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<v-card class="mb-5" border rounded="xl">
|
||||
<v-card-title class="d-flex align-center">
|
||||
项目列表
|
||||
<v-spacer />
|
||||
<v-btn-toggle
|
||||
v-model="sortType"
|
||||
mandatory
|
||||
>
|
||||
<v-btn value="default">
|
||||
<v-icon>mdi-sort-alphabetical-ascending</v-icon>
|
||||
</v-btn>
|
||||
<v-btn value="completed">
|
||||
<v-icon>mdi-check-circle-outline</v-icon>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-card-title>
|
||||
<v-card-text v-if="sortedItems.length === 0">
|
||||
暂无项目,请添加新项目
|
||||
</v-card-text>
|
||||
<v-list
|
||||
v-else
|
||||
select-strategy="leaf"
|
||||
<v-card border class="mb-5" rounded="xl">
|
||||
<v-card-title class="d-flex align-center">
|
||||
项目列表
|
||||
<v-spacer/>
|
||||
<v-btn-toggle
|
||||
v-model="sortType"
|
||||
mandatory
|
||||
>
|
||||
<v-list-item
|
||||
v-for="(item, index) in sortedItems"
|
||||
:key="item.id"
|
||||
:class="{ 'text-decoration-line-through': item.completed }"
|
||||
@click="openItemDetails(item)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-list-item-action start>
|
||||
<v-checkbox-btn
|
||||
:model-value="item.completed"
|
||||
@update:model-value="updateItemStatus(item.id, $event)"
|
||||
@click.stop
|
||||
/>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
{{ item.name }}
|
||||
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-card-actions v-if="sortedItems.length > 0">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="error"
|
||||
prepend-icon="mdi-delete-sweep"
|
||||
@click="confirmDeleteCompleted"
|
||||
:disabled="!hasCompletedItems"
|
||||
>
|
||||
删除已完成项目
|
||||
<v-btn value="default">
|
||||
<v-icon>mdi-sort-alphabetical-ascending</v-icon>
|
||||
</v-btn>
|
||||
<v-btn value="completed">
|
||||
<v-icon>mdi-check-circle-outline</v-icon>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-card-title>
|
||||
<v-card-text v-if="sortedItems.length === 0">
|
||||
暂无项目,请添加新项目
|
||||
</v-card-text>
|
||||
<v-list
|
||||
v-else
|
||||
select-strategy="leaf"
|
||||
>
|
||||
<v-list-item
|
||||
v-for="(item, index) in sortedItems"
|
||||
:key="item.id"
|
||||
:class="{ 'text-decoration-line-through': item.completed }"
|
||||
@click="openItemDetails(item)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-list-item-action start>
|
||||
<v-checkbox-btn
|
||||
:model-value="item.completed"
|
||||
@update:model-value="updateItemStatus(item.id, $event)"
|
||||
@click.stop
|
||||
/>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
{{ item.name }}
|
||||
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-card-actions v-if="sortedItems.length > 0">
|
||||
<v-spacer/>
|
||||
<v-btn
|
||||
:disabled="!hasCompletedItems"
|
||||
color="error"
|
||||
prepend-icon="mdi-delete-sweep"
|
||||
@click="confirmDeleteCompleted"
|
||||
>
|
||||
删除已完成项目
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<v-card border class="mb-5" rounded="xl">
|
||||
<v-card-title>添加新项目</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="newItemName"
|
||||
:rules="[(v) => !!v || '名称不能为空']"
|
||||
label="项目名称"
|
||||
/>
|
||||
<v-btn
|
||||
:disabled="!newItemName"
|
||||
color="primary"
|
||||
@click="addItem"
|
||||
>
|
||||
添加
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card border class="mb-5" rounded="xl">
|
||||
<v-card-title>列表排序</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="sortSeed"
|
||||
class="mb-3"
|
||||
hint="输入相同的种子值可以得到相同的排序结果"
|
||||
label="排序种子 (任意数字或文本)"
|
||||
persistent-hint
|
||||
/>
|
||||
<v-btn
|
||||
class="mr-2"
|
||||
color="primary"
|
||||
@click="randomSort"
|
||||
>
|
||||
随机排序
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="resetSort"
|
||||
>
|
||||
撤销
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 确认删除对话框 -->
|
||||
<v-dialog v-model="deleteDialog.show" max-width="500">
|
||||
<v-card border rounded="xl">
|
||||
<v-card-title>{{ deleteDialog.title }}</v-card-title>
|
||||
<v-card-text>{{ deleteDialog.text }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" variant="text" @click="deleteDialog.show = false">
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn color="error" variant="text" @click="confirmDelete">
|
||||
确认删除
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card><v-card class="mb-5" border rounded="xl">
|
||||
<v-card-title>添加新项目</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="newItemName"
|
||||
label="项目名称"
|
||||
:rules="[(v) => !!v || '名称不能为空']"
|
||||
/>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:disabled="!newItemName"
|
||||
@click="addItem"
|
||||
>
|
||||
添加
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 项目详情对话框 -->
|
||||
<v-dialog v-model="itemDialog.show" max-width="600">
|
||||
<v-card border rounded="xl">
|
||||
<v-card-title>
|
||||
<span v-if="!itemDialog.isEditing">项目详情</span>
|
||||
<span v-else>编辑项目</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card class="mb-5" border rounded="xl">
|
||||
<v-card-title>列表排序</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="sortSeed"
|
||||
label="排序种子 (任意数字或文本)"
|
||||
hint="输入相同的种子值可以得到相同的排序结果"
|
||||
persistent-hint
|
||||
class="mb-3"
|
||||
/>
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="mr-2"
|
||||
@click="randomSort"
|
||||
>
|
||||
随机排序
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="resetSort"
|
||||
>
|
||||
撤销
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<div v-if="!itemDialog.isEditing && itemDialog.item">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-subtitle-1 font-weight-bold">{{ itemDialog.item.name }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ itemDialog.item.id }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<!-- 确认删除对话框 -->
|
||||
<v-dialog v-model="deleteDialog.show" max-width="500">
|
||||
<v-card border rounded="xl">
|
||||
<v-card-title>{{ deleteDialog.title }}</v-card-title>
|
||||
<v-card-text>{{ deleteDialog.text }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" variant="text" @click="deleteDialog.show = false">
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-subtitle-1 font-weight-bold">状态</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<v-chip
|
||||
:color="itemDialog.item.completed ? 'success' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
{{ itemDialog.item.completed ? '已完成' : '未完成' }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item v-if="itemDialog.item.description">
|
||||
<v-list-item-title class="text-subtitle-1 font-weight-bold">描述</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ itemDialog.item.description }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
</v-list>
|
||||
</div>
|
||||
|
||||
<div v-else-if="itemDialog.isEditing && itemDialog.item" class="pa-2">
|
||||
<v-text-field
|
||||
v-model="itemDialog.editedItem.name"
|
||||
class="mb-3"
|
||||
label="名称"
|
||||
variant="outlined"
|
||||
></v-text-field>
|
||||
|
||||
<v-textarea
|
||||
v-model="itemDialog.editedItem.description"
|
||||
class="mb-3"
|
||||
label="描述"
|
||||
rows="3"
|
||||
variant="outlined"
|
||||
></v-textarea>
|
||||
|
||||
|
||||
<v-switch
|
||||
v-model="itemDialog.editedItem.completed"
|
||||
color="success"
|
||||
hide-details
|
||||
label="已完成"
|
||||
></v-switch>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<template v-if="!itemDialog.isEditing">
|
||||
<v-btn color="primary" variant="text" @click="startEditingItem">
|
||||
编辑
|
||||
</v-btn>
|
||||
<v-btn color="error" variant="text" @click="confirmDeleteItem(itemDialog.item?.id)">
|
||||
删除
|
||||
</v-btn>
|
||||
<v-btn color="secondary" variant="text" @click="itemDialog.show = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<v-btn color="success" variant="text" @click="saveItemChanges">
|
||||
保存
|
||||
</v-btn>
|
||||
<v-btn color="secondary" variant="text" @click="cancelEditingItem">
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn color="error" variant="text" @click="confirmDelete">
|
||||
确认删除
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 项目详情对话框 -->
|
||||
<v-dialog v-model="itemDialog.show" max-width="600">
|
||||
<v-card border rounded="xl">
|
||||
<v-card-title>
|
||||
<span v-if="!itemDialog.isEditing">项目详情</span>
|
||||
<span v-else>编辑项目</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<div v-if="!itemDialog.isEditing && itemDialog.item">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-subtitle-1 font-weight-bold">{{ itemDialog.item.name }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ itemDialog.item.id }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<v-list-item-title class="text-subtitle-1 font-weight-bold">状态</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<v-chip
|
||||
:color="itemDialog.item.completed ? 'success' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
{{ itemDialog.item.completed ? '已完成' : '未完成' }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item v-if="itemDialog.item.description">
|
||||
<v-list-item-title class="text-subtitle-1 font-weight-bold">描述</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ itemDialog.item.description }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
</v-list>
|
||||
</div>
|
||||
|
||||
<div v-else-if="itemDialog.isEditing && itemDialog.item" class="pa-2">
|
||||
<v-text-field
|
||||
v-model="itemDialog.editedItem.name"
|
||||
label="名称"
|
||||
variant="outlined"
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
|
||||
<v-textarea
|
||||
v-model="itemDialog.editedItem.description"
|
||||
label="描述"
|
||||
variant="outlined"
|
||||
rows="3"
|
||||
class="mb-3"
|
||||
></v-textarea>
|
||||
|
||||
|
||||
<v-switch
|
||||
v-model="itemDialog.editedItem.completed"
|
||||
label="已完成"
|
||||
color="success"
|
||||
hide-details
|
||||
></v-switch>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<template v-if="!itemDialog.isEditing">
|
||||
<v-btn color="primary" variant="text" @click="startEditingItem">
|
||||
编辑
|
||||
</v-btn>
|
||||
<v-btn color="error" variant="text" @click="confirmDeleteItem(itemDialog.item?.id)">
|
||||
删除
|
||||
</v-btn>
|
||||
<v-btn color="secondary" variant="text" @click="itemDialog.show = false">
|
||||
关闭
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<v-btn color="success" variant="text" @click="saveItemChanges">
|
||||
保存
|
||||
</v-btn>
|
||||
<v-btn color="secondary" variant="text" @click="cancelEditingItem">
|
||||
取消
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
@@ -544,7 +547,7 @@ export default {
|
||||
|
||||
// 移除临时的randomValue属性并更新items
|
||||
this.items = itemsWithRandom.map((item) => {
|
||||
const newItem = { ...item };
|
||||
const newItem = {...item};
|
||||
delete newItem.randomValue;
|
||||
return newItem;
|
||||
});
|
||||
|
||||
+18
-17
@@ -1,4 +1,5 @@
|
||||
<template><v-app-bar elevation="1">
|
||||
<template>
|
||||
<v-app-bar elevation="1">
|
||||
<template #prepend>
|
||||
<v-btn
|
||||
icon="mdi-arrow-left"
|
||||
@@ -7,9 +8,8 @@
|
||||
/>
|
||||
</template>
|
||||
<v-app-bar-title class="text-h6">列表</v-app-bar-title>
|
||||
</v-app-bar><v-container>
|
||||
|
||||
|
||||
</v-app-bar>
|
||||
<v-container>
|
||||
|
||||
|
||||
<v-card border class="mb-5" rounded="xl">
|
||||
@@ -21,8 +21,8 @@
|
||||
<v-list-item
|
||||
v-for="list in lists"
|
||||
:key="list.id"
|
||||
:to="list.id !== editingListId ? `/list/${list.id}` : undefined"
|
||||
:active="list.id === editingListId"
|
||||
:to="list.id !== editingListId ? `/list/${list.id}` : undefined"
|
||||
>
|
||||
<div v-if="list.id !== editingListId">
|
||||
<v-list-item-title>{{ list.name }}</v-list-item-title>
|
||||
@@ -30,27 +30,27 @@
|
||||
<div v-else class="d-flex align-center w-100">
|
||||
<v-text-field
|
||||
v-model="editListName"
|
||||
label="列表名称"
|
||||
hide-details
|
||||
density="compact"
|
||||
class="mr-2"
|
||||
autofocus
|
||||
class="mr-2"
|
||||
density="compact"
|
||||
hide-details
|
||||
label="列表名称"
|
||||
@keyup.enter="saveListName"
|
||||
></v-text-field>
|
||||
<v-btn icon color="primary" @click.stop.prevent="saveListName" class="mr-2" border>
|
||||
<v-btn border class="mr-2" color="primary" icon @click.stop.prevent="saveListName">
|
||||
<v-icon>mdi-check</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="error" @click.stop.prevent="cancelEditing" border>
|
||||
<v-btn border color="error" icon @click.stop.prevent="cancelEditing">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<template #append>
|
||||
<div v-if="list.id !== editingListId">
|
||||
<v-btn icon @click.stop.prevent="startEditing(list.id)" class="mr-2" border>
|
||||
<v-btn border class="mr-2" icon @click.stop.prevent="startEditing(list.id)">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon @click.stop.prevent="confirmDeleteList(list.id)" border>
|
||||
<v-btn border icon @click.stop.prevent="confirmDeleteList(list.id)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
@@ -58,15 +58,15 @@
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
<v-card class="mb-5" border rounded="xl">
|
||||
<v-card border class="mb-5" rounded="xl">
|
||||
<v-card-title>创建新列表</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="newListName"
|
||||
label="列表名称"
|
||||
:rules="[v => !!v || '名称不能为空']"
|
||||
label="列表名称"
|
||||
></v-text-field>
|
||||
<v-btn color="primary" @click="createNewList" :disabled="!newListName">
|
||||
<v-btn :disabled="!newListName" color="primary" @click="createNewList">
|
||||
创建列表
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
@@ -87,11 +87,12 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataProvider from "@/utils/dataProvider.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
+74
-59
@@ -27,11 +27,11 @@
|
||||
<v-list-item
|
||||
v-for="tab in settingsTabs"
|
||||
:key="tab.value"
|
||||
@click="settingsTab = tab.value"
|
||||
:active="settingsTab === tab.value"
|
||||
:color="settingsTab === tab.value ? 'primary' : 'default'"
|
||||
:prepend-icon="tab.icon"
|
||||
class="rounded-e-xl"
|
||||
:color="settingsTab === tab.value ? 'primary' : 'default'"
|
||||
@click="settingsTab = tab.value"
|
||||
>
|
||||
<v-list-item-title>{{ tab.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -40,11 +40,13 @@
|
||||
|
||||
<v-tabs-window
|
||||
v-model="settingsTab"
|
||||
style="width: 100%"
|
||||
direction="vertical"
|
||||
style="width: 100%"
|
||||
>
|
||||
<v-tabs-window-item value="index"
|
||||
><v-card class="service-card gradient-right clickable mb-4" elevation="8" rounded="xl" border hover @click="openClassworksKV" color="primary" variant="tonal">
|
||||
>
|
||||
<v-card border class="service-card gradient-right clickable mb-4" color="primary" elevation="8" hover
|
||||
rounded="xl" variant="tonal" @click="openClassworksKV">
|
||||
<v-card-item>
|
||||
<div class="card-title">
|
||||
<div>
|
||||
@@ -58,10 +60,10 @@
|
||||
<v-card-text>
|
||||
<div class="mt-4">
|
||||
<v-btn
|
||||
variant="text"
|
||||
class="text-none"
|
||||
append-icon="mdi-arrow-right"
|
||||
class="text-none"
|
||||
rounded="xl"
|
||||
variant="text"
|
||||
@click="openClassworksKV"
|
||||
>
|
||||
打开 Classworks KV
|
||||
@@ -69,126 +71,137 @@
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card title="Classworks" subtitle="设置" class="rounded-xl mb-4" border>
|
||||
<v-card border class="rounded-xl mb-4" subtitle="设置" title="Classworks">
|
||||
<v-card-text>
|
||||
<v-alert
|
||||
color="error"
|
||||
variant="tonal"
|
||||
icon="mdi-alert-circle"
|
||||
class="rounded-xl"
|
||||
>Classworks
|
||||
color="error"
|
||||
icon="mdi-alert-circle"
|
||||
variant="tonal"
|
||||
>Classworks
|
||||
是开源免费的软件,官方没有提供任何形式的付费支持服务,源代码仓库地址在
|
||||
<a
|
||||
href="https://github.com/ZeroCatDev/Classworks"
|
||||
target="_blank"
|
||||
>https://github.com/ZeroCatDev/Classworks</a
|
||||
>。如果您通过有偿协助等付费方式取得本应用,在遇到问题时请在与卖家约定的服务框架下,优先向卖家求助。如果卖家没有提供您预期的服务,请退款或通过其它形式积极维护您的合法权益。</v-alert
|
||||
>https://github.com/ZeroCatDev/Classworks</a
|
||||
>。如果您通过有偿协助等付费方式取得本应用,在遇到问题时请在与卖家约定的服务框架下,优先向卖家求助。如果卖家没有提供您预期的服务,请退款或通过其它形式积极维护您的合法权益。
|
||||
</v-alert
|
||||
>
|
||||
<v-alert
|
||||
class="mt-4 rounded-xl"
|
||||
color="info"
|
||||
variant="tonal"
|
||||
icon="mdi-information"
|
||||
>请不要使用浏览器清除缓存功能,否则会导致配置丢失。<del
|
||||
>恶意的操作可能导致您受到贵校教师的处理</del
|
||||
></v-alert
|
||||
variant="tonal"
|
||||
>请不要使用浏览器清除缓存功能,否则会导致配置丢失。
|
||||
<del
|
||||
>恶意的操作可能导致您受到贵校教师的处理
|
||||
</del
|
||||
>
|
||||
</v-alert
|
||||
>
|
||||
<v-alert
|
||||
class="mt-4 rounded-xl"
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
icon="mdi-information"
|
||||
><p>
|
||||
请不要使用包括但不限于360极速浏览器、360安全浏览器、夸克浏览器、QQ浏览器等浏览器使用
|
||||
Classworks
|
||||
,这些浏览器过时且存在严重的一致性问题。在Windows上,使用新版
|
||||
Microsoft Edge 浏览器是最推荐的选择。
|
||||
</p>
|
||||
variant="tonal"
|
||||
><p>
|
||||
请不要使用包括但不限于360极速浏览器、360安全浏览器、夸克浏览器、QQ浏览器等浏览器使用
|
||||
Classworks
|
||||
,这些浏览器过时且存在严重的一致性问题。在Windows上,使用新版
|
||||
Microsoft Edge 浏览器是最推荐的选择。
|
||||
</p>
|
||||
<p style="color: #666">
|
||||
上述浏览器商标为其所属公司所有,Classworks™
|
||||
与上述浏览器所属公司无竞争关系。
|
||||
</p>
|
||||
<br /><v-btn
|
||||
<br/>
|
||||
<v-btn
|
||||
append-icon="mdi-open-in-new"
|
||||
class="text-none rounded-xl"
|
||||
color="warning"
|
||||
href="https://www.microsoft.com/zh-cn/windows/microsoft-edge"
|
||||
target="_blank"
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
class="text-none rounded-xl"
|
||||
append-icon="mdi-open-in-new"
|
||||
>下载 Microsoft Edge(微软边缘浏览器)</v-btn
|
||||
></v-alert
|
||||
>下载 Microsoft Edge(微软边缘浏览器)
|
||||
</v-btn
|
||||
>
|
||||
</v-alert
|
||||
>
|
||||
</v-card-text>
|
||||
</v-card><about-card />
|
||||
</v-card>
|
||||
<about-card/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="server">
|
||||
<server-settings-card
|
||||
border
|
||||
:loading="loading.server"
|
||||
border
|
||||
@saved="onSettingsSaved"
|
||||
/>
|
||||
<data-provider-settings-card border class="mt-4" />
|
||||
<kv-database-card border class="mt-4" />
|
||||
<data-provider-settings-card border class="mt-4"/>
|
||||
<kv-database-card border class="mt-4"/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="student">
|
||||
<student-list-card border :is-mobile="isMobile" />
|
||||
<student-list-card :is-mobile="isMobile" border/>
|
||||
</v-tabs-window-item>
|
||||
<v-tabs-window-item value="share">
|
||||
<settings-link-generator border class="mt-4" />
|
||||
<settings-link-generator border class="mt-4"/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="refresh">
|
||||
<refresh-settings-card
|
||||
border
|
||||
:loading="loading.refresh"
|
||||
border
|
||||
@saved="onSettingsSaved"
|
||||
/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="edit">
|
||||
<edit-settings-card
|
||||
border
|
||||
:loading="loading.edit"
|
||||
border
|
||||
@saved="onSettingsSaved"
|
||||
/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="display">
|
||||
<display-settings-card
|
||||
border
|
||||
:loading="loading.display"
|
||||
border
|
||||
@saved="onSettingsSaved"
|
||||
/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="theme">
|
||||
<theme-settings-card
|
||||
border
|
||||
:loading="loading.theme"
|
||||
border
|
||||
@saved="onSettingsSaved"
|
||||
/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="randomPicker">
|
||||
<random-picker-card border :is-mobile="isMobile" />
|
||||
<random-picker-card :is-mobile="isMobile" border/>
|
||||
</v-tabs-window-item>
|
||||
<v-tabs-window-item value="subject">
|
||||
<subject-management-card border /> <br />
|
||||
<homework-template-card border />
|
||||
<subject-management-card border/>
|
||||
<br/>
|
||||
<homework-template-card border/>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="developer"
|
||||
><settings-card border title="开发者选项" icon="mdi-developer-board">
|
||||
>
|
||||
<settings-card border icon="mdi-developer-board" title="开发者选项">
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon icon="mdi-code-tags" class="mr-3" />
|
||||
<v-icon class="mr-3" icon="mdi-code-tags"/>
|
||||
</template>
|
||||
<v-list-item-title>启用开发者选项</v-list-item-title>
|
||||
<v-list-item-subtitle
|
||||
>启用后可以查看和修改开发者设置</v-list-item-subtitle
|
||||
>启用后可以查看和修改开发者设置
|
||||
</v-list-item-subtitle
|
||||
>
|
||||
<template #append>
|
||||
<v-switch
|
||||
@@ -202,39 +215,39 @@
|
||||
</v-list>
|
||||
</settings-card>
|
||||
<developer-settings-card
|
||||
border
|
||||
:loading="loading.developer"
|
||||
border
|
||||
@saved="onSettingsSaved"
|
||||
/>
|
||||
<template v-if="settings.developer.enabled">
|
||||
<v-card border class="mt-4 rounded-lg">
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon icon="mdi-cog-outline" class="mr-2" />
|
||||
<v-icon class="mr-2" icon="mdi-cog-outline"/>
|
||||
所有设置
|
||||
</v-card-title>
|
||||
<v-card-subtitle> 浏览和修改所有可用设置 </v-card-subtitle>
|
||||
<v-card-subtitle> 浏览和修改所有可用设置</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<settings-explorer @update="onSettingUpdate" />
|
||||
<settings-explorer @update="onSettingUpdate"/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
<v-col v-if="settings.developer.enabled" cols="12"> </v-col>
|
||||
<v-col v-if="settings.developer.enabled" cols="12"></v-col>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="about">
|
||||
<about-card />
|
||||
<echo-chamber-card border class="mt-4" />
|
||||
<about-card/>
|
||||
<echo-chamber-card border class="mt-4"/>
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
</v-container>
|
||||
|
||||
<!-- 消息记录组件 -->
|
||||
<message-log ref="messageLog" />
|
||||
<message-log ref="messageLog"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDisplay } from "vuetify";
|
||||
import {useDisplay} from "vuetify";
|
||||
import ServerSettingsCard from "@/components/settings/cards/ServerSettingsCard.vue";
|
||||
import EditSettingsCard from "@/components/settings/cards/EditSettingsCard.vue";
|
||||
import RefreshSettingsCard from "@/components/settings/cards/RefreshSettingsCard.vue";
|
||||
@@ -259,6 +272,7 @@ import RandomPickerCard from "@/components/settings/cards/RandomPickerCard.vue";
|
||||
import HomeworkTemplateCard from "@/components/settings/cards/HomeworkTemplateCard.vue";
|
||||
import SubjectManagementCard from "@/components/settings/cards/SubjectManagementCard.vue";
|
||||
import KvDatabaseCard from "@/components/settings/cards/KvDatabaseCard.vue";
|
||||
|
||||
export default {
|
||||
name: "Settings",
|
||||
components: {
|
||||
@@ -281,8 +295,8 @@ export default {
|
||||
KvDatabaseCard,
|
||||
},
|
||||
setup() {
|
||||
const { mobile } = useDisplay();
|
||||
return { isMobile: mobile };
|
||||
const {mobile} = useDisplay();
|
||||
return {isMobile: mobile};
|
||||
},
|
||||
data() {
|
||||
const provider = getSetting("server.provider");
|
||||
@@ -326,8 +340,8 @@ export default {
|
||||
return {
|
||||
settings,
|
||||
dataProviders: [
|
||||
{ title: "服务器", value: "server" },
|
||||
{ title: "本地数据库", value: "indexedDB" },
|
||||
{title: "服务器", value: "server"},
|
||||
{title: "本地数据库", value: "indexedDB"},
|
||||
],
|
||||
studentData: {
|
||||
list: [],
|
||||
@@ -642,6 +656,7 @@ export default {
|
||||
.settings-page {
|
||||
.v-card {
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user