feat: 지역별 고등학교 나이스 연동, sitemap 생성 및 파비콘 설정 완료

This commit is contained in:
2026-10-15 12:13:23 +09:00
parent 0d230f56da
commit a827b8e127
91 changed files with 80110 additions and 50197 deletions
+170 -51
View File
@@ -8,6 +8,27 @@
</p>
</div>
<!-- 나이스(NEIS) 연동 관내 실제 고등학교 칩 영역 -->
<div v-if="nearbyHighSchools.length > 0" class="neis-schools-box">
<div class="neis-header">
<span class="neis-title">
<i class="fa-solid fa-school-flag text-blue-600 mr-1.5"></i>
<strong>{{ regionDong }} 인근 주요 고등학교 내신 대비 현황</strong>
</span>
<span class="neis-source">NEIS 학교정보 연동</span>
</div>
<div class="school-tags-wrap">
<div
v-for="school in nearbyHighSchools"
:key="school.code"
class="school-tag-pill"
>
<span class="school-tag-name">{{ school.name }}</span>
<span class="school-tag-type">{{ school.type }}</span>
</div>
</div>
</div>
<!-- 고등학생 타겟인 경우 -->
<div v-if="isHigh" class="schools-grid">
<div class="school-card">
@@ -94,6 +115,7 @@
<script setup>
import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json'
const props = defineProps({
regionDong: {
@@ -118,6 +140,38 @@ const targetSchoolType = computed(() => {
if (isMiddle.value) return '중학교'
return '초·중학교'
})
// 동네 기준 또는 구 기준 인근 고등학교 목록
const nearbyHighSchools = computed(() => {
const dong = props.regionDong || ''
const cleanDong = dong.trim()
// 1. 해당 동네에 직접 위치한 고등학교
if (schoolsIndex.byDong && schoolsIndex.byDong[cleanDong]) {
return schoolsIndex.byDong[cleanDong]
}
// 2. 부분 일치 검색 (예: '개포' -> '개포동')
for (const [d, list] of Object.entries(schoolsIndex.byDong || {})) {
if (d.includes(cleanDong) || cleanDong.includes(d)) {
return list
}
}
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) {
return list.slice(0, 6)
}
}
// 4. 기본 샘플 학교
const allDongs = Object.keys(schoolsIndex.byDong || {})
if (allDongs.length > 0) {
return (schoolsIndex.byDong[allDongs[0]] || []).slice(0, 4)
}
return []
})
</script>
<style scoped>
@@ -134,103 +188,168 @@ const targetSchoolType = computed(() => {
.section-badge {
display: inline-block;
font-size: clamp(0.78rem, 2vw, 0.88rem);
font-size: 13px;
font-weight: 700;
color: var(--primary-color, #1e3a8a);
background: var(--badge-bg, rgba(30, 58, 138, 0.08));
padding: 5px 12px;
color: #2563eb;
background-color: #eff6ff;
padding: 4px 14px;
border-radius: 9999px;
margin-bottom: 8px;
margin-bottom: 10px;
border: 1px solid #bfdbfe;
}
.section-title {
font-size: clamp(1.3rem, 3.8vw, 1.95rem);
font-size: 26px;
font-weight: 800;
color: var(--text-color, #1e293b);
letter-spacing: -0.6px;
word-break: keep-all;
line-height: 1.35;
color: #0f172a;
letter-spacing: -0.02em;
margin-bottom: 10px;
}
.section-desc {
font-size: clamp(0.85rem, 2.2vw, 0.98rem);
font-size: 15px;
color: #64748b;
margin-top: 8px;
word-break: keep-all;
max-width: 720px;
margin-left: auto;
margin-right: auto;
margin: 0 auto;
line-height: 1.6;
}
/* 나이스 고등학교 칩 영역 */
.neis-schools-box {
background: #f8fafc;
border: 1.5px solid #e2e8f0;
border-radius: 18px;
padding: 18px 22px;
margin-bottom: 28px;
}
.neis-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.neis-title {
font-size: 14.5px;
color: #1e293b;
}
.neis-source {
font-size: 11.5px;
color: #64748b;
background: #ffffff;
padding: 3px 8px;
border-radius: 6px;
border: 1px solid #e2e8f0;
}
.school-tags-wrap {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.school-tag-pill {
display: inline-flex;
align-items: center;
gap: 6px;
background: #ffffff;
border: 1.5px solid #cbd5e1;
padding: 7px 14px;
border-radius: 999px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
}
.school-tag-pill:hover {
border-color: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.12);
}
.school-tag-name {
font-size: 13.5px;
font-weight: 800;
color: #0f172a;
}
.school-tag-type {
font-size: 11px;
font-weight: 700;
color: #2563eb;
background: #eff6ff;
padding: 2px 6px;
border-radius: 4px;
}
.schools-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18px;
gap: 20px;
}
.school-card {
background: var(--bg-card, #ffffff);
border: 1px solid var(--border-color, #e2e8f0);
border-radius: var(--card-radius, 14px);
padding: clamp(18px, 3.5vw, 26px);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.04);
position: relative;
background-color: #ffffff;
border: 1.5px solid #e2e8f0;
border-radius: 20px;
padding: 26px 24px;
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.04);
}
.school-type-badge {
display: inline-block;
font-size: 0.75rem;
font-size: 12px;
font-weight: 700;
padding: 3px 8px;
border-radius: 4px;
margin-bottom: 10px;
}
.school-type-badge.middle {
background: #e0f2fe;
color: #0369a1;
padding: 4px 10px;
border-radius: 6px;
margin-bottom: 12px;
}
.school-type-badge.high {
background: #fef3c7;
color: #b45309;
background-color: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}
.school-type-badge.middle {
background-color: #ecfdf5;
color: #059669;
border: 1px solid #a7f3d0;
}
.school-type-badge.elem {
background: #f1f5f9;
color: #475569;
background-color: #fffbeb;
color: #d97706;
border: 1px solid #fde68a;
}
.school-name {
font-size: clamp(1.05rem, 2.6vw, 1.25rem);
font-size: 18px;
font-weight: 800;
color: var(--text-color, #0f172a);
margin-bottom: 10px;
letter-spacing: -0.4px;
color: #0f172a;
margin-bottom: 12px;
}
.school-focus {
font-size: clamp(0.82rem, 2vw, 0.92rem);
line-height: 1.6;
font-size: 14px;
color: #334155;
margin-bottom: 14px;
padding-bottom: 12px;
border-bottom: 1px dashed var(--border-color, #e2e8f0);
word-break: keep-all;
line-height: 1.6;
margin-bottom: 16px;
}
.school-checklist {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
padding: 0;
margin: 0;
border-top: 1px dashed #e2e8f0;
padding-top: 14px;
}
.school-checklist li {
font-size: clamp(0.8rem, 2vw, 0.9rem);
font-size: 13.5px;
color: #475569;
line-height: 1.5;
word-break: keep-all;
line-height: 1.7;
}
@media (max-width: 640px) {