anti-china / 法條檢索系統.html
s880453's picture
Update 法條檢索系統.html
2b0505c verified
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防中相關立法資訊檢索系統</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script>
<style>
/* 標題與歡迎訊息 */
.header {
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
color: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 15px;
font-weight: 600;
}
.header p {
font-size: 1.2em;
opacity: 0.9;
}
/* 導覽按鈕 */
.nav-buttons {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.nav-btn {
background: white;
color: #3498db;
padding: 12px 25px;
border: 2px solid #3498db;
border-radius: 25px;
text-decoration: none;
font-weight: 500;
transition: all 0.3s;
}
.nav-btn:hover {
background: #3498db;
color: white;
transform: translateY(-2px);
}
/* 系統使用說明 - 改進樣式 */
.guide {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 12px;
padding: 20px;
margin-bottom: 30px;
border-left: 4px solid #3498db;
transition: all 0.3s;
}
.guide.collapsed {
background: linear-gradient(135deg, #f8f9fa 0%, #f0f0f0 100%);
border-left-color: #dee2e6;
}
.guide h3 {
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
font-size: 18px;
font-weight: 600;
}
.guide-toggle-btn {
background: none;
border: none;
color: #3498db;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: color 0.3s;
}
.guide-toggle-btn:hover {
color: #2980b9;
}
.guide-content {
display: grid;
gap: 15px;
}
.guide-item {
display: flex;
align-items: flex-start;
gap: 12px;
}
.guide-item-bullet {
font-weight: bold;
color: #3498db;
font-size: 16px;
}
.guide-item-content {
flex: 1;
}
.guide-item strong {
color: #2c3e50;
font-weight: 600;
}
.guide-highlight {
color: #e74c3c;
font-weight: 700;
}
.guide-sub-items {
margin-left: 20px;
margin-top: 8px;
padding: 10px;
background: rgba(52, 152, 219, 0.1);
border-radius: 8px;
font-size: 13px;
line-height: 1.8;
}
.guide-tag {
background-color: #e74c3c;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
}
/* 確保圖標大小正確 */
.lucide {
width: 1em;
height: 1em;
vertical-align: middle;
}
/* 時間軸樣式 */
.timeline-line {
position: absolute;
left: 2rem;
top: 0;
bottom: 0;
width: 2px;
background-color: #d1d5db;
}
.timeline-node {
position: absolute;
left: 1.5rem;
width: 1rem;
height: 1rem;
background-color: white;
border-radius: 50%;
border: 2px solid #9ca3af;
z-index: 10;
}
/* 動畫效果 */
@keyframes spin {
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
/* 捲軸樣式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f3f4f6;
}
::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
</style>
</head>
<body class="bg-gray-50">
<!-- 載入畫面 -->
<div id="loadingScreen" class="min-h-screen bg-gray-50 flex items-center justify-center">
<div class="text-center">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
<p class="mt-4 text-gray-600">載入中...</p>
</div>
</div>
<!-- 主要內容(初始隱藏) -->
<div id="mainContent" class="hidden">
<!-- 法律列表頁面 -->
<div id="lawListPage">
<!-- 頁首 -->
<header class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<!-- 標題與歡迎訊息 -->
<div class="header">
<h1>⚖️ 防中相關立法資訊檢索系統</h1>
<p>提供防止中國滲透相關法律制定與修正歷程查詢服務</p>
</div>
</div>
</header>
<!-- 導覽按鈕 -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="nav-buttons">
<a href="systems.html" class="nav-btn">🏠 首頁</a>
<a href="選舉系統.html" class="nav-btn">🗳️ 選舉分析</a>
<a href="NCC裁罰分析系統.html" class="nav-btn">📊 NCC分析</a>
</div>
</div>
<!-- 系統使用說明 -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div id="guideContainer" class="guide">
<div class="flex items-center justify-between">
<h3>
<i data-lucide="info" class="h-5 w-5"></i>
系統使用說明
</h3>
<button onclick="toggleGuide()" class="guide-toggle-btn">
<span id="guideToggleText">收起</span>
</button>
</div>
<div id="guideContent" class="guide-content">
<div class="guide-item">
<span class="guide-item-bullet"></span>
<div class="guide-item-content">
<strong>搜尋功能</strong>:可使用上方搜尋框查詢法律名稱、議案編號、提案人等資訊。搜尋到議案時會顯示在「相關議案搜尋結果」區塊,點擊「查看詳細」可直接跳到該議案
</div>
</div>
<div class="guide-item">
<span class="guide-item-bullet"></span>
<div class="guide-item-content">
<strong class="guide-highlight">防中議案篩選(重要功能)</strong>
<div class="guide-sub-items">
- 首頁法律卡片上的紅色<span class="guide-tag">防中議案</span>標籤<strong>可點擊</strong><br/>
- 點擊後會進入該法律頁面並<strong>自動篩選只顯示防中議案</strong><br/>
- 在法律詳細頁面右上角也有防中議案按鈕,可隨時切換篩選<br/>
- 系統會根據不同法律使用對應的關鍵字進行智能標示
</div>
</div>
</div>
<div class="guide-item">
<span class="guide-item-bullet"></span>
<div class="guide-item-content">
<strong>分類瀏覽</strong>:使用分類標籤快速篩選「媒體相關」或「國安相關」法律
</div>
</div>
<div class="guide-item">
<span class="guide-item-bullet"></span>
<div class="guide-item-content">
<strong>時間軸檢視</strong>:查看完整立法歷程,點擊時間軸上的「查看議案」可展開詳細資訊
</div>
</div>
<div class="guide-item">
<span class="guide-item-bullet"></span>
<div class="guide-item-content">
<strong>議案附件</strong>:可直接查看或下載相關文書(PDF、DOC)
</div>
</div>
</div>
</div>
</div>
<!-- 搜尋區塊 -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="bg-white rounded-lg shadow p-6">
<div class="flex flex-col md:flex-row gap-4">
<div class="flex-1">
<div class="relative">
<i data-lucide="search" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5"></i>
<input
type="text"
id="searchInput"
placeholder="搜尋法律名稱、編號、議案編號、提案人、連署人等..."
class="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onkeyup="handleSearchInput(event)"
/>
</div>
</div>
<select
id="searchField"
class="px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onchange="performSearch()"
>
<option value="all">不分欄位</option>
<option value="name">法律名稱</option>
<option value="billName">議案名稱</option>
<option value="proposer">提案人/連署人</option>
<option value="billNumber">議案編號</option>
<option value="description">說明</option>
</select>
</div>
<div class="mt-3 text-sm text-gray-600" id="searchStats"></div>
</div>
</div>
<!-- 法律列表和議案搜尋結果 -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12">
<!-- 議案搜尋結果 -->
<div id="billSearchResults" class="mb-6 hidden">
<h2 class="text-lg font-semibold text-gray-900 mb-4">相關議案搜尋結果</h2>
<div class="space-y-4" id="billSearchResultsList"></div>
</div>
<!-- 分類標籤 -->
<div class="mb-6 bg-white rounded-lg shadow p-4">
<div class="flex flex-wrap gap-2">
<button
onclick="setActiveCategory('all')"
id="cat-all"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors bg-blue-600 text-white"
>
全部法律 (<span id="countAll">0</span>)
</button>
<div class="flex items-center gap-2 ml-4">
<span class="text-sm text-gray-500">已通過:</span>
<button
onclick="setActiveCategory('passed-media')"
id="cat-passed-media"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors bg-green-100 text-green-700 hover:bg-green-200"
>
媒體相關 (<span id="countPassedMedia">0</span>)
</button>
<button
onclick="setActiveCategory('passed-security')"
id="cat-passed-security"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors bg-red-100 text-red-700 hover:bg-red-200"
>
國安相關 (<span id="countPassedSecurity">0</span>)
</button>
</div>
<div class="flex items-center gap-2 ml-4">
<span class="text-sm text-gray-500">尚未通過:</span>
<button
onclick="setActiveCategory('pending')"
id="cat-pending"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors bg-yellow-100 text-yellow-700 hover:bg-yellow-200"
>
審議中 (<span id="countPending">0</span>)
</button>
</div>
</div>
</div>
<!-- 法律卡片列表 -->
<div class="grid gap-4" id="lawCardsList"></div>
</div>
</div>
<!-- 法律詳細頁面 -->
<div id="lawDetailPage" class="hidden">
<!-- 詳細頁面內容將動態生成 -->
</div>
</div>
<script>
// 全域變數
let laws = [];
let billsData = [];
let searchResults = { laws: [], bills: [] };
let selectedLaw = null;
let showGuide = true;
let activeCategory = 'all';
let detailSearchTerm = '';
let showOnlyAntiChina = false;
let versions = [];
let bills = [];
let progressData = [];
let activeTab = 'timeline';
// 初始化
document.addEventListener('DOMContentLoaded', async function() {
await loadData();
lucide.createIcons();
});
// 載入資料
async function loadData() {
try {
// 載入法律基本資料
const basicResponse = await fetch('法律基本資料.csv');
const basicText = await basicResponse.text();
const parsedBasic = Papa.parse(basicText, {
header: true,
dynamicTyping: true,
skipEmptyLines: true
});
// 載入法律詳細數據
const detailResponse = await fetch('法律詳細數據.csv');
const detailText = await detailResponse.text();
const parsedDetail = Papa.parse(detailText, {
header: true,
dynamicTyping: true,
skipEmptyLines: true
});
laws = parsedBasic.data;
billsData = mergeBillsWithProgress(parsedDetail.data);
performSearch();
// 隱藏載入畫面,顯示主要內容
document.getElementById('loadingScreen').classList.add('hidden');
document.getElementById('mainContent').classList.remove('hidden');
// 重新創建圖標
lucide.createIcons();
} catch (error) {
console.error('載入資料時發生錯誤:', error);
document.getElementById('loadingScreen').innerHTML = `
<div class="text-center">
<p class="text-red-600">載入資料失敗</p>
</div>
`;
}
}
// 合併 bills 和 progress 資料
function mergeBillsWithProgress(detailData) {
const bills = detailData.filter(item => item['資料類型'] === 'bills');
const progress = detailData.filter(item => item['資料類型'] === 'progress');
const progressMap = {};
progress.forEach(p => {
const billNumber = p['關係文書/議案編號'];
if (billNumber && billNumber !== 'nan') {
progressMap[billNumber] = p;
}
});
const mergedBills = bills.map(bill => {
const billNumber = bill['關係文書/議案編號'];
const relatedProgress = progressMap[billNumber];
if (relatedProgress) {
return {
...bill,
'進度': relatedProgress['進度'] !== 'nan' ? relatedProgress['進度'] : bill['進度'],
'關係文書類型': relatedProgress['關係文書類型'] !== 'nan' ? relatedProgress['關係文書類型'] : bill['關係文書類型']
};
}
return bill;
});
return mergedBills;
}
// 處理值
function processValue(value) {
if (value === null || value === undefined || value === 'nan' || value === 'NaN') {
return null;
}
if (value === '' || value === ' ') {
return '(無資料)';
}
return value;
}
// 格式化日期
function formatDate(dateStr) {
if (!dateStr || dateStr === 'nan') return null;
try {
const date = new Date(dateStr);
return date.toLocaleDateString('zh-TW', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
} catch {
return dateStr;
}
}
// 根據法律名稱獲取防中關鍵字
function getAntiChinaKeywords(lawName) {
if (lawName.includes('兩岸人民關係條例') || lawName.includes('臺灣地區與大陸地區人民關係條例') ||
lawName.includes('台灣地區與大陸地區人民關係條例') || lawName.includes('兩岸條例')) {
return ['廣告', '國家核心關鍵技術', '陸資', '中資', '共諜'];
} else if (lawName.includes('刑法')) {
return [];
} else {
return ['中國', '大陸', '境外勢力', '滲透', '統戰', '境外敵對勢力', '中共',
'共諜', '紅媒', '親中', '中資', '陸資'];
}
}
// 切換使用說明
function toggleGuide() {
showGuide = !showGuide;
const container = document.getElementById('guideContainer');
const content = document.getElementById('guideContent');
const toggleText = document.getElementById('guideToggleText');
if (showGuide) {
container.classList.remove('collapsed');
content.style.display = 'grid';
toggleText.textContent = '收起';
} else {
container.classList.add('collapsed');
content.style.display = 'none';
toggleText.textContent = '展開';
}
}
// 處理搜尋輸入
function handleSearchInput(event) {
if (event.key === 'Enter') {
performSearch();
} else {
performSearch();
}
}
// 執行搜尋
function performSearch() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const searchField = document.getElementById('searchField').value;
if (!searchTerm) {
searchResults = { laws: laws, bills: [] };
} else {
// 過濾法律
const filteredLaws = laws.filter(law => {
const relatedBills = billsData.filter(bill =>
bill['法律編號'] === law['法律編號'] && bill['資料類型'] === 'bills'
);
switch (searchField) {
case 'all':
const lawMatch = (
(law['法律編號'] && law['法律編號'].toString().includes(searchTerm)) ||
(law['法律名稱'] && law['法律名稱'].toLowerCase().includes(searchTerm)) ||
(law['別名'] && law['別名'].toLowerCase().includes(searchTerm)) ||
(law['其他名稱'] && law['其他名稱'].toLowerCase().includes(searchTerm)) ||
(law['主管機關'] && law['主管機關'].toLowerCase().includes(searchTerm))
);
const billMatch = relatedBills.some(bill => (
(bill['關係文書/議案編號'] && bill['關係文書/議案編號'].toString().toLowerCase().includes(searchTerm)) ||
(bill['議案名稱'] && bill['議案名稱'].toLowerCase().includes(searchTerm)) ||
(bill['提案人'] && bill['提案人'] !== 'nan' && bill['提案人'].toLowerCase().includes(searchTerm)) ||
(bill['連署人'] && bill['連署人'] !== 'nan' && bill['連署人'].toLowerCase().includes(searchTerm)) ||
(bill['提案單位/提案委員'] && bill['提案單位/提案委員'].toLowerCase().includes(searchTerm)) ||
(bill['說明'] && bill['說明'] !== 'nan' && bill['說明'].toLowerCase().includes(searchTerm))
));
return lawMatch || billMatch;
case 'name':
return law['法律名稱'] && law['法律名稱'].toLowerCase().includes(searchTerm);
case 'billName':
return relatedBills.some(bill =>
bill['議案名稱'] && bill['議案名稱'].toLowerCase().includes(searchTerm)
);
case 'proposer':
return relatedBills.some(bill =>
(bill['提案人'] && bill['提案人'] !== 'nan' && bill['提案人'].toLowerCase().includes(searchTerm)) ||
(bill['連署人'] && bill['連署人'] !== 'nan' && bill['連署人'].toLowerCase().includes(searchTerm)) ||
(bill['提案單位/提案委員'] && bill['提案單位/提案委員'].toLowerCase().includes(searchTerm))
);
case 'billNumber':
return relatedBills.some(bill =>
bill['關係文書/議案編號'] && bill['關係文書/議案編號'].toString().toLowerCase().includes(searchTerm)
);
case 'description':
return relatedBills.some(bill =>
bill['說明'] && bill['說明'] !== 'nan' && bill['說明'].toLowerCase().includes(searchTerm)
);
default:
return true;
}
});
// 過濾議案
const filteredBills = billsData.filter(bill => {
if (bill['資料類型'] !== 'bills') return false;
switch (searchField) {
case 'all':
return (
(bill['關係文書/議案編號'] && bill['關係文書/議案編號'].toString().toLowerCase().includes(searchTerm)) ||
(bill['議案名稱'] && bill['議案名稱'].toLowerCase().includes(searchTerm)) ||
(bill['提案人'] && bill['提案人'] !== 'nan' && bill['提案人'].toLowerCase().includes(searchTerm)) ||
(bill['連署人'] && bill['連署人'] !== 'nan' && bill['連署人'].toLowerCase().includes(searchTerm)) ||
(bill['提案單位/提案委員'] && bill['提案單位/提案委員'].toLowerCase().includes(searchTerm)) ||
(bill['說明'] && bill['說明'] !== 'nan' && bill['說明'].toLowerCase().includes(searchTerm))
);
case 'billName':
return bill['議案名稱'] && bill['議案名稱'].toLowerCase().includes(searchTerm);
case 'proposer':
return (
(bill['提案人'] && bill['提案人'] !== 'nan' && bill['提案人'].toLowerCase().includes(searchTerm)) ||
(bill['連署人'] && bill['連署人'] !== 'nan' && bill['連署人'].toLowerCase().includes(searchTerm)) ||
(bill['提案單位/提案委員'] && bill['提案單位/提案委員'].toLowerCase().includes(searchTerm))
);
case 'billNumber':
return bill['關係文書/議案編號'] && bill['關係文書/議案編號'].toString().toLowerCase().includes(searchTerm);
case 'description':
return bill['說明'] && bill['說明'] !== 'nan' && bill['說明'].toLowerCase().includes(searchTerm);
default:
return false;
}
});
searchResults = { laws: filteredLaws, bills: filteredBills };
}
displayLaws();
displayBillSearchResults();
updateSearchStats();
}
// 顯示法律列表
function displayLaws() {
const lawsWithStats = searchResults.laws.map(law => {
const relatedBills = billsData.filter(bill =>
bill['法律編號'] === law['法律編號'] && bill['資料類型'] === 'bills'
);
// 判斷法律類別
let category = '國安相關';
const lawName = law['法律名稱'];
if (lawName.includes('廣播') || lawName.includes('電視') || lawName.includes('衛星') ||
lawName.includes('電信') || lawName.includes('通訊') || lawName.includes('媒體')) {
category = '媒體相關';
}
// 防中議案統計
let antiChinaKeywords = [];
let hasAntiChinaBills = false;
let antiChinaBillCount = 0;
if (lawName.includes('兩岸人民關係條例') || lawName.includes('臺灣地區與大陸地區人民關係條例') ||
lawName.includes('台灣地區與大陸地區人民關係條例') || lawName.includes('兩岸條例')) {
antiChinaKeywords = ['廣告', '國家核心關鍵技術', '陸資', '中資', '共諜'];
hasAntiChinaBills = relatedBills.some(bill => {
const checkText = `${bill['議案名稱'] || ''} ${bill['說明'] || ''}`.toLowerCase();
return antiChinaKeywords.some(keyword => checkText.includes(keyword.toLowerCase()));
});
antiChinaBillCount = relatedBills.filter(bill => {
const checkText = `${bill['議案名稱'] || ''} ${bill['說明'] || ''}`.toLowerCase();
return antiChinaKeywords.some(keyword => checkText.includes(keyword.toLowerCase()));
}).length;
} else if (lawName.includes('刑法')) {
const antiChinaBillNumbers = ['1031208070200900', '1040916070201100', '1051024070200800',
'1061206070200100', '1080429070200600', '1091111070200600'];
hasAntiChinaBills = relatedBills.some(bill =>
antiChinaBillNumbers.includes(bill['關係文書/議案編號']?.toString())
);
antiChinaBillCount = relatedBills.filter(bill =>
antiChinaBillNumbers.includes(bill['關係文書/議案編號']?.toString())
).length;
} else {
antiChinaKeywords = ['中國', '大陸', '境外勢力', '滲透', '統戰', '境外敵對勢力', '中共',
'共諜', '紅媒', '親中', '中資', '陸資'];
hasAntiChinaBills = relatedBills.some(bill => {
const checkText = `${bill['議案名稱'] || ''} ${bill['說明'] || ''}`.toLowerCase();
return antiChinaKeywords.some(keyword => checkText.includes(keyword.toLowerCase()));
});
antiChinaBillCount = relatedBills.filter(bill => {
const checkText = `${bill['議案名稱'] || ''} ${bill['說明'] || ''}`.toLowerCase();
return antiChinaKeywords.some(keyword => checkText.includes(keyword.toLowerCase()));
}).length;
}
return {
...law,
billCount: relatedBills.length,
category: category,
hasAntiChinaBills: hasAntiChinaBills,
antiChinaBillCount: antiChinaBillCount
};
});
// 分類法律
const categorizedLaws = {
passed: {
media: lawsWithStats.filter(law => law['法律狀態'] === '現行' && law.category === '媒體相關'),
security: lawsWithStats.filter(law => law['法律狀態'] === '現行' && law.category === '國安相關')
},
pending: lawsWithStats.filter(law => law['法律狀態'] !== '現行')
};
// 更新計數
document.getElementById('countAll').textContent = lawsWithStats.length;
document.getElementById('countPassedMedia').textContent = categorizedLaws.passed.media.length;
document.getElementById('countPassedSecurity').textContent = categorizedLaws.passed.security.length;
document.getElementById('countPending').textContent = categorizedLaws.pending.length;
// 根據分類顯示
let displayLaws = lawsWithStats;
switch(activeCategory) {
case 'passed-media':
displayLaws = categorizedLaws.passed.media;
break;
case 'passed-security':
displayLaws = categorizedLaws.passed.security;
break;
case 'pending':
displayLaws = categorizedLaws.pending;
break;
default:
displayLaws = lawsWithStats;
}
// 生成法律卡片
const container = document.getElementById('lawCardsList');
if (displayLaws.length === 0) {
container.innerHTML = `
<div class="bg-white rounded-lg shadow p-8 text-center text-gray-500">
此分類目前沒有法律資料
</div>
`;
return;
}
container.innerHTML = displayLaws.map((law, index) => `
<div class="bg-white rounded-lg shadow hover:shadow-md transition-shadow cursor-pointer" onclick="showLawDetail('${law['法律編號'] || index}')">
<div class="p-6">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center gap-3">
<i data-lucide="file-text" class="h-5 w-5 text-blue-600 flex-shrink-0"></i>
<h3 class="text-xl font-semibold text-gray-900">
${law['法律名稱']}
</h3>
${law['別名'] && processValue(law['別名']) ?
`<span class="text-sm text-gray-500">
(${processValue(law['別名'])})
</span>` : ''
}
<span class="ml-2 px-2 py-1 rounded-full text-xs ${
law.category === '媒體相關'
? 'bg-green-100 text-green-800'
: 'bg-blue-100 text-blue-800'
}">
${law.category}
</span>
${law.hasAntiChinaBills ? `
<button
onclick="event.stopPropagation(); showLawDetailWithAntiChina('${law['法律編號']}')"
class="ml-2 px-2 py-1 rounded-full text-xs bg-red-600 text-white flex items-center gap-1 hover:bg-red-700 transition-colors"
title="點擊查看防中議案"
>
<i data-lucide="alert-triangle" class="h-3 w-3"></i>
防中議案 (${law.antiChinaBillCount})
</button>
` : ''}
</div>
<div class="mt-3 grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="flex items-center text-sm text-gray-600">
<span class="font-medium mr-2">法律編號:</span>
${law['法律編號']}
</div>
<div class="flex items-center text-sm text-gray-600">
<span class="font-medium mr-2">類別:</span>
${processValue(law['類別']) || '未分類'}
</div>
<div class="flex items-center text-sm text-gray-600">
<span class="font-medium mr-2">狀態:</span>
<span class="px-2 py-1 rounded-full text-xs ${
law['法律狀態'] === '現行'
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}">
${law['法律狀態']}
</span>
</div>
<div class="flex items-center text-sm text-gray-600">
<span class="font-medium mr-2">主管機關:</span>
${processValue(law['主管機關']) || '未指定'}
</div>
</div>
<div class="mt-3 flex items-center justify-between">
<div class="text-sm text-gray-500">
<div>最新版本:${formatDate(law['最新版本日期'])} ${law['最新版本動作']}</div>
${law.billCount > 0 ? `
<div class="mt-1">
<span class="inline-flex items-center">
<i data-lucide="file-text" class="h-3 w-3 mr-1"></i>
相關議案 ${law.billCount}
</span>
</div>
` : ''}
</div>
<i data-lucide="chevron-right" class="h-5 w-5 text-gray-400"></i>
</div>
</div>
</div>
</div>
</div>
`).join('');
// 重新創建圖標
lucide.createIcons();
}
// 顯示議案搜尋結果
function displayBillSearchResults() {
const searchTerm = document.getElementById('searchInput').value;
const container = document.getElementById('billSearchResults');
const listContainer = document.getElementById('billSearchResultsList');
if (!searchTerm || searchResults.bills.length === 0) {
container.classList.add('hidden');
return;
}
container.classList.remove('hidden');
const billsToShow = searchResults.bills.slice(0, 5);
listContainer.innerHTML = billsToShow.map((bill, index) => {
const relatedLaw = laws.find(law => law['法律編號'] === bill['法律編號']);
const lawName = relatedLaw?.['法律名稱'] || '';
let isAntiChina = false;
if (lawName.includes('刑法')) {
const antiChinaBillNumbers = ['1031208070200900', '1040916070201100', '1051024070200800',
'1061206070200100', '1080429070200600', '1091111070200600'];
isAntiChina = antiChinaBillNumbers.includes(bill['關係文書/議案編號']?.toString());
} else {
const antiChinaKeywords = getAntiChinaKeywords(lawName);
const checkText = `${bill['議案名稱'] || ''} ${bill['說明'] || ''}`.toLowerCase();
isAntiChina = antiChinaKeywords.some(keyword => checkText.includes(keyword.toLowerCase()));
}
return `
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-start justify-between">
<div class="flex-1">
<h3 class="font-semibold text-gray-900 flex items-center">
<button
onclick="showLawDetailWithBill('${bill['法律編號']}', '${bill['關係文書/議案編號']}')"
class="hover:text-blue-600 text-left"
>
${bill['議案名稱']}
</button>
${isAntiChina ? `
<span class="ml-2 px-2 py-1 rounded-full text-xs bg-red-600 text-white flex items-center gap-1">
<i data-lucide="alert-triangle" class="h-3 w-3"></i>
防中議案
</span>
` : ''}
</h3>
<div class="mt-2 text-sm text-gray-600">
<div>議案編號:${bill['關係文書/議案編號']}</div>
<div>相關法律:
<button
onclick="event.stopPropagation(); showLawDetail('${relatedLaw?.['法律編號']}')"
class="text-blue-600 hover:text-blue-800"
>
${relatedLaw?.['法律名稱']}
</button>
</div>
<div>提案人:${bill['提案單位/提案委員'] || '-'}</div>
<div>狀態:${bill['提案/議案最新狀態'] || '-'}</div>
</div>
${bill['說明'] && bill['說明'] !== 'nan' ? `
<div class="mt-2 text-sm text-gray-500">
${bill['說明'].length > 100 ?
bill['說明'].substring(0, 100) + '...' :
bill['說明']
}
</div>
` : ''}
</div>
<button
onclick="showLawDetailWithBill('${bill['法律編號']}', '${bill['關係文書/議案編號']}')"
class="ml-4 text-blue-600 hover:text-blue-800 text-sm"
>
查看詳細 →
</button>
</div>
</div>
`;
}).join('');
if (searchResults.bills.length > 5) {
listContainer.innerHTML += `
<div class="text-center text-sm text-gray-500">
還有 ${searchResults.bills.length - 5} 筆議案...
</div>
`;
}
lucide.createIcons();
}
// 更新搜尋統計
function updateSearchStats() {
const searchTerm = document.getElementById('searchInput').value;
const statsElement = document.getElementById('searchStats');
if (searchTerm) {
let html = `<div>找到 ${searchResults.laws.length} 筆法律</div>`;
if (searchResults.bills.length > 0) {
html += `<div>找到 ${searchResults.bills.length} 筆相關議案</div>`;
}
statsElement.innerHTML = html;
} else {
statsElement.innerHTML = `<div>共 ${searchResults.laws.length} 筆法律資料</div>`;
}
}
// 設定分類
function setActiveCategory(category) {
activeCategory = category;
// 更新按鈕樣式
const buttons = {
'all': document.getElementById('cat-all'),
'passed-media': document.getElementById('cat-passed-media'),
'passed-security': document.getElementById('cat-passed-security'),
'pending': document.getElementById('cat-pending')
};
Object.keys(buttons).forEach(key => {
if (key === category) {
buttons[key].className = 'px-4 py-2 rounded-full text-sm font-medium transition-colors ' +
(key === 'all' ? 'bg-blue-600 text-white' :
key === 'passed-media' ? 'bg-green-600 text-white' :
key === 'passed-security' ? 'bg-red-600 text-white' :
'bg-yellow-600 text-white');
} else {
buttons[key].className = 'px-4 py-2 rounded-full text-sm font-medium transition-colors ' +
(key === 'all' ? 'bg-gray-100 text-gray-700 hover:bg-gray-200' :
key === 'passed-media' ? 'bg-green-100 text-green-700 hover:bg-green-200' :
key === 'passed-security' ? 'bg-red-100 text-red-700 hover:bg-red-200' :
'bg-yellow-100 text-yellow-700 hover:bg-yellow-200');
}
});
displayLaws();
}
// 顯示法律詳細資訊
async function showLawDetail(lawId, autoShowAntiChina = false, targetBillNumber = null) {
selectedLaw = laws.find(l => l['法律編號'] == lawId);
if (!selectedLaw) return;
showOnlyAntiChina = autoShowAntiChina;
activeTab = targetBillNumber ? 'bills' : 'timeline';
// 載入詳細資料
await loadLawDetails();
// 切換頁面
document.getElementById('lawListPage').classList.add('hidden');
document.getElementById('lawDetailPage').classList.remove('hidden');
// 生成詳細頁面內容
renderLawDetailPage();
// 如果有目標議案,滾動到該議案
if (targetBillNumber) {
setTimeout(() => {
const targetElement = document.querySelector(`[data-bill-number="${targetBillNumber}"]`);
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 展開該議案
const expandButton = targetElement.querySelector('[data-expand-bill]');
if (expandButton) {
expandButton.click();
}
}
}, 500);
}
}
// 顯示法律詳細資訊(含防中篩選)
function showLawDetailWithAntiChina(lawId) {
showLawDetail(lawId, true);
}
// 顯示法律詳細資訊(含議案跳轉)
function showLawDetailWithBill(lawId, billNumber) {
showLawDetail(lawId, false, billNumber);
}
// 載入法律詳細資料
async function loadLawDetails() {
try {
// 載入版本資料
const versionResponse = await fetch('法律版本.csv');
const versionText = await versionResponse.text();
const parsedVersion = Papa.parse(versionText, {
header: true,
dynamicTyping: true,
skipEmptyLines: true
});
versions = parsedVersion.data.filter(v => v['法律編號'] === selectedLaw['法律編號']);
// 篩選議案
const lawBills = billsData.filter(b => b['法律編號'] === selectedLaw['法律編號'] && b['資料類型'] === 'bills');
// 加上防中標記
bills = lawBills.map(bill => ({
...bill,
isAntiChina: isAntiChinaRelated(
`${bill['議案名稱'] || ''} ${bill['說明'] || ''}`,
bill['關係文書/議案編號']
)
}));
// 載入進度資料
try {
const progressResponse = await fetch('paste.txt');
const progressText = await progressResponse.text();
progressData = JSON.parse(progressText);
} catch (e) {
progressData = [];
}
} catch (error) {
console.error('載入詳細資料時發生錯誤:', error);
}
}
// 檢查是否為防中相關
function isAntiChinaRelated(text, billNumber = null) {
if (!text) return false;
if (selectedLaw['法律名稱'].includes('刑法')) {
const antiChinaBillNumbers = ['1031208070200900', '1040916070201100', '1051024070200800',
'1061206070200100', '1080429070200600', '1091111070200600'];
return billNumber && antiChinaBillNumbers.includes(billNumber.toString());
}
const antiChinaKeywords = getAntiChinaKeywords(selectedLaw['法律名稱']);
const lowerText = text.toLowerCase();
return antiChinaKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()));
}
// 渲染法律詳細頁面
function renderLawDetailPage() {
const antiChinaBillCount = bills.filter(bill => bill.isAntiChina).length;
const antiChinaKeywords = getAntiChinaKeywords(selectedLaw['法律名稱']);
const detailPage = document.getElementById('lawDetailPage');
detailPage.innerHTML = `
<div class="min-h-screen bg-gray-50">
<header class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<button
onclick="backToList()"
class="mb-4 text-blue-600 hover:text-blue-800 font-medium flex items-center"
>
<i data-lucide="chevron-right" class="h-4 w-4 rotate-180 mr-1"></i>
返回列表
</button>
<div class="flex items-start justify-between">
<div>
<h1 class="text-3xl font-bold text-gray-900">${selectedLaw['法律名稱']}</h1>
${selectedLaw['別名'] && processValue(selectedLaw['別名']) ?
`<p class="mt-1 text-gray-600">別名:${processValue(selectedLaw['別名'])}</p>` : ''
}
<div class="mt-2 flex items-center gap-4 text-sm text-gray-600">
<span>法律編號:${selectedLaw['法律編號']}</span>
<span>主管機關:${processValue(selectedLaw['主管機關']) || '未指定'}</span>
<span class="px-2 py-1 rounded-full text-xs ${
selectedLaw['法律狀態'] === '現行'
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}">
${selectedLaw['法律狀態']}
</span>
</div>
</div>
${antiChinaBillCount > 0 ? `
<div class="flex flex-col items-end gap-2">
<button
onclick="toggleAntiChinaFilter()"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors flex items-center gap-2 ${
showOnlyAntiChina
? 'bg-red-600 text-white'
: 'bg-red-100 text-red-700 hover:bg-red-200'
}"
>
<i data-lucide="shield" class="h-4 w-4"></i>
防中議案 (${antiChinaBillCount})
${showOnlyAntiChina ? ' ✓' : ''}
</button>
${antiChinaKeywords.length > 0 ? `
<div class="text-xs text-gray-500 text-right max-w-xs">
關鍵字:${antiChinaKeywords.join('、')}
</div>
` : ''}
</div>
` : ''}
</div>
</div>
</header>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Tab 切換 -->
<div class="bg-white rounded-lg shadow mb-6">
<div class="border-b">
<nav class="flex -mb-px">
<button
onclick="setActiveTab('timeline')"
id="tab-timeline"
class="py-3 px-6 border-b-2 font-medium text-sm ${
activeTab === 'timeline'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}"
>
<i data-lucide="calendar" class="inline-block h-4 w-4 mr-2"></i>
歷程時間軸
</button>
<button
onclick="setActiveTab('versions')"
id="tab-versions"
class="py-3 px-6 border-b-2 font-medium text-sm ${
activeTab === 'versions'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}"
>
<i data-lucide="file-text" class="inline-block h-4 w-4 mr-2"></i>
版本列表
</button>
<button
onclick="setActiveTab('bills')"
id="tab-bills"
class="py-3 px-6 border-b-2 font-medium text-sm ${
activeTab === 'bills'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}"
>
<i data-lucide="users" class="inline-block h-4 w-4 mr-2"></i>
相關議案 (<span id="billsCount">0</span>)
</button>
</nav>
</div>
<!-- 法律內容搜尋 -->
<div class="p-4 bg-gray-50">
<div class="relative">
<i data-lucide="search" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4"></i>
<input
type="text"
id="detailSearchInput"
placeholder="在${selectedLaw['法律名稱']}的議案中搜尋..."
class="w-full pl-10 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onkeyup="performDetailSearch()"
/>
</div>
<div id="detailSearchStats" class="mt-2 text-xs text-gray-600"></div>
</div>
</div>
<!-- 內容區域 -->
<div id="tabContent"></div>
</div>
</div>
`;
renderTabContent();
lucide.createIcons();
}
// 返回列表
function backToList() {
document.getElementById('lawListPage').classList.remove('hidden');
document.getElementById('lawDetailPage').classList.add('hidden');
selectedLaw = null;
showOnlyAntiChina = false;
detailSearchTerm = '';
}
// 切換防中篩選
function toggleAntiChinaFilter() {
showOnlyAntiChina = !showOnlyAntiChina;
renderLawDetailPage();
}
// 設定Tab
function setActiveTab(tab) {
activeTab = tab;
// 更新Tab樣式
['timeline', 'versions', 'bills'].forEach(t => {
const tabElement = document.getElementById(`tab-${t}`);
if (t === tab) {
tabElement.className = 'py-3 px-6 border-b-2 font-medium text-sm border-blue-500 text-blue-600';
} else {
tabElement.className = 'py-3 px-6 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700';
}
});
renderTabContent();
}
// 詳細搜尋
function performDetailSearch() {
detailSearchTerm = document.getElementById('detailSearchInput').value.toLowerCase();
renderTabContent();
}
// 渲染Tab內容
function renderTabContent() {
const container = document.getElementById('tabContent');
// 過濾議案
let displayBills = showOnlyAntiChina ? bills.filter(bill => bill.isAntiChina) : bills;
if (detailSearchTerm) {
displayBills = displayBills.filter(bill =>
(bill['議案名稱'] && bill['議案名稱'].toLowerCase().includes(detailSearchTerm)) ||
(bill['議案編號'] && bill['議案編號'].toString().toLowerCase().includes(detailSearchTerm)) ||
(bill['關係文書/議案編號'] && bill['關係文書/議案編號'].toString().toLowerCase().includes(detailSearchTerm)) ||
(bill['提案單位/提案委員'] && bill['提案單位/提案委員'].toLowerCase().includes(detailSearchTerm)) ||
(bill['說明'] && bill['說明'].toLowerCase().includes(detailSearchTerm))
);
}
// 更新議案數量
document.getElementById('billsCount').textContent = displayBills.length;
// 更新搜尋統計
if (detailSearchTerm) {
document.getElementById('detailSearchStats').textContent = `找到 ${displayBills.length} 筆相關議案`;
} else {
document.getElementById('detailSearchStats').textContent = '';
}
switch(activeTab) {
case 'timeline':
container.innerHTML = renderTimelineView(displayBills);
break;
case 'versions':
container.innerHTML = renderVersionsView();
break;
case 'bills':
container.innerHTML = renderBillsView(displayBills);
break;
}
lucide.createIcons();
}
// 渲染時間軸視圖
function renderTimelineView(displayBills) {
const events = createTimelineEvents(displayBills);
if (events.length === 0) {
return `
<div class="bg-white rounded-lg shadow p-8 text-center text-gray-500">
尚無歷程資料
</div>
`;
}
return `
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-6 flex items-center">
<i data-lucide="clock" class="h-5 w-5 mr-2 text-gray-600"></i>
立法歷程時間軸
</h2>
<div class="relative">
<div class="timeline-line"></div>
<div class="space-y-6">
${events.map((event, index) => renderTimelineEvent(event, index)).join('')}
</div>
</div>
</div>
`;
}
// 建立時間軸事件
function createTimelineEvents(displayBills) {
const events = [];
// 版本事件
versions.forEach(version => {
if (version['日期'] && version['日期'] !== 'nan') {
events.push({
date: new Date(version['日期']),
type: 'version',
title: `${version['動作']}版本`,
description: `版本編號:${version['版本編號']}`,
status: version['現行版本'] === '現行' ? '現行' : '非現行',
data: version
});
}
});
// 議案事件
displayBills.forEach((bill, billIndex) => {
if (bill['會議/提案日期'] && bill['會議/提案日期'] !== 'nan') {
events.push({
date: new Date(bill['會議/提案日期']),
type: 'bill',
title: '議案提出',
description: bill['議案名稱'],
billIndex: billIndex,
billData: bill,
isAntiChina: bill.isAntiChina
});
}
});
// 排序
events.sort((a, b) => b.date - a.date);
return events;
}
// 渲染時間軸事件
function renderTimelineEvent(event, index) {
const getEventIcon = () => {
if (event.type === 'version') {
return event.status === '現行' ?
'<i data-lucide="check-circle" class="h-5 w-5 text-green-600"></i>' :
'<i data-lucide="circle" class="h-5 w-5 text-gray-400"></i>';
}
if (event.type === 'bill') {
return event.isAntiChina ?
'<i data-lucide="alert-triangle" class="h-5 w-5 text-red-600"></i>' :
'<i data-lucide="file-text" class="h-5 w-5 text-purple-600"></i>';
}
return '<i data-lucide="alert-circle" class="h-5 w-5 text-blue-600"></i>';
};
const getEventColor = () => {
if (event.type === 'version') {
return event.status === '現行' ? 'bg-green-100 border-green-300' : 'bg-gray-100 border-gray-300';
}
if (event.type === 'bill') {
return event.isAntiChina ? 'bg-red-100 border-red-300' : 'bg-purple-100 border-purple-300';
}
return 'bg-blue-100 border-blue-300';
};
return `
<div class="relative flex items-start">
<div class="timeline-node"></div>
<div class="ml-16 flex-1">
<div class="p-4 rounded-lg border ${getEventColor()}">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center">
${getEventIcon()}
<h3 class="ml-2 font-semibold text-gray-900">
${event.title}
${event.isAntiChina ? `
<span class="ml-2 px-2 py-1 rounded-full text-xs bg-red-600 text-white">
防中議案
</span>
` : ''}
</h3>
</div>
<p class="mt-1 text-sm text-gray-600">${event.description}</p>
<p class="mt-1 text-xs text-gray-500">
${event.date.toLocaleDateString('zh-TW', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</p>
</div>
${event.type === 'version' && event.data ? `
<button
onclick="toggleTimelineDetail('version-${index}')"
class="ml-4 text-blue-600 hover:text-blue-800 text-sm"
>
<span id="toggle-text-version-${index}">詳細</span>
</button>
` : ''}
${event.type === 'bill' ? `
<button
onclick="toggleTimelineDetail('bill-${index}')"
class="ml-4 text-purple-600 hover:text-purple-800 text-sm font-medium"
>
<span id="toggle-text-bill-${index}">查看議案</span> →
</button>
` : ''}
</div>
${event.type === 'version' ? `
<div id="detail-version-${index}" class="hidden mt-4 pt-4 border-t border-gray-200">
<div class="grid grid-cols-2 gap-3 text-sm">
${event.data['委員會'] ? `
<div>
<span class="font-medium">審查委員會:</span>
<span class="ml-2 text-gray-600">${event.data['委員會']}</span>
</div>
` : ''}
<div>
<span class="font-medium">歷程總數:</span>
<span class="ml-2 text-gray-600">${event.data['歷程總數']} 次</span>
</div>
<div>
<span class="font-medium">一讀:</span>
<span class="ml-2 text-gray-600">${event.data['一讀次數']} 次</span>
</div>
<div>
<span class="font-medium">二讀:</span>
<span class="ml-2 text-gray-600">${event.data['二讀次數']} 次</span>
</div>
<div>
<span class="font-medium">三讀:</span>
<span class="ml-2 text-gray-600">${event.data['三讀次數']} 次</span>
</div>
<div>
<span class="font-medium">委員會審查:</span>
<span class="ml-2 text-gray-600">${event.data['委員會審查次數']} 次</span>
</div>
</div>
</div>
` : ''}
${event.type === 'bill' ? `
<div id="detail-bill-${index}" class="hidden mt-4 pt-4 border-t border-gray-200">
${renderBillDetail(event.billData, event.isAntiChina)}
</div>
` : ''}
</div>
</div>
</div>
`;
}
// 切換時間軸詳細資訊
function toggleTimelineDetail(id) {
const detailElement = document.getElementById(`detail-${id}`);
const toggleText = document.getElementById(`toggle-text-${id}`);
if (detailElement.classList.contains('hidden')) {
detailElement.classList.remove('hidden');
toggleText.textContent = '收起';
} else {
detailElement.classList.add('hidden');
toggleText.textContent = id.includes('version') ? '詳細' : '查看議案';
}
}
// 渲染議案詳細資訊
function renderBillDetail(bill, isAntiChina) {
const antiChinaKeywords = getAntiChinaKeywords(selectedLaw['法律名稱']);
let html = `
<div class="grid grid-cols-2 gap-3 text-sm mb-4">
<div>
<span class="font-medium">提案人:</span>
<span class="ml-2 text-gray-600">${bill['提案單位/提案委員']}</span>
</div>
<div>
<span class="font-medium">狀態:</span>
<span class="ml-2 text-gray-600">${bill['提案/議案最新狀態']}</span>
</div>
<div>
<span class="font-medium">類別:</span>
<span class="ml-2 text-gray-600">${bill['議案類別']}</span>
</div>
<div>
<span class="font-medium">議案編號:</span>
<span class="ml-2 text-gray-600">${bill['關係文書/議案編號']}</span>
</div>
</div>
`;
// 議案說明
if (bill['說明'] && bill['說明'] !== 'nan') {
let text = bill['說明'];
// 高亮防中關鍵字
if (isAntiChina && antiChinaKeywords.length > 0) {
antiChinaKeywords.forEach(keyword => {
const regex = new RegExp(`(${keyword})`, 'gi');
text = text.replace(regex, '【$1】');
});
}
html += `
<div class="bg-gray-50 rounded p-4">
<h4 class="font-medium text-gray-900 mb-2">議案說明</h4>
<p class="text-sm text-gray-600 whitespace-pre-wrap">${text}</p>
</div>
`;
}
// 附件
const attachments = [];
for (let i = 1; i <= 6; i++) {
const name = bill[`附件${i}_名稱`];
const url = bill[`附件${i}_網址`];
if (name && name !== 'nan' && url && url !== 'nan') {
attachments.push({ name, url, index: i });
}
}
if (attachments.length > 0) {
html += `
<div class="mt-4">
<h4 class="font-medium text-gray-900 mb-2">相關附件</h4>
<div class="space-y-1">
${attachments.map(attachment => `
<div>
<a
href="${attachment.url}"
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 hover:text-blue-800 text-sm"
>
${attachment.name}
</a>
</div>
`).join('')}
</div>
</div>
`;
}
// 公報連結
if (bill['公報資訊網'] && bill['公報資訊網'] !== 'nan') {
html += `
<div class="mt-4">
<a
href="${bill['公報資訊網']}"
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 hover:text-blue-800 text-sm font-medium"
>
查看立法院議事暨公報資訊網 →
</a>
</div>
`;
}
return html;
}
// 渲染版本列表視圖
function renderVersionsView() {
if (versions.length === 0) {
return `
<div class="bg-white rounded-lg shadow p-8 text-center text-gray-500">
尚無版本資料
</div>
`;
}
const sortedVersions = [...versions].sort((a, b) => {
const dateA = new Date(a['日期']);
const dateB = new Date(b['日期']);
return dateB - dateA;
});
return `
<div class="bg-white rounded-lg shadow">
<div class="p-6">
<h2 class="text-xl font-semibold mb-6 flex items-center">
<i data-lucide="file-text" class="h-5 w-5 mr-2 text-gray-600"></i>
法律版本列表
</h2>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
版本日期
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
動作
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
狀態
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
委員會
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
歷程統計
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
${sortedVersions.map((version, index) => `
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
${formatDate(version['日期'])}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<span class="px-2 py-1 rounded-full text-xs ${
version['動作'] === '制定'
? 'bg-blue-100 text-blue-800'
: 'bg-yellow-100 text-yellow-800'
}">
${version['動作']}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<span class="px-2 py-1 rounded-full text-xs ${
version['現行版本'] === '現行'
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}">
${version['現行版本']}
</span>
</td>
<td class="px-6 py-4 text-sm text-gray-900">
${processValue(version['委員會']) || '-'}
</td>
<td class="px-6 py-4 text-sm text-gray-600">
<div class="space-y-1">
<div>總計:${version['歷程總數']} 次</div>
<div class="text-xs">
一讀 ${version['一讀次數']} /
二讀 ${version['二讀次數']} /
三讀 ${version['三讀次數']}
</div>
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</div>
`;
}
// 渲染議案列表視圖
function renderBillsView(displayBills) {
if (displayBills.length === 0) {
return `
<div class="bg-white rounded-lg shadow p-8 text-center text-gray-500">
尚無相關議案資料
</div>
`;
}
const sortedBills = [...displayBills].sort((a, b) => {
const dateA = a['會議/提案日期'] ? new Date(a['會議/提案日期']) : new Date(0);
const dateB = b['會議/提案日期'] ? new Date(b['會議/提案日期']) : new Date(0);
return dateB - dateA;
});
return `
<div class="space-y-4">
${sortedBills.map((bill, index) => renderBillCard(bill, index)).join('')}
</div>
`;
}
// 渲染議案卡片
function renderBillCard(bill, index) {
// 解析議案流程
let billProgress = [];
try {
if (bill['議案流程'] && bill['議案流程'] !== 'nan') {
billProgress = JSON.parse(bill['議案流程']);
}
} catch (e) {
console.error('解析議案流程失敗:', e);
}
return `
<div class="bg-white rounded-lg shadow overflow-hidden" data-bill-number="${bill['關係文書/議案編號']}">
<div
class="p-6 cursor-pointer hover:bg-gray-50"
onclick="toggleBillDetail('bill-card-${index}')"
data-expand-bill
>
<div class="flex items-start justify-between">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 mb-2 flex items-center">
${bill['議案名稱']}
${bill.isAntiChina ? `
<span class="ml-2 px-2 py-1 rounded-full text-xs bg-red-600 text-white flex items-center gap-1">
<i data-lucide="alert-triangle" class="h-3 w-3"></i>
防中議案
</span>
` : ''}
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
<div>
<span class="font-medium text-gray-600">議案編號:</span>
<span class="ml-2">${bill['關係文書/議案編號'] || '-'}</span>
</div>
<div>
<span class="font-medium text-gray-600">議案類別:</span>
<span class="ml-2">${bill['議案類別'] || '-'}</span>
</div>
<div>
<span class="font-medium text-gray-600">提案日期:</span>
<span class="ml-2">${formatDate(bill['會議/提案日期']) || '-'}</span>
</div>
<div class="md:col-span-2">
<span class="font-medium text-gray-600">最新狀態:</span>
<span class="ml-2 px-2 py-1 rounded-full text-xs ${
bill['提案/議案最新狀態'] === '三讀'
? 'bg-green-100 text-green-800'
: bill['提案/議案最新狀態'] === '交付審查'
? 'bg-yellow-100 text-yellow-800'
: 'bg-gray-100 text-gray-800'
}">
${bill['提案/議案最新狀態'] || '-'}
</span>
${bill['進度'] && bill['進度'] !== 'nan' ? `
<span class="ml-2 text-sm text-gray-600">
(進度:${bill['進度']})
</span>
` : ''}
</div>
</div>
<div class="mt-3 text-sm text-gray-600">
<span class="font-medium">提案人:</span>
<span class="ml-2">${processValue(bill['提案單位/提案委員']) || '-'}</span>
</div>
</div>
<i data-lucide="chevron-right" id="chevron-bill-card-${index}"
class="h-5 w-5 text-gray-400 transform transition-transform"></i>
</div>
</div>
<div id="detail-bill-card-${index}" class="hidden border-t px-6 py-4 bg-gray-50">
${renderBillCardDetail(bill, billProgress)}
</div>
</div>
`;
}
// 切換議案詳細資訊
function toggleBillDetail(id) {
const detailElement = document.getElementById(`detail-${id}`);
const chevron = document.getElementById(`chevron-${id}`);
if (detailElement.classList.contains('hidden')) {
detailElement.classList.remove('hidden');
chevron.classList.add('rotate-90');
} else {
detailElement.classList.add('hidden');
chevron.classList.remove('rotate-90');
}
}
// 渲染議案卡片詳細資訊
function renderBillCardDetail(bill, billProgress) {
let html = '';
// 說明
if (bill['說明'] && bill['說明'] !== 'nan') {
html += `
<div class="mb-4">
<h4 class="font-medium text-gray-900 mb-2">說明</h4>
<p class="text-sm text-gray-600 whitespace-pre-wrap">${bill['說明']}</p>
</div>
`;
}
// 議案流程
if (billProgress.length > 0) {
html += `
<div>
<h4 class="font-medium text-gray-900 mb-3">議案流程</h4>
<div class="space-y-2">
${billProgress.reverse().map((progress, pIndex) => `
<div class="bg-white rounded p-3 text-sm">
<div class="flex items-center justify-between">
<div>
<span class="font-medium">${progress['狀態']}</span>
<span class="mx-2 text-gray-400">|</span>
<span class="text-gray-600">${progress['院會/委員會']}</span>
<span class="mx-2 text-gray-400">|</span>
<span class="text-gray-600">會期 ${progress['會期']}</span>
</div>
<div class="text-gray-500">
${progress['日期'] ? progress['日期'].join(', ') : ''}
</div>
</div>
</div>
`).join('')}
</div>
</div>
`;
}
// 附件
const attachments = [];
for (let i = 1; i <= 6; i++) {
const name = bill[`附件${i}_名稱`];
const url = bill[`附件${i}_網址`];
if (name && name !== 'nan' && url && url !== 'nan') {
attachments.push({ name, url, index: i });
}
}
if (attachments.length > 0) {
html += `
<div class="mt-4">
<h4 class="font-medium text-gray-900 mb-2">相關附件</h4>
<div class="space-y-1">
${attachments.map(attachment => `
<div>
<a
href="${attachment.url}"
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 hover:text-blue-800 text-sm"
>
${attachment.name}
</a>
</div>
`).join('')}
</div>
</div>
`;
}
// 公報連結
if (bill['公報資訊網'] && bill['公報資訊網'] !== 'nan') {
html += `
<div class="mt-4">
<a
href="${bill['公報資訊網']}"
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 hover:text-blue-800 text-sm font-medium"
>
查看立法院議事暨公報資訊網 →
</a>
</div>
`;
}
return html;
}
</script>
</body>
</html>