|
<head> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>地址測試</title> |
|
<link rel="icon" href="/favicon.ico" type="image/x-icon"> |
|
</head> |
|
<body> |
|
|
|
<main style="width: 20em; margin-inline: auto;"> |
|
<div class="progessbar" style="position: relative; text-align: center; border: 1px dashed blue; height: 16px; width: 100%;"> |
|
<div class="text" style="z-index:1; position: absolute; left: 0; top: 0; width: 100%; line-height: 16px;"></div> |
|
<div class="bar" style="position: absolute; left: 0; top: 0; height: 16px; background-color: green;"></div> |
|
</div> |
|
<div class="msg"></div> |
|
<hr> |
|
<div id="addr" style="height: 70px"> |
|
<select id="county"> |
|
<option value="">請選擇縣市</option> |
|
<option value="彰化縣">彰化縣</option> |
|
<option value="新北市">新北市</option> |
|
<option value="新竹市">新竹市</option> |
|
<option value="新竹縣">新竹縣</option> |
|
<option value="桃園市">桃園市</option> |
|
<option value="臺中市">臺中市</option> |
|
<option value="臺北市">臺北市</option> |
|
<option value="臺東縣">臺東縣</option> |
|
<option value="苗栗縣">苗栗縣</option> |
|
<option value="金門縣">金門縣</option> |
|
<option value="雲林縣">雲林縣</option> |
|
</select> |
|
</div> |
|
<hr> |
|
<input type="text" style="width: 100%"/> |
|
<hr> |
|
<div id="charset" style="display: inline-block; width: fit-content; margin-right: 1em;"> |
|
<input type="button" name="鄰" value="鄰" /> |
|
<input type="button" name="路" value="路" /> |
|
<input type="button" name="街" value="街" /> |
|
<input type="button" name="段" value="段" /> |
|
<input type="button" name="巷" value="巷" /> |
|
<input type="button" name="弄" value="弄" /> |
|
<input type="button" name="號" value="號" /> |
|
<input type="button" name="樓" value="樓" /> |
|
<input type="button" name="之" value="之" /> |
|
</div> |
|
<input type="button" name="finish" value="完成填寫" style="background: cyan;" /> |
|
<hr> |
|
<textArea disabled style="min-width: 100%; width: fit-content; height: 300px"></textArea> |
|
</main> |
|
</body> |
|
|
|
<script> |
|
const container = document.getElementById('addr') |
|
const address = document.querySelector('input') |
|
const log = document.querySelector('textArea') |
|
|
|
// Change Listener for each address selector |
|
const nextLevel = (event) => { |
|
const ele = event.target |
|
Array.from(container.querySelectorAll('select')) |
|
.filter(sibling => sibling.id > ele.id) |
|
.forEach(sibling => container.removeChild(sibling)) |
|
|
|
if (!ele.value) return |
|
|
|
const pathList = Array.from(container.children) |
|
.map(select => select.value) |
|
const apiUrl = `https://4ba.tw/address/${pathList.join('/')}/index.json` |
|
log.textContent += `${apiUrl}\n` |
|
if (pathList[3]) pathList[3] += "鄰" |
|
address.value = pathList.join('') |
|
fetch(apiUrl) |
|
.then(response => { |
|
if (!response.ok) return |
|
return response.json() |
|
}) |
|
.then(data => { |
|
if (data && data.length > 0) { |
|
log.textContent += `${data.length} options\n` |
|
const nextSelect = document.createElement('select') |
|
nextSelect.id = `s${container.children.length}` |
|
const defaultOption = document.createElement('option') |
|
defaultOption.value = '' |
|
defaultOption.textContent = '請選擇' |
|
nextSelect.appendChild(defaultOption) |
|
|
|
data.forEach(optionValue => { |
|
const option = document.createElement('option') |
|
option.value = optionValue |
|
option.textContent = optionValue |
|
nextSelect.appendChild(option) |
|
}) |
|
|
|
container.appendChild(nextSelect) |
|
nextSelect.addEventListener('change', nextLevel) |
|
} else { |
|
log.textContent += "END of Address\n" |
|
} |
|
}) |
|
} |
|
const county = document.getElementById('county') |
|
county.addEventListener('change', nextLevel) |
|
|
|
// Add Chinese char into address bar |
|
const charset = document.getElementById('charset') |
|
Array.from(charset.children).forEach(char => { |
|
char.onclick = () => { |
|
address.value += char.name |
|
} |
|
}) |
|
</script> |
|
<script> |
|
// 起始時間(計時器的啟動時間)。 |
|
const startTime = new Date().getTime() |
|
// 目標時間(要倒數幾秒)。 |
|
const targetSeconds = 30 |
|
// 初始化。 |
|
init(targetSeconds) |
|
|
|
// timer. |
|
var timer = function (startTime) { |
|
// 當前時間。 |
|
var currentTime = new Date().getTime() |
|
// 當前時間 - 起始時間 = 經過時間。(因為不需要毫秒,所以將結果除以1000。) |
|
var diffSec = Math.round((currentTime - startTime) / 1000) |
|
// 目標時間 - 經過時間 = 剩餘時間。 |
|
var remainingTime = targetSeconds - diffSec |
|
// update progess. |
|
update(remainingTime) |
|
if (remainingTime <= 0) { |
|
// stop the timer. |
|
clearInterval(timerId) |
|
// 確保最後顯示的時間為00:00 |
|
update(0) |
|
// do anything you want to. |
|
document.querySelector(".msg").textContent = "time up!" |
|
} |
|
} |
|
|
|
// start the timer. |
|
var timerId = setInterval( function () { timer(startTime); }, 1000) |
|
|
|
// 初始化。此處借用update函式來初次設定進度條。 |
|
function init(seconds) { |
|
update(seconds) |
|
} |
|
|
|
// update progess with the timer. |
|
function update (seconds) { |
|
barRenderer(seconds) |
|
textRenderer(seconds) |
|
} |
|
|
|
// refresh the bar. |
|
function barRenderer (seconds) { |
|
const percent = (seconds / targetSeconds) * 100 |
|
const text = document.querySelector(".bar") |
|
text.style.width = percent + "%" |
|
} |
|
|
|
// refresh the text of the bar. |
|
function textRenderer (seconds) { |
|
var sec = seconds % 60 |
|
var min = Math.floor(seconds / 60) |
|
|
|
/* 兩種作法都可以 */ |
|
//min = min > 9 ? min : "0" + min |
|
//sec = sec > 9 ? sec : "0" + sec |
|
min = min.toString().padStart(2, '0') |
|
sec = sec.toString().padStart(2, '0') |
|
|
|
document.querySelector(".text").textContent = min + ":" + sec |
|
} |
|
</script> |
TO ALL
這份腳本使用 Makefile,來正規化罷免選區所在的11個縣市政府的門牌資料
目前共有 10750個村里 698萬筆門牌
(尚有
南投縣
、連江縣
、花蓮縣
和基隆市
未取得資料集,已要求 puma 委員索取,FYI @audreyt )正規化後的資料可使用指令
make addr address.misc
窮舉出所有地址,製作多級目錄和用於描述當前目錄的index.json
將之作為伺服器的靜態資源即可完成門牌查詢API
目前成果由我的 VPS 提供API服務: https://4ba.tw/address/
亦可使用指令
wget --recursive https://4ba.tw/address
來鏡像所有資料我製作了一個示範頁面,用於展示如何將 API 用於快速登打連署人地址