1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-07-01 16:49:22 +00:00

Refactor index.vue and settings.vue for improved layout and functionality. Added URL configuration dialog and settings link generator component. Enhanced button formatting and code readability.

This commit is contained in:
SunWuyuan 2025-04-19 20:29:39 +08:00
parent 94149d0381
commit eefd9eb054
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
5 changed files with 2628 additions and 162 deletions

1306
1.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,458 @@
<template>
<v-card border class="settings-link-generator mb-4">
<v-card-title class="text-h6">
<v-icon start icon="mdi-link-variant" class="mr-2" />
设置分享
</v-card-title>
<v-card-text>
<!-- 快速选择按钮 -->
<div class="d-flex mb-3 gap-2 flex-wrap">
<v-btn
size="small"
variant="tonal"
color="primary"
prepend-icon="mdi-select-all"
@click="selectAll"
>
全选
</v-btn>
<v-btn
size="small"
variant="tonal"
color="primary"
prepend-icon="mdi-server-network"
@click="selectDataSourceSettings"
>
数据源设置
</v-btn>
<v-btn
size="small"
variant="tonal"
color="primary"
prepend-icon="mdi-compare"
@click="selectChangedSettings"
>
已变更设置
</v-btn>
<v-btn
size="small"
variant="tonal"
color="error"
prepend-icon="mdi-select-remove"
@click="resetSelection"
>
取消选择
</v-btn>
</div>
<!-- 选择摘要和链接 -->
<div class="d-flex align-center mt-3 mb-3 flex-wrap gap-2">
<v-chip color="primary" class="mr-2">
已选 {{ selectedItems.length }} 项设置
</v-chip>
<template v-if="selectedItems.length > 0">
<v-chip
v-for="item in selectedItems"
:key="item"
size="small"
class="mr-1"
variant="text"
>
{{ getSettingDescription(item) }}
</v-chip>
</template>
</div>
<v-text-field
v-model="generatedLink"
label="生成的链接"
readonly
variant="outlined"
class="mb-2"
:append-inner-icon="linkCopied ? 'mdi-check' : 'mdi-content-copy'"
@click:append-inner="copyLink"
/>
<!-- 设置列表折叠面板 -->
<v-expansion-panels variant="accordion">
<v-expansion-panel>
<v-expansion-panel-title> 显示设置列表详情 </v-expansion-panel-title>
<v-expansion-panel-text>
<v-data-table
:items-per-page="settingItems.length"
:headers="headers"
:items="filteredItems"
item-value="key"
v-model="selectedItems"
show-select
density="compact"
class="rounded setting-table"
@update:selected="handleSelectionChange"
:sort-by="[{ key: 'isChanged', order: 'desc' }]"
>
<template v-slot:top>
<v-text-field
v-model="search"
label="搜索设置"
prepend-inner-icon="mdi-magnify"
single-line
hide-details
class="mb-4"
></v-text-field>
</template>
<template #[`item.description`]="{ item }">
<div class="d-flex align-center">
<v-icon size="small" :icon="item.icon" class="mr-2"></v-icon>
{{ item.description }}
</div>
</template>
<template #[`item.value`]="{ item }">
<span v-if="typeof item.value === 'boolean'">
{{ item.value ? "是" : "否" }}
</span>
<span v-else>{{ item.value }}</span>
</template>
<template #[`item.key`]="{ item }">
<span class="text-caption text-grey">{{ item.key }}</span>
</template>
<template #[`item.isChanged`]="{ item }">
<v-chip
size="x-small"
:color="item.isChanged ? 'warning' : 'success'"
:text="item.isChanged ? '已修改' : '默认'"
density="compact"
/>
</template>
</v-data-table>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
</v-card>
</template>
<script>
import {
exportSettingsAsKeyValue,
settingsDefinitions,
} from "@/utils/settings";
/**
* 设置链接生成器组件
*
* 提供一个界面让用户选择需要包含在链接中的设置项
* 然后生成一个包含这些设置的URL可以分享给其他用户
*
* 当其他用户打开生成的链接时这些设置将自动应用
*/
export default {
name: "SettingsLinkGenerator",
data() {
return {
//
selectedItems: [],
//
generatedLink: "",
//
linkCopied: false,
search: "",
headers: [
{ title: "", key: "data-table-select" },
{ title: "设置项", key: "description", sortable: true },
{ title: "当前值", key: "value", sortable: true },
{
title: "键名",
key: "key",
class: "d-none d-sm-table-cell",
sortable: true,
},
{ title: "状态", key: "isChanged", sortable: true },
],
};
},
computed: {
/**
* 获取处理后的设置项列表
*/
settingItems() {
const currentSettings = exportSettingsAsKeyValue();
const items = [];
for (const [key, definition] of Object.entries(settingsDefinitions)) {
//
if (
definition.requireDeveloper &&
!currentSettings["developer.enabled"]
) {
continue;
}
//
const isChanged = currentSettings[key] !== definition.default;
items.push({
key: key,
description: definition.description || key,
value: currentSettings[key],
icon: definition.icon || "mdi-cog",
isChanged: isChanged,
defaultValue: definition.default,
});
}
//
return items.sort((a, b) => a.key.localeCompare(b.key));
},
/**
* 根据搜索条件筛选设置项
*/
filteredItems() {
if (!this.search) return this.settingItems;
const searchText = this.search.toLowerCase();
//
if (searchText === "已修改") {
return this.settingItems.filter((item) => item.isChanged);
}
if (searchText === "是" || searchText === "否") {
return this.settingItems.filter(
(item) =>
typeof item.value === "boolean" &&
(searchText === "是" ? item.value : !item.value)
);
}
//
return this.settingItems.filter((item) => {
const description = item.description.toLowerCase();
const key = item.key.toLowerCase();
const value = String(item.value).toLowerCase();
const status = item.isChanged ? "已修改" : "默认";
return (
description.includes(searchText) ||
key.includes(searchText) ||
value.includes(searchText) ||
status.includes(searchText)
);
});
},
/**
* 是否有显示相关设置
*/
hasDisplaySettings() {
return this.selectedItems.some((key) => key.startsWith("display."));
},
/**
* 是否有编辑相关设置
*/
hasEditSettings() {
return this.selectedItems.some((key) => key.startsWith("edit."));
},
/**
* 是否有服务器相关设置
*/
hasServerSettings() {
return this.selectedItems.some((key) => key.startsWith("server."));
},
/**
* 判断是否已选择已修改的设置
*/
hasChangedSettings() {
const currentSettings = exportSettingsAsKeyValue();
return this.selectedItems.some((key) => {
const definition = settingsDefinitions[key];
return definition && currentSettings[key] !== definition.default;
});
},
},
methods: {
/**
* 处理表格选择变化
*/
handleSelectionChange(items) {
this.selectedItems = items.map((item) => item.key);
this.generateLink();
},
/**
* 生成包含所选设置的链接
*/
generateLink() {
//
const baseUrl = `${window.location.protocol}//${window.location.host}/`;
//
const allSettings = exportSettingsAsKeyValue();
//
const configObj = {};
for (const key of this.selectedItems) {
configObj[key] = allSettings[key];
}
//
if (Object.keys(configObj).length === 0) {
this.generatedLink = baseUrl;
return;
}
try {
// JSONbase64
const jsonString = JSON.stringify(configObj);
const utf8Encoder = new TextEncoder();
const utf8Bytes = utf8Encoder.encode(jsonString);
const base64String = btoa(
Array.from(utf8Bytes)
.map((byte) => String.fromCharCode(byte))
.join("")
);
//
const queryParams = { config: base64String };
// URL
const urlParams = new URLSearchParams(window.location.search);
const currentDate = urlParams.get("date");
if (currentDate) {
queryParams.date = currentDate;
}
//
const queryString = new URLSearchParams(queryParams).toString();
// URL
this.generatedLink = `${baseUrl}?${queryString}`;
} catch (err) {
console.error("生成链接失败:", err);
this.generatedLink = "链接生成失败,请重试";
}
//
this.linkCopied = false;
},
/**
* 复制生成的链接到剪贴板
*/
async copyLink() {
if (!this.generatedLink) {
this.generateLink();
}
try {
await navigator.clipboard.writeText(this.generatedLink);
this.linkCopied = true;
// 3
setTimeout(() => {
this.linkCopied = false;
}, 3000);
} catch (err) {
console.error("复制链接失败:", err);
}
},
/**
* 重置所有选择
*/
resetSelection() {
this.selectedItems = [];
this.generatedLink = "";
this.linkCopied = false;
},
/**
* 选择所有设置
*/
selectAll() {
this.selectedItems = this.settingItems.map((item) => item.key);
this.generateLink();
},
/**
* 选择数据源相关设置
*/
selectDataSourceSettings() {
const dataSourceKeys = this.settingItems
.filter((item) => item.key.startsWith("server."))
.map((item) => item.key);
this.selectedItems = dataSourceKeys;
this.generateLink();
},
/**
* 选择已修改的设置
*/
selectChangedSettings() {
const changedKeys = this.settingItems
.filter((item) => item.isChanged)
.map((item) => item.key);
this.selectedItems = changedKeys;
this.generateLink();
},
/**
* 根据前缀选择设置
*/
selectByPrefix(prefix) {
const keys = this.settingItems
.filter((item) => item.key.startsWith(`${prefix}.`))
.map((item) => item.key);
this.selectedItems = keys;
},
/**
* 自动生成链接当选择变化时
*/
autoGenerateLink() {
if (this.selectedItems.length > 0) {
this.generateLink();
} else {
this.generatedLink = "";
}
},
/**
* 获取设置描述
*/
getSettingDescription(key) {
const setting = this.settingItems.find((item) => item.key === key);
return setting ? setting.description : key;
},
},
watch: {
//
selectedItems: {
handler() {
this.autoGenerateLink();
},
deep: true,
},
},
};
</script>

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,9 @@
<v-col cols="12" md="6">
<theme-settings-card border />
</v-col>
<v-col cols="12">
<settings-link-generator border />
</v-col>
<!-- 开发者选项卡片 -->
<v-col :cols="12" :md="settings.developer.enabled ? 12 : 6">
<settings-card
@ -187,7 +189,7 @@ import AboutCard from '@/components/settings/AboutCard.vue';
import '../styles/settings.scss';
import dataProvider from '@/utils/dataProvider';
import SettingsExplorer from '@/components/settings/SettingsExplorer.vue';
import SettingsLinkGenerator from '@/components/SettingsLinkGenerator.vue';
export default {
name: 'Settings',
components: {
@ -202,7 +204,8 @@ export default {
DataProviderSettingsCard,
ThemeSettingsCard,
EchoChamberCard,
SettingsExplorer
SettingsExplorer,
SettingsLinkGenerator
},
setup() {
const { mobile } = useDisplay();

View File

@ -0,0 +1,33 @@
// 处理新的base64编码的config参数
const encodedConfig = urlParams.get('config');
if (encodedConfig) {
try {
// 解码base64
const base64Decoded = atob(encodedConfig);
// 转换回UTF-8字节数组
const utf8Bytes = new Uint8Array(base64Decoded.length);
for (let i = 0; i < base64Decoded.length; i++) {
utf8Bytes[i] = base64Decoded.charCodeAt(i);
}
// 解码UTF-8字节数组为字符串
const utf8Decoder = new TextDecoder();
const jsonString = utf8Decoder.decode(utf8Bytes);
// 解析JSON
const configObj = JSON.parse(jsonString);
// 应用每个设置
for (const [key, value] of Object.entries(configObj)) {
const success = setSetting(key, value);
if (success) {
appliedSettings[key] = value;
}
}
return appliedSettings;
} catch (error) {
console.error('解析配置参数失败:', error);
}
}