Refactor RandomPicker.vue for improved readability and maintainability. Enhance template formatting by standardizing indentation and spacing, and update button properties for consistency. Add a new button for navigating to the list page in index.vue, and adjust date handling in settings and index pages for better date management. Update dataProvider and settings utility functions for improved response handling and configuration management.
This commit is contained in:
+42
-12
@@ -149,6 +149,16 @@
|
||||
>
|
||||
随机点名
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showListCardButton"
|
||||
color="primary-darken-1"
|
||||
prepend-icon="mdi-list-box"
|
||||
size="large"
|
||||
class="ml-2"
|
||||
@click="$router.push('/list')"
|
||||
>
|
||||
列表
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="showFullscreenButton"
|
||||
:color="state.isFullscreen ? 'blue-grey' : 'blue'"
|
||||
@@ -673,8 +683,8 @@ export default {
|
||||
snackbarText: "",
|
||||
fontSize: getSetting("font.size"),
|
||||
datePickerDialog: false,
|
||||
selectedDate: new Date().toISOString().split("T")[0],
|
||||
selectedDateObj: new Date(this.selectedDate),
|
||||
selectedDate: new Date().toISOString().split("T")[0].replace(/-/g, ''),
|
||||
selectedDateObj: new Date(),
|
||||
refreshInterval: null,
|
||||
subjectOrder: [
|
||||
"语文",
|
||||
@@ -849,6 +859,9 @@ export default {
|
||||
showRandomPickerButton() {
|
||||
return getSetting("randomPicker.enabled");
|
||||
},
|
||||
showListCardButton() {
|
||||
return getSetting("display.showListCard");
|
||||
},
|
||||
confirmNonTodaySave() {
|
||||
return getSetting("edit.confirmNonTodaySave");
|
||||
},
|
||||
@@ -1066,9 +1079,26 @@ export default {
|
||||
const today = this.getToday();
|
||||
|
||||
// 确保日期格式正确
|
||||
const currentDate = dateFromUrl ? new Date(dateFromUrl) : today;
|
||||
let currentDate = today;
|
||||
if (dateFromUrl) {
|
||||
// 处理yyyymmdd格式的日期字符串
|
||||
if (/^\d{8}$/.test(dateFromUrl)) {
|
||||
const year = dateFromUrl.substring(0, 4);
|
||||
const month = dateFromUrl.substring(4, 6);
|
||||
const day = dateFromUrl.substring(6, 8);
|
||||
currentDate = new Date(`${year}-${month}-${day}`);
|
||||
} else {
|
||||
currentDate = new Date(dateFromUrl);
|
||||
}
|
||||
// 确保日期有效,无效则使用今天的日期
|
||||
if (isNaN(currentDate.getTime())) {
|
||||
currentDate = today;
|
||||
}
|
||||
}
|
||||
|
||||
this.state.dateString = this.formatDate(currentDate);
|
||||
this.state.selectedDate = this.state.dateString;
|
||||
this.state.selectedDateObj = currentDate; // 设置日期对象
|
||||
this.state.isToday =
|
||||
this.formatDate(currentDate) === this.formatDate(today);
|
||||
// 如果没有从URL应用配置,使用本地设置
|
||||
@@ -1090,7 +1120,7 @@ export default {
|
||||
"classworks-data-" + this.state.dateString
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
if (response.success == false) {
|
||||
if (response.error.code === "NOT_FOUND") {
|
||||
this.state.showNoDataMessage = true;
|
||||
this.state.noDataMessage = response.error.message;
|
||||
@@ -1105,11 +1135,11 @@ export default {
|
||||
} else {
|
||||
// 确保数据结构完整
|
||||
this.state.boardData = {
|
||||
homework: response.data.homework || {},
|
||||
homework: response.homework || {},
|
||||
attendance: {
|
||||
absent: response.data.attendance?.absent || [],
|
||||
late: response.data.attendance?.late || [],
|
||||
exclude: response.data.attendance?.exclude || [],
|
||||
absent: response.attendance?.absent || [],
|
||||
late: response.attendance?.late || [],
|
||||
exclude: response.attendance?.exclude || [],
|
||||
},
|
||||
};
|
||||
this.state.synced = true;
|
||||
@@ -1194,8 +1224,7 @@ export default {
|
||||
"classworks-data-" + this.state.dateString,
|
||||
this.state.boardData
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
if (response.success == false) {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
|
||||
@@ -1212,9 +1241,9 @@ export default {
|
||||
// Try to get student list from the dedicated key
|
||||
const response = await dataProvider.loadData("classworks-list-main");
|
||||
|
||||
if (response.success && Array.isArray(response.data)) {
|
||||
if (response.success != false && Array.isArray(response)) {
|
||||
// Transform the data into a simple list of names
|
||||
this.state.studentList = response.data.map(
|
||||
this.state.studentList = response.map(
|
||||
(student) => student.name
|
||||
);
|
||||
return;
|
||||
@@ -1390,6 +1419,7 @@ export default {
|
||||
if (this.state.dateString !== formattedDate) {
|
||||
this.state.dateString = formattedDate;
|
||||
this.state.selectedDate = formattedDate;
|
||||
this.state.selectedDateObj = selectedDate;
|
||||
this.state.isToday =
|
||||
formattedDate === this.formatDate(this.getToday());
|
||||
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
<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>
|
||||
<v-container>
|
||||
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-btn
|
||||
icon
|
||||
class="mr-2"
|
||||
to="/list"
|
||||
border
|
||||
>
|
||||
<v-icon>mdi-arrow-left</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>
|
||||
|
||||
|
||||
<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-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>
|
||||
</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-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>
|
||||
|
||||
<!-- 确认删除对话框 -->
|
||||
<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-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>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataProvider from "@/utils/dataProvider.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
listId: null,
|
||||
list: null,
|
||||
items: [],
|
||||
originalItems: [], // 保存原始顺序的项目列表
|
||||
newItemName: "",
|
||||
sortSeed: "1", // 默认排序种子
|
||||
sortType: "default", // 默认排序类型
|
||||
isRandomSorted: false, // 是否已经随机排序
|
||||
deleteDialog: {
|
||||
show: false,
|
||||
title: "",
|
||||
text: "",
|
||||
itemId: null,
|
||||
action: null
|
||||
},
|
||||
isRenaming: false,
|
||||
newListName: "",
|
||||
itemDialog: {
|
||||
show: false,
|
||||
item: null,
|
||||
isEditing: false,
|
||||
editedItem: null
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sortedItems() {
|
||||
if (this.sortType === "completed") {
|
||||
// 按完成状态排序:未完成的在前,完成的在后
|
||||
return [...this.items].sort((a, b) => {
|
||||
if (a.completed === b.completed) return 0;
|
||||
return a.completed ? 1 : -1;
|
||||
});
|
||||
}
|
||||
// 默认返回当前排序(可能是随机排序后的)
|
||||
return this.items;
|
||||
},
|
||||
hasCompletedItems() {
|
||||
return this.items.some(item => item.completed);
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.listId = this.$route.params.id;
|
||||
await Promise.all([this.loadListInfo(), this.loadItems()]);
|
||||
},
|
||||
methods: {
|
||||
async loadListInfo() {
|
||||
try {
|
||||
const listsInfo = await dataProvider.loadData("classworks-list-info");
|
||||
if (listsInfo && Array.isArray(listsInfo)) {
|
||||
this.list = listsInfo.find((list) => list.id === this.listId);
|
||||
}
|
||||
|
||||
if (!this.list) {
|
||||
// List not found, redirect back to list page
|
||||
this.$router.push("/list");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load list info", error);
|
||||
this.$router.push("/list");
|
||||
}
|
||||
},
|
||||
startRenaming() {
|
||||
if (this.list) {
|
||||
this.newListName = this.list.name;
|
||||
this.isRenaming = true;
|
||||
}
|
||||
},
|
||||
cancelRenaming() {
|
||||
this.isRenaming = false;
|
||||
this.newListName = "";
|
||||
},
|
||||
async saveListName() {
|
||||
if (!this.newListName.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 加载所有列表信息
|
||||
const listsInfo = await dataProvider.loadData("classworks-list-info");
|
||||
if (listsInfo && Array.isArray(listsInfo)) {
|
||||
// 找到当前列表并更新名称
|
||||
const listIndex = listsInfo.findIndex((list) => list.id === this.listId);
|
||||
if (listIndex !== -1) {
|
||||
listsInfo[listIndex].name = this.newListName.trim();
|
||||
// 保存更新后的列表信息
|
||||
await dataProvider.saveData("classworks-list-info", listsInfo);
|
||||
// 更新当前页面的列表信息
|
||||
this.list.name = this.newListName.trim();
|
||||
}
|
||||
}
|
||||
// 退出重命名模式
|
||||
this.isRenaming = false;
|
||||
} catch (error) {
|
||||
console.error("Failed to update list name", error);
|
||||
}
|
||||
},
|
||||
async loadItems() {
|
||||
try {
|
||||
let items = await dataProvider.loadData(
|
||||
`classworks-list-${this.listId}`
|
||||
);
|
||||
if (!items || !Array.isArray(items)) {
|
||||
items = [];
|
||||
await dataProvider.saveData(`classworks-list-${this.listId}`, items);
|
||||
}
|
||||
|
||||
// 确保每个项目都有正确的数据结构
|
||||
this.items = items.map(item => {
|
||||
// 如果是从学生列表直接复制过来的,可能没有completed属性
|
||||
if (typeof item.completed === 'undefined') {
|
||||
return {
|
||||
id: item.id || Date.now() + Math.floor(Math.random() * 1000),
|
||||
name: item.name,
|
||||
completed: false,
|
||||
description: item.description || '',
|
||||
};
|
||||
}
|
||||
// 确保有描述和备注字段
|
||||
return {
|
||||
...item,
|
||||
description: item.description || '',
|
||||
};
|
||||
});
|
||||
|
||||
// 保存原始顺序
|
||||
this.originalItems = JSON.parse(JSON.stringify(this.items));
|
||||
} catch (error) {
|
||||
console.error("Failed to load items", error);
|
||||
this.items = [];
|
||||
this.originalItems = [];
|
||||
}
|
||||
},
|
||||
async addItem() {
|
||||
if (!this.newItemName) return;
|
||||
|
||||
const newItem = {
|
||||
id: Date.now().toString(),
|
||||
name: this.newItemName,
|
||||
completed: false,
|
||||
description: '',
|
||||
};
|
||||
|
||||
this.items.push(newItem);
|
||||
// 同时更新原始列表
|
||||
this.originalItems.push(JSON.parse(JSON.stringify(newItem)));
|
||||
|
||||
await this.saveItems();
|
||||
this.newItemName = "";
|
||||
},
|
||||
openItemDetails(item) {
|
||||
this.itemDialog = {
|
||||
show: true,
|
||||
item: item,
|
||||
isEditing: false,
|
||||
editedItem: null
|
||||
};
|
||||
},
|
||||
startEditingItem() {
|
||||
if (!this.itemDialog.item) return;
|
||||
|
||||
this.itemDialog.isEditing = true;
|
||||
this.itemDialog.editedItem = JSON.parse(JSON.stringify(this.itemDialog.item));
|
||||
},
|
||||
cancelEditingItem() {
|
||||
this.itemDialog.isEditing = false;
|
||||
this.itemDialog.editedItem = null;
|
||||
},
|
||||
async saveItemChanges() {
|
||||
if (!this.itemDialog.editedItem) return;
|
||||
|
||||
const itemIndex = this.items.findIndex(item => item.id === this.itemDialog.item.id);
|
||||
if (itemIndex !== -1) {
|
||||
// 更新项目
|
||||
this.items[itemIndex] = {
|
||||
...this.itemDialog.editedItem
|
||||
};
|
||||
|
||||
// 同时更新原始列表
|
||||
const originalIndex = this.originalItems.findIndex(item => item.id === this.itemDialog.item.id);
|
||||
if (originalIndex !== -1) {
|
||||
this.originalItems[originalIndex] = JSON.parse(JSON.stringify(this.items[itemIndex]));
|
||||
}
|
||||
|
||||
await this.saveItems();
|
||||
|
||||
// 更新对话框中显示的项目
|
||||
this.itemDialog.item = this.items[itemIndex];
|
||||
this.itemDialog.isEditing = false;
|
||||
this.itemDialog.editedItem = null;
|
||||
}
|
||||
},
|
||||
confirmDeleteItem(itemId) {
|
||||
const item = this.items.find(item => item.id === itemId);
|
||||
if (item) {
|
||||
this.deleteDialog = {
|
||||
show: true,
|
||||
title: "删除确认",
|
||||
text: `确定要删除 "${item.name}" 吗?`,
|
||||
itemId: itemId,
|
||||
action: 'deleteItem'
|
||||
};
|
||||
// 如果是从项目详情对话框中删除,则关闭项目详情对话框
|
||||
if (this.itemDialog.show && this.itemDialog.item?.id === itemId) {
|
||||
this.itemDialog.show = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmDeleteCompleted() {
|
||||
const completedCount = this.items.filter(item => item.completed).length;
|
||||
this.deleteDialog = {
|
||||
show: true,
|
||||
title: "删除已完成项目",
|
||||
text: `确定要删除所有已完成的项目吗?(共 ${completedCount} 项)`,
|
||||
action: 'deleteCompleted'
|
||||
};
|
||||
},
|
||||
confirmDelete() {
|
||||
if (this.deleteDialog.action === 'deleteItem' && this.deleteDialog.itemId) {
|
||||
this.deleteItem(this.deleteDialog.itemId);
|
||||
} else if (this.deleteDialog.action === 'deleteCompleted') {
|
||||
this.deleteCompletedItems();
|
||||
}
|
||||
this.deleteDialog.show = false;
|
||||
},
|
||||
async deleteItem(itemId) {
|
||||
this.items = this.items.filter((item) => item.id !== itemId);
|
||||
this.originalItems = this.originalItems.filter(
|
||||
(item) => item.id !== itemId
|
||||
);
|
||||
await this.saveItems();
|
||||
},
|
||||
async deleteCompletedItems() {
|
||||
this.items = this.items.filter(item => !item.completed);
|
||||
this.originalItems = this.originalItems.filter(item => !item.completed);
|
||||
await this.saveItems();
|
||||
},
|
||||
async updateItemStatus(itemId, newStatus) {
|
||||
// 更新项目的完成状态
|
||||
const item = this.items.find(item => item.id === itemId);
|
||||
if (item) {
|
||||
item.completed = newStatus;
|
||||
|
||||
// 同时更新原始列表中对应项目的状态
|
||||
const originalItem = this.originalItems.find(item => item.id === itemId);
|
||||
if (originalItem) {
|
||||
originalItem.completed = newStatus;
|
||||
}
|
||||
|
||||
await this.saveItems();
|
||||
}
|
||||
},
|
||||
async saveItems() {
|
||||
try {
|
||||
await dataProvider.saveData(
|
||||
`classworks-list-${this.listId}`,
|
||||
this.items
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to save items", error);
|
||||
}
|
||||
},
|
||||
// 使用种子值进行随机排序
|
||||
randomSort() {
|
||||
// 对每个元素分配一个基于种子和元素ID的随机值
|
||||
const itemsWithRandom = this.items.map((item) => {
|
||||
// 为每个项目生成一个唯一但可重现的随机数
|
||||
const itemSeed = this.hashCode(item.id + this.sortSeed);
|
||||
return {
|
||||
...item,
|
||||
randomValue: this.seededRandom(itemSeed),
|
||||
};
|
||||
});
|
||||
|
||||
// 根据随机值排序
|
||||
itemsWithRandom.sort((a, b) => a.randomValue - b.randomValue);
|
||||
|
||||
// 移除临时的randomValue属性并更新items
|
||||
this.items = itemsWithRandom.map((item) => {
|
||||
const newItem = { ...item };
|
||||
delete newItem.randomValue;
|
||||
return newItem;
|
||||
});
|
||||
|
||||
this.isRandomSorted = true;
|
||||
this.saveItems(); // 保存排序后的结果
|
||||
},
|
||||
// 重置为原始顺序
|
||||
resetSort() {
|
||||
this.items = JSON.parse(JSON.stringify(this.originalItems));
|
||||
this.isRandomSorted = false;
|
||||
this.saveItems();
|
||||
},
|
||||
// 字符串转哈希值,用于种子
|
||||
hashCode(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return Math.abs(hash); // 确保返回正数
|
||||
},
|
||||
// 基于种子的伪随机数生成器
|
||||
seededRandom(seed) {
|
||||
// 使用简单但可重现的伪随机数生成算法
|
||||
const x = Math.sin(seed) * 10000;
|
||||
return x - Math.floor(x);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,243 @@
|
||||
<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-app-bar-title>
|
||||
</v-app-bar><v-container>
|
||||
|
||||
|
||||
|
||||
|
||||
<v-card border class="mb-5" rounded="xl">
|
||||
<v-card-title>现有列表</v-card-title>
|
||||
<v-card-text v-if="lists.length === 0">
|
||||
暂无列表,请创建新列表
|
||||
</v-card-text>
|
||||
<v-list v-else>
|
||||
<v-list-item
|
||||
v-for="list in lists"
|
||||
:key="list.id"
|
||||
:to="list.id !== editingListId ? `/list/${list.id}` : undefined"
|
||||
:active="list.id === editingListId"
|
||||
>
|
||||
<div v-if="list.id !== editingListId">
|
||||
<v-list-item-title>{{ list.name }}</v-list-item-title>
|
||||
</div>
|
||||
<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
|
||||
@keyup.enter="saveListName"
|
||||
></v-text-field>
|
||||
<v-btn icon color="primary" @click.stop.prevent="saveListName" class="mr-2" border>
|
||||
<v-icon>mdi-check</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="error" @click.stop.prevent="cancelEditing" border>
|
||||
<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-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon @click.stop.prevent="confirmDeleteList(list.id)" border>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
<v-card class="mb-5" border rounded="xl">
|
||||
<v-card-title>创建新列表</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="newListName"
|
||||
label="列表名称"
|
||||
:rules="[v => !!v || '名称不能为空']"
|
||||
></v-text-field>
|
||||
<v-btn color="primary" @click="createNewList" :disabled="!newListName">
|
||||
创建列表
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<!-- 确认删除对话框 -->
|
||||
<v-dialog v-model="deleteDialog.show" max-width="500">
|
||||
<v-card border>
|
||||
<v-card-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-dialog>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataProvider from "@/utils/dataProvider.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
newListName: "",
|
||||
studentList: [], // 存储学生列表数据
|
||||
deleteDialog: {
|
||||
show: false,
|
||||
text: "",
|
||||
listId: null
|
||||
},
|
||||
editingListId: null,
|
||||
editListName: ""
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await Promise.all([
|
||||
this.loadLists(),
|
||||
this.loadStudentList()
|
||||
]);
|
||||
},
|
||||
methods: {
|
||||
async loadLists() {
|
||||
try {
|
||||
let listsInfo = await dataProvider.loadData("classworks-list-info");
|
||||
if (!listsInfo || !Array.isArray(listsInfo)) {
|
||||
listsInfo = [];
|
||||
await dataProvider.saveData("classworks-list-info", listsInfo);
|
||||
}
|
||||
this.lists = listsInfo;
|
||||
} catch (error) {
|
||||
console.error("Failed to load lists", error);
|
||||
this.lists = [];
|
||||
await dataProvider.saveData("classworks-list-info", []);
|
||||
}
|
||||
},
|
||||
|
||||
async loadStudentList() {
|
||||
try {
|
||||
// 从classworks-list-main加载学生列表数据
|
||||
const response = await dataProvider.loadData("classworks-list-main");
|
||||
if (response && Array.isArray(response)) {
|
||||
this.studentList = response;
|
||||
} else {
|
||||
this.studentList = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load student list", error);
|
||||
this.studentList = [];
|
||||
}
|
||||
},
|
||||
|
||||
async createNewList() {
|
||||
if (!this.newListName) return;
|
||||
|
||||
const listId = Date.now().toString();
|
||||
const newList = {
|
||||
id: listId,
|
||||
name: this.newListName,
|
||||
};
|
||||
|
||||
// Add to lists info
|
||||
this.lists.push(newList);
|
||||
await dataProvider.saveData("classworks-list-info", this.lists);
|
||||
|
||||
// 创建列表数据,确保正确的数据结构
|
||||
const newListData = [];
|
||||
|
||||
// 如果有学生列表数据,则填充
|
||||
if (this.studentList && this.studentList.length > 0) {
|
||||
this.studentList.forEach(student => {
|
||||
newListData.push({
|
||||
id: student.id || Date.now() + Math.floor(Math.random() * 1000),
|
||||
name: student.name,
|
||||
completed: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await dataProvider.saveData(`classworks-list-${listId}`, newListData);
|
||||
|
||||
this.newListName = "";
|
||||
|
||||
// Navigate to the new list
|
||||
this.$router.push(`/list/${listId}`);
|
||||
},
|
||||
|
||||
startEditing(listId) {
|
||||
const list = this.lists.find(list => list.id === listId);
|
||||
if (list) {
|
||||
this.editingListId = listId;
|
||||
this.editListName = list.name;
|
||||
}
|
||||
},
|
||||
|
||||
cancelEditing() {
|
||||
this.editingListId = null;
|
||||
this.editListName = "";
|
||||
},
|
||||
|
||||
async saveListName() {
|
||||
if (!this.editListName.trim() || !this.editingListId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 找到当前编辑的列表并更新名称
|
||||
const listIndex = this.lists.findIndex(list => list.id === this.editingListId);
|
||||
if (listIndex !== -1) {
|
||||
this.lists[listIndex].name = this.editListName.trim();
|
||||
// 保存更新后的列表信息
|
||||
await dataProvider.saveData("classworks-list-info", this.lists);
|
||||
}
|
||||
// 退出编辑模式
|
||||
this.editingListId = null;
|
||||
this.editListName = "";
|
||||
} catch (error) {
|
||||
console.error("Failed to update list name", error);
|
||||
}
|
||||
},
|
||||
|
||||
confirmDeleteList(listId) {
|
||||
const list = this.lists.find(list => list.id === listId);
|
||||
if (list) {
|
||||
this.deleteDialog = {
|
||||
show: true,
|
||||
text: `确定要删除列表 "${list.name}" 吗?`,
|
||||
listId: listId
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
confirmDelete() {
|
||||
if (this.deleteDialog.listId) {
|
||||
this.deleteList(this.deleteDialog.listId);
|
||||
}
|
||||
this.deleteDialog.show = false;
|
||||
},
|
||||
|
||||
async deleteList(listId) {
|
||||
this.lists = this.lists.filter(list => list.id !== listId);
|
||||
await dataProvider.saveData("classworks-list-info", this.lists);
|
||||
// We don't need to delete the actual list data as it will just remain unused
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
+3
-18
@@ -383,9 +383,9 @@ export default {
|
||||
// Try to get student list from the dedicated key
|
||||
const response = await dataProvider.loadData('classworks-list-main');
|
||||
|
||||
if (response.success && Array.isArray(response.data)) {
|
||||
if (response.success!=false && Array.isArray(response)) {
|
||||
// Transform the data into a simple list of names
|
||||
this.studentData.list = response.data.map(student => student.name);
|
||||
this.studentData.list = response.map(student => student.name);
|
||||
this.studentData.text = this.studentData.list.join('\n');
|
||||
this.lastSavedData = [...this.studentData.list];
|
||||
this.hasUnsavedChanges = false;
|
||||
@@ -394,21 +394,6 @@ export default {
|
||||
} catch (error) {
|
||||
console.warn('Failed to load student list from dedicated key, falling back to config', error);
|
||||
}
|
||||
|
||||
// Fall back to retrieving from config if the dedicated key is not available
|
||||
const response = await kvProvider.local.loadConfig();
|
||||
|
||||
if (response.success && response.data && Array.isArray(response.data.studentList)) {
|
||||
this.studentData.list = response.data.studentList;
|
||||
this.studentData.text = response.data.studentList.join('\n');
|
||||
this.lastSavedData = [...response.data.studentList];
|
||||
this.hasUnsavedChanges = false;
|
||||
} else {
|
||||
// If no student list is found anywhere, initialize with empty list
|
||||
this.studentData.list = [];
|
||||
this.studentData.text = '';
|
||||
this.lastSavedData = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生列表失败:', error);
|
||||
this.studentsError = error.message || '加载失败,请检查设置';
|
||||
@@ -436,7 +421,7 @@ export default {
|
||||
// Save the student list to the dedicated key
|
||||
const response = await dataProvider.saveData("classworks-list-main", formattedStudentList);
|
||||
|
||||
if (!response.success) {
|
||||
if (response.success==false) {
|
||||
throw new Error(response.error?.message || "保存失败");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user