更改下拉框
This commit is contained in:
170
index.html
170
index.html
@@ -4,6 +4,7 @@
|
|||||||
<base target="_top">
|
<base target="_top">
|
||||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
/* --- 基础变量 --- */
|
||||||
:root {
|
:root {
|
||||||
--bg-color: #f8fafc;
|
--bg-color: #f8fafc;
|
||||||
--card-bg: #ffffff;
|
--card-bg: #ffffff;
|
||||||
@@ -12,9 +13,15 @@
|
|||||||
--border-color: #e2e8f0;
|
--border-color: #e2e8f0;
|
||||||
--accent-blue: #2563eb;
|
--accent-blue: #2563eb;
|
||||||
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
--color-safe: #10b981;
|
||||||
|
--color-unsafe: #ef4444;
|
||||||
|
--color-unknown: #f59e0b;
|
||||||
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- 暗黑模式变量 --- */
|
||||||
body.dark-mode {
|
body.dark-mode {
|
||||||
--bg-color: #0f172a;
|
--bg-color: #0f172a;
|
||||||
--card-bg: #1e293b;
|
--card-bg: #1e293b;
|
||||||
@@ -23,20 +30,28 @@
|
|||||||
--border-color: #334155;
|
--border-color: #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- 全局样式 --- */
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
|
line-height: 1.6;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition: background-color 0.3s, color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
[v-cloak] { display: none; }
|
[v-cloak] { display: none; }
|
||||||
.container { max-width: 1280px; margin: 0 auto; padding: 2rem 1rem; }
|
|
||||||
|
|
||||||
/* Header */
|
.container {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 头部 --- */
|
||||||
.header {
|
.header {
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
padding: 1.5rem 2rem;
|
padding: 1.5rem 2rem;
|
||||||
@@ -49,38 +64,72 @@
|
|||||||
box-shadow: var(--card-shadow);
|
box-shadow: var(--card-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand h1 { font-size: 1.5rem; font-weight: 800; color: var(--accent-blue); font-style: italic; }
|
.brand h1 {
|
||||||
.brand p { font-size: 0.8rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; }
|
font-size: 1.5rem;
|
||||||
|
font-weight: 800;
|
||||||
.controls { display: flex; gap: 0.75rem; align-items: center; }
|
color: var(--accent-blue);
|
||||||
input[type="text"] {
|
font-style: italic;
|
||||||
padding: 0.6rem 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
background: var(--bg-color);
|
|
||||||
color: var(--text-main);
|
|
||||||
width: 260px;
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
.brand p {
|
||||||
padding: 0.6rem;
|
font-size: 0.8rem;
|
||||||
border-radius: 0.5rem;
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 输入框与下拉框重塑 --- */
|
||||||
|
input[type="text"], .custom-select {
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] { width: 260px; }
|
||||||
|
input[type="text"]:focus, .custom-select:focus {
|
||||||
|
border-color: var(--accent-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义下拉框样式 */
|
||||||
|
.custom-select {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
padding-right: 2.5rem; /* 为箭头留位 */
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2364748b'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 0.8rem center;
|
||||||
|
background-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .custom-select {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-btn {
|
.theme-btn {
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.75rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table */
|
/* --- 表格样式 --- */
|
||||||
.table-container {
|
.table-container {
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
@@ -100,20 +149,21 @@
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
td { padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border-color); vertical-align: middle; }
|
td { padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border-color); vertical-align: middle; }
|
||||||
tr:hover { background: rgba(37, 99, 235, 0.02); }
|
tr:hover { background: rgba(37, 99, 235, 0.02); }
|
||||||
|
|
||||||
.item-cell { display: flex; align-items: center; gap: 1rem; }
|
.item-cell { display: flex; align-items: center; gap: 1rem; }
|
||||||
.icon-box { position: relative; width: 48px; height: 48px; flex-shrink: 0; }
|
.icon-box { position: relative; width: 52px; height: 52px; flex-shrink: 0; }
|
||||||
.icon-img { width: 100%; height: 100%; object-fit: cover; border-radius: 0.75rem; border: 1px solid var(--border-color); }
|
.icon-img { width: 100%; height: 100%; object-fit: cover; border-radius: 0.85rem; border: 1px solid var(--border-color); }
|
||||||
.status-dot { position: absolute; bottom: -2px; right: -2px; width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--card-bg); }
|
.status-dot { position: absolute; bottom: -2px; right: -2px; width: 13px; height: 13px; border-radius: 50%; border: 2px solid var(--card-bg); }
|
||||||
|
|
||||||
.item-desc h3 { font-size: 1.1rem; font-weight: 700; margin-bottom: 0.15rem; }
|
.item-desc h3 { font-size: 1.1rem; font-weight: 700; margin-bottom: 0.15rem; }
|
||||||
.item-desc p { font-size: 0.85rem; color: var(--text-muted); }
|
.item-desc p { font-size: 0.85rem; color: var(--text-muted); }
|
||||||
|
|
||||||
.type-label {
|
.type-label {
|
||||||
display: inline-block; padding: 0.25rem 0.75rem; border-radius: 0.5rem;
|
display: inline-block; padding: 0.25rem 0.75rem; border-radius: 0.5rem;
|
||||||
font-size: 0.8rem; font-weight: 600; border: 1px solid currentColor;
|
font-size: 0.8rem; font-weight: 600; border: 1px solid currentColor; background: rgba(0,0,0,0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.safety-text { font-weight: 400; display: flex; align-items: center; gap: 0.4rem; }
|
.safety-text { font-weight: 400; display: flex; align-items: center; gap: 0.4rem; }
|
||||||
@@ -122,28 +172,16 @@
|
|||||||
|
|
||||||
.btn-link {
|
.btn-link {
|
||||||
display: inline-block; padding: 0.5rem 1rem; background: #eff6ff; color: var(--accent-blue);
|
display: inline-block; padding: 0.5rem 1rem; background: #eff6ff; color: var(--accent-blue);
|
||||||
text-decoration: none; border-radius: 0.5rem; font-size: 0.85rem; font-weight: 700;
|
text-decoration: none; border-radius: 0.5rem; font-size: 0.85rem; font-weight: 700; transition: 0.2s;
|
||||||
}
|
}
|
||||||
.btn-link:hover { background: var(--accent-blue); color: white; }
|
.btn-link:hover { background: var(--accent-blue); color: white; }
|
||||||
|
|
||||||
/* Pagination */
|
/* --- 分页 --- */
|
||||||
.pagination {
|
.pagination { display: flex; justify-content: center; align-items: center; gap: 1.5rem; padding: 1rem 0; }
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5rem;
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
.page-info { font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
|
.page-info { font-size: 0.9rem; color: var(--text-muted); font-weight: 600; }
|
||||||
.page-btn {
|
.page-btn {
|
||||||
padding: 0.5rem 1.25rem;
|
padding: 0.5rem 1.25rem; background: var(--card-bg); border: 1px solid var(--border-color);
|
||||||
background: var(--card-bg);
|
border-radius: 0.5rem; cursor: pointer; color: var(--text-main); font-weight: 600; transition: 0.2s;
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--text-main);
|
|
||||||
font-weight: 600;
|
|
||||||
transition: 0.2s;
|
|
||||||
}
|
}
|
||||||
.page-btn:disabled { opacity: 0.3; cursor: not-allowed; }
|
.page-btn:disabled { opacity: 0.3; cursor: not-allowed; }
|
||||||
.page-btn:hover:not(:disabled) { border-color: var(--accent-blue); color: var(--accent-blue); }
|
.page-btn:hover:not(:disabled) { border-color: var(--accent-blue); color: var(--accent-blue); }
|
||||||
@@ -162,14 +200,19 @@
|
|||||||
<h1>SECURITY</h1>
|
<h1>SECURITY</h1>
|
||||||
<p>{{ ui[lang].subtitle }}</p>
|
<p>{{ ui[lang].subtitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input v-model="searchTerm" type="text" :placeholder="ui[lang].searchPlaceholder">
|
<input v-model="searchTerm" type="text" :placeholder="ui[lang].searchPlaceholder">
|
||||||
<select v-model="lang">
|
|
||||||
|
<select v-model="lang" class="custom-select">
|
||||||
<option value="cn">中文</option>
|
<option value="cn">中文</option>
|
||||||
<option value="en">ENGLISH</option>
|
<option value="en">ENGLISH</option>
|
||||||
<option value="es">ESPAÑOL</option>
|
<option value="es">ESPAÑOL</option>
|
||||||
</select>
|
</select>
|
||||||
<button @click="toggleDark" class="theme-btn">{{ isDark ? '☀️' : '🌙' }}</button>
|
|
||||||
|
<button @click="toggleDark" class="theme-btn">
|
||||||
|
{{ isDark ? '☀️' : '🌙' }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -199,11 +242,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="type-label" :style="{ color: getTypeColor(item.type) }">{{ translateStatic(item.type) }}</span></td>
|
|
||||||
<td>
|
<td>
|
||||||
<span v-for="p in splitPlatform(item.platform)" class="plat-tag">{{ p }}</span>
|
<span class="type-label" :style="{ color: getTypeColor(item.type) }">
|
||||||
|
{{ translateStatic(item.type) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
|
||||||
|
<span v-for="p in splitPlatform(item.platform)" class="plat-tag">{{ p }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="safety-text" :style="{ color: getSafetyColor(item.safety) }">
|
||||||
|
● {{ translateStatic(item.safety) }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><div class="safety-text" :style="{ color: getSafetyColor(item.safety) }">● {{ translateStatic(item.safety) }}</div></td>
|
|
||||||
<td><span class="date-val">{{ formatDate(item.date) }}</span></td>
|
<td><span class="date-val">{{ formatDate(item.date) }}</span></td>
|
||||||
<td style="text-align: right;">
|
<td style="text-align: right;">
|
||||||
<a v-if="item.url" :href="item.url" target="_blank" class="btn-link">{{ ui[lang].linkBtn }}</a>
|
<a v-if="item.url" :href="item.url" target="_blank" class="btn-link">{{ ui[lang].linkBtn }}</a>
|
||||||
@@ -232,11 +285,8 @@ createApp({
|
|||||||
const searchTerm = ref('');
|
const searchTerm = ref('');
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const isDark = ref(false);
|
const isDark = ref(false);
|
||||||
|
|
||||||
// Pagination state
|
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const pageSize = 20;
|
const pageSize = 20;
|
||||||
|
|
||||||
const defaultIcon = 'https://cdn-icons-png.flaticon.com/512/684/684831.png';
|
const defaultIcon = 'https://cdn-icons-png.flaticon.com/512/684/684831.png';
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
@@ -263,29 +313,33 @@ createApp({
|
|||||||
.getAllData();
|
.getAllData();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. 全局筛选结果
|
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed(() => {
|
||||||
const main = rawData.value.main || [];
|
const main = rawData.value.main || [];
|
||||||
const transMap = {};
|
const transMap = {};
|
||||||
(rawData.value[lang.value] || []).forEach(row => { transMap[row["序号"]] = row; });
|
(rawData.value[lang.value] || []).forEach(row => { transMap[row["序号"]] = row; });
|
||||||
|
|
||||||
const results = main.map(row => {
|
return main.map(row => {
|
||||||
const id = row["序号"];
|
const id = row["序号"];
|
||||||
const t = transMap[id] || {};
|
const t = transMap[id] || {};
|
||||||
const name = t[lang.value + "名字"] || row["名字"];
|
return {
|
||||||
const note = t[lang.value + "备注"] || row["备注"];
|
id,
|
||||||
return { id, name, note, icon: row["图标链接"] || defaultIcon, type: row["类型"], platform: String(row["平台"] || ""), url: row["官网链接"], safety: row["安全性"], date: row["更新日期"] };
|
name: t[lang.value + "名字"] || row["名字"],
|
||||||
|
note: t[lang.value + "备注"] || row["备注"],
|
||||||
|
icon: row["图标链接"] || defaultIcon,
|
||||||
|
type: row["类型"],
|
||||||
|
platform: String(row["平台"] || ""),
|
||||||
|
url: row["官网链接"],
|
||||||
|
safety: row["安全性"],
|
||||||
|
date: row["更新日期"]
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.filter(item => {
|
.filter(item => {
|
||||||
const s = searchTerm.value.toLowerCase();
|
const s = searchTerm.value.toLowerCase();
|
||||||
return !s || item.name.toLowerCase().includes(s) || item.note.toLowerCase().includes(s) || (item.url && item.url.toLowerCase().includes(s));
|
return !s || item.name.toLowerCase().includes(s) || item.note.toLowerCase().includes(s) || (item.url && item.url.toLowerCase().includes(s));
|
||||||
})
|
})
|
||||||
.sort((a, b) => new Date(b.date) - new Date(a.date));
|
.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||||
|
|
||||||
return results;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. 当前分页结果
|
|
||||||
const paginatedItems = computed(() => {
|
const paginatedItems = computed(() => {
|
||||||
const start = (currentPage.value - 1) * pageSize;
|
const start = (currentPage.value - 1) * pageSize;
|
||||||
const end = start + pageSize;
|
const end = start + pageSize;
|
||||||
@@ -294,8 +348,8 @@ createApp({
|
|||||||
|
|
||||||
const totalPages = computed(() => Math.ceil(filteredItems.value.length / pageSize));
|
const totalPages = computed(() => Math.ceil(filteredItems.value.length / pageSize));
|
||||||
|
|
||||||
// Watchers: Reset to page 1 on search or lang change
|
// 修复:移除 lang 监听,切换语言不再重置页码
|
||||||
watch([searchTerm, ], () => { currentPage.value = 1; });
|
watch(searchTerm, () => { currentPage.value = 1; });
|
||||||
|
|
||||||
const translateStatic = (val) => (staticMap[val] && staticMap[val][lang.value]) || val;
|
const translateStatic = (val) => (staticMap[val] && staticMap[val][lang.value]) || val;
|
||||||
const splitPlatform = (str) => str ? str.split(/[,,]/).map(s => s.trim()) : [];
|
const splitPlatform = (str) => str ? str.split(/[,,]/).map(s => s.trim()) : [];
|
||||||
|
|||||||
Reference in New Issue
Block a user