CVNSS commited on
Commit
6cf2aac
·
verified ·
1 Parent(s): 0439ae0

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +104 -178
index.html CHANGED
@@ -2,203 +2,129 @@
2
  <html lang="vi">
3
  <head>
4
  <meta charset="UTF-8">
5
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
- <title>Tra cứu phường/xã cũ mới sáp nhập | Tất cả tỉnh Việt Nam</title>
8
- <meta name="description" content="Tìm kiếm thông tin phường/xã nhanh và chính xác tại Việt Nam. Lọc theo tỉnh/thành phố và từ khóa tên phường xã. Hiển thị 3321 kết quả." />
9
- <meta name="keywords" content="tìm kiếm phường xã, danh sách phường xã, tra cứu địa danh, Tất cả tỉnh Việt Nam" />
10
- <link rel="canonical" href="https://huggingface.co/spaces/CVNSS/sapnhap3321" />
11
-
12
- <!-- Open Graph -->
13
- <meta property="og:title" content="Tra cứu, tìm kiếm phường/xã cũ mới sau sáp nhập" />
14
- <meta property="og:description" content="Tra cứu phường/xã trên toàn quốc theo tỉnh và từ khóa nhanh chóng." />
15
- <meta property="og:type" content="website" />
16
- <meta property="og:url" content="https://huggingface.co/spaces/CVNSS/sapnhap3321" />
17
- <meta property="og:image" content="https://huggingface.co/spaces/CVNSS/sapnhap3321/thumbnail.png" />
18
- <meta property="og:locale" content="vi_VN" />
19
-
20
- <!-- Twitter Card -->
21
- <meta name="twitter:card" content="summary_large_image" />
22
- <meta name="twitter:title" content="Tìm kiếm phường/xã Việt Nam" />
23
- <meta name="twitter:description" content="Tra cứu phường/xã trên toàn quốc theo tỉnh và từ khóa nhanh chóng." />
24
- <meta name="twitter:image" content="https://huggingface.co/spaces/CVNSS/sapnhap3321/thumbnail.png" />
25
-
26
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
27
  <style>
28
- body { padding-top: 2rem; background-color: #f8f9fa; }
29
- .search-card { border-radius: 1rem; box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.1); }
30
- .list-group-item { border: none; border-radius: 0.5rem; margin-bottom: 0.5rem; transition: background .3s; }
31
- .list-group-item:hover { background-color: #e9ecef; }
32
- .merge-list small { color: #6c757d; }
33
- .footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #dee2e6; font-size: 0.85rem; color: #6c757d; }
34
- .footer a { color: #0d6efd; text-decoration: none; }
35
- .footer a:hover { text-decoration: underline; }
36
- @media (max-width: 575.98px) {
37
- .card-title { font-size: 1.4rem; }
38
- .footer { font-size: 0.8rem; }
39
- .list-group-item h6 { font-size: 0.95rem; }
40
- .pagination .page-item { flex: 1 1 auto; }
41
- .pagination .page-link { text-align: center; }
42
- }
43
  </style>
44
  </head>
45
  <body>
46
- <div class="container-sm px-3 px-md-5 d-flex flex-column min-vh-100">
47
  <div class="card search-card mb-4">
48
  <div class="card-body">
49
- <h1 class="card-title text-center mb-4">Tra cứu phường/xã sau sáp nhập</h1>
50
- <form id="searchForm" class="mb-0">
51
- <div class="row g-2">
52
- <div class="col-md-3 col-12">
53
- <select id="province" class="form-select">
54
- <option value="all" selected>Tất cả tỉnh</option>
55
- </select>
56
- </div>
57
- <div class="col-md-7 col-12">
58
- <input id="q" type="text" class="form-control" placeholder="Nhập tên phường/xã mới hoặc cũ...(có thể để trống)" autofocus>
59
- </div>
60
- <div class="col-md-2 col-12">
61
- <button class="btn btn-primary w-100">Tìm kiếm</button>
62
- </div>
63
  </div>
64
- </form>
 
65
  </div>
66
  </div>
67
-
68
- <div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-1 mb-2">
69
- <h5 class="mb-0 text-secondary">
70
- <span id="listTitle">Danh sách phường/xã</span>
71
- </h5>
72
- <small class="text-muted" id="counterInfo"></small>
73
- </div>
74
- <ul class="list-group mt-1 mb-3" id="listGroup"></ul>
75
- <nav aria-label="Page navigation">
76
- <ul class="pagination justify-content-center flex-wrap flex-md-nowrap" id="pagination"></ul>
77
- </nav>
78
- <div class="footer text-center mt-auto mb-1">
79
- <span>Phát triển bởi <a href="https://www.facebook.com/quyphamdev/" target="_blank">Phạm Vương Quý</a> | Dữ liệu & UX bởi CVNSS/ChatGPT 2025</span>
80
  </div>
81
  </div>
82
- <script>
83
- // === CONFIG ===
84
- const DATA_URL = "wards_merge_2025.json"; // Đảm bảo upload file này!
85
- // ==============
86
- let DATA = [], provinces = [], curPage = 1, perPage = 8, q = "", province = "all";
87
- let lastQ = "", lastProvince = "all";
 
 
 
 
88
 
89
- function uniq(a) { return [...new Set(a)]; }
90
- function normalize(str) {
91
- return (str||"").normalize("NFC").toLowerCase().replace(/[\u0300-\u036f]/g,"");
92
- }
93
 
94
- function renderProvinces() {
95
- let sel = document.getElementById("province");
96
- provinces = uniq(DATA.map(d=>`${d.province_id}|${d.province}`));
97
- provinces.sort((a,b) => a.split('|')[1].localeCompare(b.split('|')[1]));
98
- provinces.forEach(s=>{
99
- let [id, name] = s.split("|");
100
- sel.innerHTML += `<option value="${id}">${name}</option>`;
101
- });
102
- }
103
 
104
- function filterData() {
105
- let qNorm = normalize(q);
106
- let filtered = DATA.filter(d => {
107
- let byProvince = (province==="all" || d.province_id===province);
108
- if (!qNorm) return byProvince;
109
- // Tìm trong phường xã mới, tỉnh, merged_from (tên cũ/quận/huyện cũ)
110
- let haystack = [
111
- d.ward_new, d.province,
112
- ...(Array.isArray(d.merged_from)?d.merged_from: [d.merged_from])
113
- ].join(" ").toLowerCase();
114
- return byProvince && haystack.normalize("NFC").includes(qNorm);
115
- });
116
- return filtered;
117
- }
118
 
119
- function renderList() {
120
- let filtered = filterData();
121
- let total = filtered.length;
122
- let totalPage = Math.max(1, Math.ceil(total/perPage));
123
- if (curPage > totalPage) curPage = totalPage;
124
- let start = (curPage-1)*perPage, end = start+perPage;
125
- let showing = filtered.slice(start, end);
 
 
 
 
 
 
 
 
 
 
126
 
127
- let group = document.getElementById("listGroup");
128
- group.innerHTML = showing.map(d=>{
129
- let merged = (Array.isArray(d.merged_from) ? d.merged_from : [d.merged_from]).filter(Boolean).join(", ");
130
- return `
131
- <li class="list-group-item">
132
- <div class="d-flex justify-content-between align-items-start">
133
- <div>
134
- <h6 class="mb-1">${d.ward_new||""}</h6>
135
- <small class="text-muted">${d.province||""}</small>
136
- <div class="merge-list mt-1"><small>Sáp nhập từ: ${merged||"<i>Không rõ</i>"}</small></div>
137
- </div>
138
- </div>
139
- </li>`;
140
- }).join("");
141
-
142
- // Pagination
143
- let pagi = document.getElementById("pagination");
144
- pagi.innerHTML = "";
145
- if (totalPage > 1) {
146
- let html = "";
147
- html += `<li class="page-item${curPage==1?' disabled':''}"><a class="page-link" href="#" data-page="1">« Đầu</a></li>`;
148
- html += `<li class="page-item${curPage==1?' disabled':''}"><a class="page-link" href="#" data-page="${curPage-1}">‹ Trước</a></li>`;
149
- let pageNums = [];
150
- for (let i=Math.max(1,curPage-2); i<=Math.min(totalPage,curPage+2); ++i) pageNums.push(i);
151
- if (pageNums[0] > 1) html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
152
- pageNums.forEach(i=> html += `<li class="page-item${i==curPage?' active':''}"><a class="page-link" href="#" data-page="${i}">${i}</a></li>`);
153
- if (pageNums[pageNums.length-1] < totalPage) html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
154
- html += `<li class="page-item${curPage==totalPage?' disabled':''}"><a class="page-link" href="#" data-page="${curPage+1}">Tiếp ›</a></li>`;
155
- html += `<li class="page-item${curPage==totalPage?' disabled':''}"><a class="page-link" href="#" data-page="${totalPage}">Cuối »</a></li>`;
156
- pagi.innerHTML = html;
157
- }
158
- // Info
159
- let info = document.getElementById("counterInfo");
160
- info.innerHTML = `Đang hiển thị <strong>${start+1}–${Math.min(end,total)}</strong> trong tổng số <strong>${total}</strong>`;
161
 
162
- // Update title
163
- document.getElementById("listTitle").innerHTML =
164
- `Danh sách phường/xã (Trang ${curPage} / ${Math.ceil(total/perPage)})`;
165
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
- // Event
168
- document.addEventListener("DOMContentLoaded",()=>{
169
- fetch(DATA_URL).then(r=>r.json()).then(data=>{
170
- DATA = data;
171
- renderProvinces();
172
- renderList();
173
- });
174
- document.getElementById("searchForm").onsubmit = function(e) {
175
- e.preventDefault();
176
- q = document.getElementById("q").value.trim();
177
- province = document.getElementById("province").value;
178
- curPage = 1;
179
- renderList();
180
- };
181
- document.getElementById("pagination").onclick = function(e) {
182
- let t = e.target;
183
- if (t.tagName === "A" && t.dataset.page) {
184
- e.preventDefault();
185
- let newPage = +t.dataset.page;
186
- if (newPage > 0) { curPage = newPage; renderList(); }
187
- }
188
- };
189
- document.getElementById("province").onchange = function() {
190
- province = this.value;
191
- curPage = 1;
192
- renderList();
193
- };
194
- document.getElementById("q").oninput = function() {
195
- if (this.value.length > 2 || this.value.length === 0) {
196
- q = this.value.trim();
197
- curPage = 1;
198
- renderList();
199
  }
200
- }
201
- });
202
- </script>
 
 
 
 
203
  </body>
204
  </html>
 
2
  <html lang="vi">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <title>Tra cứu Xã/Phường cũ mới sau sáp nhập (2025)</title>
6
+ <meta name="viewport" content="width=device-width,initial-scale=1">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
  <style>
9
+ body { background: #f8f9fa; }
10
+ .search-card { border-radius: 1rem; box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.1);}
11
+ .result-card { border-radius: 0.7rem; box-shadow: 0 0.3rem 0.6rem rgba(0,0,0,0.07);}
12
+ .list-group-item { border: none; border-radius: 0.5rem; margin-bottom: 0.4rem; }
13
+ .footer { margin-top: 2rem; font-size: 0.9rem; color: #888;}
 
 
 
 
 
 
 
 
 
 
14
  </style>
15
  </head>
16
  <body>
17
+ <div class="container py-4">
18
  <div class="card search-card mb-4">
19
  <div class="card-body">
20
+ <h2 class="mb-3 text-center">Tra cứu xã/phường cũ mới sau sáp nhập 2025</h2>
21
+ <div class="row g-3">
22
+ <div class="col-md-6">
23
+ <label class="form-label">Tìm theo <b>Xã/Phường mới</b> hoặc <b>Tỉnh/Thành</b>:</label>
24
+ <input type="text" id="inputNew" class="form-control" placeholder="Nhập tên xã/phường mới hoặc tỉnh">
25
+ </div>
26
+ <div class="col-md-6">
27
+ <label class="form-label">Tìm theo <b>Xã/Phường trước sáp nhập</b>:</label>
28
+ <input type="text" id="inputOld" class="form-control" placeholder="Nhập tên xã/phường cũ (có thể một phần)">
 
 
 
 
 
29
  </div>
30
+ </div>
31
+ <small class="text-muted mt-2 d-block">Có thể nhập một phần tên (không cần phân biệt hoa/thường, dấu/không dấu).</small>
32
  </div>
33
  </div>
34
+ <div id="result" class="mt-3"></div>
35
+ <div class="footer text-center mt-5">
36
+ &copy; 2025 | Tra cứu xã/phường Việt Nam sau sáp nhập.<br>
37
+ Code bởi ChatGPT và bạn Long Ngo. Dữ liệu đóng góp cộng đồng.
 
 
 
 
 
 
 
 
 
38
  </div>
39
  </div>
40
+ <script>
41
+ // Dán dữ liệu JSON trực tiếp vào đây (hoặc fetch từ file, nếu host cho phép)
42
+ const data =
43
+ // ----- BẮT ĐẦU CHÉP JSON Ở ĐÂY -----
44
+ [
45
+ {"province":"An Giang","ward_new":"An Biên","ward_old":"Thị trấn Thứ Ba, Đông Yên, Xã Hưng Yên"},
46
+ {"province":"An Giang","ward_new":"An Châu","ward_old":"Thị trấn An Châu, Xã Hòa Bình Thạnh, Xã Vĩnh Thành"},
47
+ // ... CHÉP CẢ FILE JSON Ở ĐÂY (do CORS, không fetch được từ HuggingFace Static Assets)
48
+ ];
49
+ // ----- KẾT THÚC CHÉP JSON Ở ĐÂY -----
50
 
51
+ function removeAccent(str) {
52
+ return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
53
+ }
 
54
 
55
+ function searchData() {
56
+ const valNew = removeAccent(document.getElementById('inputNew').value.trim());
57
+ const valOld = removeAccent(document.getElementById('inputOld').value.trim());
58
+ let html = "";
 
 
 
 
 
59
 
60
+ // Nếu cả 2 trường đều rỗng
61
+ if (!valNew && !valOld) {
62
+ html = '<div class="alert alert-info">Nhập tên xã/phường mới hoặc cũ để tra cứu.</div>';
63
+ document.getElementById('result').innerHTML = html;
64
+ return;
65
+ }
 
 
 
 
 
 
 
 
66
 
67
+ // Nếu chỉ điền trường mới hoặc tỉnh
68
+ if (valNew) {
69
+ const result = data.filter(row =>
70
+ removeAccent(row.ward_new).includes(valNew) ||
71
+ removeAccent(row.province).includes(valNew)
72
+ );
73
+ html = `<div class="card result-card p-3 mb-3"><b>Kết quả tìm theo xã/phường mới hoặc tỉnh:</b> (${result.length} kết quả)</div>`;
74
+ if (result.length === 0) html += `<div class="alert alert-warning">Không tìm thấy kết quả.</div>`;
75
+ result.forEach(row => {
76
+ html += `<div class="list-group mb-1">
77
+ <div class="list-group-item">
78
+ <b>${row.ward_new}</b> <span class="badge bg-secondary">${row.province}</span><br>
79
+ <small class="text-muted">Sáp nhập từ: ${row.ward_old}</small>
80
+ </div>
81
+ </div>`;
82
+ });
83
+ }
84
 
85
+ // Nếu chỉ điền trường cũ
86
+ if (valOld && !valNew) {
87
+ const result = data.filter(row =>
88
+ removeAccent(row.ward_old).includes(valOld)
89
+ );
90
+ html = `<div class="card result-card p-3 mb-3"><b>Kết quả tìm theo xã/phường cũ:</b> (${result.length} kết quả)</div>`;
91
+ if (result.length === 0) html += `<div class="alert alert-warning">Không tìm thấy kết quả.</div>`;
92
+ result.forEach(row => {
93
+ html += `<div class="list-group mb-1">
94
+ <div class="list-group-item">
95
+ <b>${row.ward_new}</b> <span class="badge bg-secondary">${row.province}</span><br>
96
+ <small class="text-muted">Sáp nhập từ: ${row.ward_old}</small>
97
+ </div>
98
+ </div>`;
99
+ });
100
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
+ // Nếu điền cả hai trường: tìm đồng thời cả 2 chiều (AND logic)
103
+ if (valNew && valOld) {
104
+ const result = data.filter(row =>
105
+ (removeAccent(row.ward_new).includes(valNew) || removeAccent(row.province).includes(valNew))
106
+ && removeAccent(row.ward_old).includes(valOld)
107
+ );
108
+ html = `<div class="card result-card p-3 mb-3"><b>Kết quả lọc nâng cao:</b> (${result.length} kết quả)</div>`;
109
+ if (result.length === 0) html += `<div class="alert alert-warning">Không tìm thấy kết quả.</div>`;
110
+ result.forEach(row => {
111
+ html += `<div class="list-group mb-1">
112
+ <div class="list-group-item">
113
+ <b>${row.ward_new}</b> <span class="badge bg-secondary">${row.province}</span><br>
114
+ <small class="text-muted">Sáp nhập từ: ${row.ward_old}</small>
115
+ </div>
116
+ </div>`;
117
+ });
118
+ }
119
 
120
+ document.getElementById('result').innerHTML = html;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  }
122
+
123
+ document.getElementById('inputNew').addEventListener('input', searchData);
124
+ document.getElementById('inputOld').addEventListener('input', searchData);
125
+
126
+ // Nếu bạn muốn tải dữ liệu từ file .json trên HuggingFace Spaces, hãy dùng fetch và nhớ đặt file vào public/static (nếu HF cho phép CORS)
127
+ // fetch('vn_wards_2025.json').then(r=>r.json()).then(d=>data = d)
128
+ </script>
129
  </body>
130
  </html>