mirror of
https://github.com/tiennm99/thptqg.git
synced 2026-08-14 03:22:24 +00:00
feat: add diacritics-insensitive name search
Add ho_ten_ascii column with normalized names (no diacritics, lowercase) so users can search "nguyen van a" to find "NGUYỄN VĂN A". - ASCII input searches against ho_ten_ascii column - Vietnamese input searches both ho_ten and ho_ten_ascii - Indexed for fast lookups
This commit is contained in:
@@ -26,6 +26,16 @@ const SCORE_PATTERNS = {
|
||||
|
||||
const ALL_SCORE_FIELDS = Object.keys(SCORE_PATTERNS);
|
||||
|
||||
// Strip Vietnamese diacritics: "NGUYỄN BŨU LỘC" → "nguyen buu loc"
|
||||
function toAscii(str) {
|
||||
return str
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.replace(/đ/g, "d")
|
||||
.replace(/Đ/g, "D")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
// Parse score text "Toán: 3.75 Ngữ văn: 5.00 ..." into { toan: 3.75, ... }
|
||||
function parseScoreString(diemThi) {
|
||||
const scores = {};
|
||||
@@ -85,6 +95,7 @@ function processSeparateScoresRow(row) {
|
||||
return {
|
||||
so_bao_danh: sbd,
|
||||
ho_ten: hoTen,
|
||||
ho_ten_ascii: toAscii(hoTen),
|
||||
ngay_sinh: null,
|
||||
ten_cum_thi: null,
|
||||
gioi_tinh: null,
|
||||
@@ -126,6 +137,7 @@ function processMappedRow(row, map) {
|
||||
return {
|
||||
so_bao_danh: sbd,
|
||||
ho_ten: hoTen,
|
||||
ho_ten_ascii: toAscii(hoTen),
|
||||
ngay_sinh: ngaySinh || null,
|
||||
ten_cum_thi: tenCumThi || null,
|
||||
gioi_tinh: gioiTinh || null,
|
||||
@@ -148,6 +160,7 @@ function main() {
|
||||
CREATE TABLE student (
|
||||
so_bao_danh TEXT PRIMARY KEY,
|
||||
ho_ten TEXT NOT NULL,
|
||||
ho_ten_ascii TEXT NOT NULL,
|
||||
ngay_sinh TEXT,
|
||||
ten_cum_thi TEXT,
|
||||
gioi_tinh TEXT,
|
||||
@@ -165,16 +178,17 @@ function main() {
|
||||
tieng_trung REAL
|
||||
);
|
||||
CREATE INDEX idx_ho_ten ON student(ho_ten);
|
||||
CREATE INDEX idx_ho_ten_ascii ON student(ho_ten_ascii);
|
||||
CREATE INDEX idx_ten_cum_thi ON student(ten_cum_thi);
|
||||
`);
|
||||
|
||||
const insert = db.prepare(`
|
||||
INSERT OR REPLACE INTO student
|
||||
(so_bao_danh, ho_ten, ngay_sinh, ten_cum_thi, gioi_tinh,
|
||||
(so_bao_danh, ho_ten, ho_ten_ascii, ngay_sinh, ten_cum_thi, gioi_tinh,
|
||||
toan, ngu_van, vat_ly, hoa_hoc, sinh_hoc, lich_su, dia_ly,
|
||||
tieng_anh, tieng_phap, tieng_duc, tieng_nhat, tieng_trung)
|
||||
VALUES
|
||||
(@so_bao_danh, @ho_ten, @ngay_sinh, @ten_cum_thi, @gioi_tinh,
|
||||
(@so_bao_danh, @ho_ten, @ho_ten_ascii, @ngay_sinh, @ten_cum_thi, @gioi_tinh,
|
||||
@toan, @ngu_van, @vat_ly, @hoa_hoc, @sinh_hoc, @lich_su, @dia_ly,
|
||||
@tieng_anh, @tieng_phap, @tieng_duc, @tieng_nhat, @tieng_trung)
|
||||
`);
|
||||
|
||||
+31
-3
@@ -8,6 +8,20 @@ import "./App.css";
|
||||
const DB_URL = import.meta.env.BASE_URL + "thptqg2016.db.gz";
|
||||
const MAX_RESULTS = 100;
|
||||
|
||||
// Strip Vietnamese diacritics for search: "nguyễn bữu lộc" → "nguyen buu loc"
|
||||
function toAscii(str) {
|
||||
return str
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.replace(/đ/gi, "d")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
// Check if string contains only ASCII (no Vietnamese diacritics)
|
||||
function isAsciiOnly(str) {
|
||||
return /^[\x00-\x7F]*$/.test(str);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { db, loading, error, progress } = useSqlite(DB_URL);
|
||||
const [results, setResults] = useState(null);
|
||||
@@ -28,11 +42,25 @@ function App() {
|
||||
"SELECT * FROM student WHERE so_bao_danh = $q LIMIT $limit",
|
||||
);
|
||||
stmt.bind({ $q: query.toUpperCase(), $limit: MAX_RESULTS });
|
||||
} else {
|
||||
} else if (isAsciiOnly(query)) {
|
||||
// ASCII input: search against normalized column (diacritics-insensitive)
|
||||
stmt = db.prepare(
|
||||
"SELECT * FROM student WHERE ho_ten LIKE $q LIMIT $limit",
|
||||
"SELECT * FROM student WHERE ho_ten_ascii LIKE $q LIMIT $limit",
|
||||
);
|
||||
stmt.bind({ $q: `%${query}%`, $limit: MAX_RESULTS });
|
||||
stmt.bind({ $q: `%${toAscii(query)}%`, $limit: MAX_RESULTS });
|
||||
} else {
|
||||
// Vietnamese input: search both original and normalized
|
||||
const normalized = toAscii(query);
|
||||
stmt = db.prepare(
|
||||
`SELECT * FROM student
|
||||
WHERE ho_ten LIKE $q OR ho_ten_ascii LIKE $qn
|
||||
LIMIT $limit`,
|
||||
);
|
||||
stmt.bind({
|
||||
$q: `%${query}%`,
|
||||
$qn: `%${normalized}%`,
|
||||
$limit: MAX_RESULTS,
|
||||
});
|
||||
}
|
||||
|
||||
const rows = [];
|
||||
|
||||
@@ -15,7 +15,7 @@ export function SearchForm({ onSearch, disabled }) {
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Nhập số báo danh hoặc họ tên..."
|
||||
placeholder="Nhập SBD hoặc họ tên (VD: nguyen van a)..."
|
||||
disabled={disabled}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user