ui 개편 완료
This commit is contained in:
@@ -104,6 +104,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { getShortSido } from '../utils/dataHelper'
|
||||
|
||||
const props = defineProps({
|
||||
region: {
|
||||
@@ -129,7 +130,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const shortSidoName = computed(() => {
|
||||
return (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim()
|
||||
return getShortSido(props.region?.sido || '')
|
||||
})
|
||||
|
||||
const gradeShortName = computed(() => {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 같은 동의 다른 학년 링크 -->
|
||||
<!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
|
||||
<div class="hub-card">
|
||||
<div class="card-head">
|
||||
<i class="fa-solid fa-graduation-cap card-icon"></i>
|
||||
@@ -45,10 +45,16 @@
|
||||
>
|
||||
{{ regionDong }} {{ g }} {{ subjectName }}
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
:to="`/${encodeURIComponent(regionDong)}`"
|
||||
class="chip-link"
|
||||
>
|
||||
{{ regionDong }} 초·중·고 과외 전체
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 관내 인접 동 링크 -->
|
||||
<!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
|
||||
<div class="hub-card">
|
||||
<div class="card-head">
|
||||
<i class="fa-solid fa-location-dot card-icon"></i>
|
||||
@@ -66,6 +72,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
|
||||
<details class="all-regions-directory">
|
||||
<summary class="directory-summary">
|
||||
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
|
||||
<span class="dir-arrow">▼</span>
|
||||
</summary>
|
||||
<div class="directory-body">
|
||||
<div class="link-chips">
|
||||
<NuxtLink
|
||||
v-for="d in allUniqueDongs"
|
||||
:key="`sub-${d}`"
|
||||
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
|
||||
class="chip-link dir-link"
|
||||
:class="{ active: d === regionDong }"
|
||||
>
|
||||
{{ d }} {{ grade }} {{ subjectName }}과외
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
v-for="d in allUniqueDongs"
|
||||
:key="`main-${d}`"
|
||||
:to="`/${encodeURIComponent(d)}`"
|
||||
class="chip-link dir-link"
|
||||
>
|
||||
{{ d }} 과외
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
@@ -85,7 +120,7 @@ const props = defineProps({
|
||||
},
|
||||
subjectName: {
|
||||
type: String,
|
||||
default: '영어'
|
||||
default: '수학'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -99,32 +134,30 @@ const subjects = [
|
||||
|
||||
const grades = ['초등학생', '중학생', '고등학생']
|
||||
|
||||
// 동일 시군구 내 인근 동 추출 (최대 6개)
|
||||
// 전체 중복 제거된 동 목록
|
||||
const allUniqueDongs = computed(() => {
|
||||
const allList = regionsData.list || []
|
||||
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
|
||||
})
|
||||
|
||||
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
|
||||
const nearbyDongs = computed(() => {
|
||||
const curDong = props.regionDong
|
||||
const allList = (regionsData.list || [])
|
||||
|
||||
// 현재 동의 시군구 찾기
|
||||
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
|
||||
const sigungu = curRegion ? curRegion.sigungu : ''
|
||||
|
||||
let candidates = []
|
||||
if (sigungu) {
|
||||
candidates = allList
|
||||
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
|
||||
.map(r => r.dong)
|
||||
}
|
||||
|
||||
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
|
||||
if (candidates.length < 12) {
|
||||
const others = allList
|
||||
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
|
||||
.map(r => r.dong)
|
||||
candidates = [...candidates, ...others]
|
||||
}
|
||||
const uniqueDongs = allUniqueDongs.value
|
||||
const total = uniqueDongs.length
|
||||
if (total === 0) return []
|
||||
|
||||
// 중복 제거 후 12개 반환
|
||||
return Array.from(new Set(candidates)).slice(0, 12)
|
||||
const curIdx = uniqueDongs.indexOf(curDong)
|
||||
const startIdx = curIdx >= 0 ? curIdx : 0
|
||||
const result = []
|
||||
|
||||
for (let offset = 1; offset < total && result.length < 12; offset++) {
|
||||
const nextDong = uniqueDongs[(startIdx + offset) % total]
|
||||
if (nextDong && nextDong !== curDong) {
|
||||
result.push(nextDong)
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
|
||||
border-color: #1e40af;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.all-regions-directory {
|
||||
margin-top: 20px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.directory-summary {
|
||||
padding: 14px 20px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.directory-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dir-arrow {
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
details[open].all-regions-directory .dir-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.directory-body {
|
||||
padding: 0 20px 20px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.dir-link {
|
||||
font-size: 0.76rem;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import schoolsIndex from '../data/schools_index.json'
|
||||
import { getRegionByDong } from '../utils/dataHelper'
|
||||
|
||||
const props = defineProps({
|
||||
regionDong: {
|
||||
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개
|
||||
// 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
|
||||
const regInfo = getRegionByDong(cleanDong)
|
||||
const targetGu = regInfo?.sigungu || ''
|
||||
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
|
||||
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
|
||||
const guList = schoolsIndex.bySigungu[targetGu]
|
||||
if (guList.length <= 6) return guList
|
||||
const startIdx = dongSeed % Math.max(1, guList.length - 5)
|
||||
return guList.slice(startIdx, startIdx + 6)
|
||||
}
|
||||
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
|
||||
if (cleanDong.includes(gu) || gu.includes(cleanDong)) {
|
||||
return list.slice(0, 6)
|
||||
if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
|
||||
return Array.isArray(list) ? list.slice(0, 6) : []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-tag-text">POINT 01</div>
|
||||
<div class="metric-num">98.7%</div>
|
||||
<div class="metric-num">{{ satisfactionRate }}%</div>
|
||||
<div class="metric-label">{{ regionDong }} 학부모 만족도</div>
|
||||
<div class="metric-sub">재수강 의사 압도적 1위</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-tag-text">POINT 02</div>
|
||||
<div class="metric-num">+26.8점</div>
|
||||
<div class="metric-num">+{{ scoreJump }}점</div>
|
||||
<div class="metric-label">평균 내신 성적 상승폭</div>
|
||||
<div class="metric-sub">수강 3개월 이내 실측</div>
|
||||
</div>
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-tag-text">POINT 04</div>
|
||||
<div class="metric-num">12,500+</div>
|
||||
<div class="metric-num">{{ matchCount }}+</div>
|
||||
<div class="metric-label">누적 1:1 맞춤 배정</div>
|
||||
<div class="metric-sub">전국 학군별 데이터 기반</div>
|
||||
</div>
|
||||
@@ -30,12 +30,22 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
regionDong: {
|
||||
type: String,
|
||||
default: '해당 지역'
|
||||
}
|
||||
})
|
||||
|
||||
const dongSeed = computed(() => {
|
||||
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
|
||||
})
|
||||
|
||||
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
|
||||
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
|
||||
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { getRegionByDong } from '../utils/dataHelper'
|
||||
|
||||
const props = defineProps({
|
||||
regionDong: {
|
||||
@@ -42,60 +43,98 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성
|
||||
const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
|
||||
|
||||
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
|
||||
const matchedStories = computed(() => {
|
||||
const isHigh = (props.grade || '').includes('고등')
|
||||
const dong = props.regionDong
|
||||
const sub = props.subjectName
|
||||
const isElem = (props.grade || '').includes('초등')
|
||||
const dong = props.regionDong || '관내'
|
||||
const regInfo = getRegionByDong(dong)
|
||||
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
|
||||
const sub = props.subjectName || '수학'
|
||||
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
|
||||
|
||||
const s1 = surnames[seed % surnames.length]
|
||||
const s2 = surnames[(seed + 5) % surnames.length]
|
||||
const s3 = surnames[(seed + 11) % surnames.length]
|
||||
|
||||
if (isHigh) {
|
||||
// 고등학생 전용 스토리
|
||||
const beforeScore = 52 + (seed % 14)
|
||||
const afterScore = 94 + (seed % 6)
|
||||
const rankNum = 2 + (seed % 5)
|
||||
return [
|
||||
{
|
||||
student: `${dong} 고2 김○○ 학생`,
|
||||
before: `${sub} 내신 4등급 (56점)`,
|
||||
after: `내신 1등급 (96점)`,
|
||||
period: '3개월',
|
||||
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!`
|
||||
student: `${gu}${dong} 고2 ${s1}○○ 학생`,
|
||||
before: `${sub} 내신 4등급 (${beforeScore}점)`,
|
||||
after: `내신 1등급 (${afterScore}점)`,
|
||||
period: `${3 + (seed % 2)}개월`,
|
||||
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
|
||||
},
|
||||
{
|
||||
student: `${dong} 고1 박○○ 학생`,
|
||||
student: `${gu}${dong} 고1 ${s2}○○ 학생`,
|
||||
before: `중간고사 3등급`,
|
||||
after: `기말고사 전교 3등 (1등급)`,
|
||||
period: '4개월',
|
||||
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
|
||||
after: `기말고사 전교 ${rankNum}등 (1등급)`,
|
||||
period: `${4 + (seed % 2)}개월`,
|
||||
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
|
||||
},
|
||||
{
|
||||
student: `${dong} 고3 이○○ 학부모님`,
|
||||
before: `${sub} 수능 모평 3등급`,
|
||||
student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
|
||||
before: `${sub} 모의고사 3등급`,
|
||||
after: `수능 1등급 최저 충족`,
|
||||
period: '6개월',
|
||||
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!`
|
||||
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
|
||||
}
|
||||
]
|
||||
} else if (isElem) {
|
||||
const beforeScore = 70 + (seed % 12)
|
||||
return [
|
||||
{
|
||||
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
|
||||
before: `${sub} 단원평가 ${beforeScore}점`,
|
||||
after: `단원평가 100점 만점`,
|
||||
period: '3개월',
|
||||
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
|
||||
},
|
||||
{
|
||||
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
|
||||
before: `서술형 풀이 기피`,
|
||||
after: `풀이과정 노트 작성 습관화`,
|
||||
period: '4개월',
|
||||
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
|
||||
},
|
||||
{
|
||||
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
|
||||
before: `공부 집중력 부족`,
|
||||
after: `매일 1시간 자기주도 복습`,
|
||||
period: '4개월',
|
||||
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
|
||||
}
|
||||
]
|
||||
} else {
|
||||
// 중학생 전용 스토리
|
||||
const beforeScore = 62 + (seed % 14)
|
||||
const afterScore = 96 + (seed % 5)
|
||||
return [
|
||||
{
|
||||
student: `${dong} 중2 이○○ 학생`,
|
||||
before: `${sub} 68점 (C등급)`,
|
||||
after: `중간·기말 98점 (A등급)`,
|
||||
period: '3개월',
|
||||
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!`
|
||||
student: `${gu}${dong} 중2 ${s1}○○ 학생`,
|
||||
before: `${sub} ${beforeScore}점 (C등급)`,
|
||||
after: `중간·기말 ${afterScore}점 (A등급)`,
|
||||
period: `${3 + (seed % 2)}개월`,
|
||||
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
|
||||
},
|
||||
{
|
||||
student: `${dong} 중3 최○○ 학생`,
|
||||
before: `서술형 감점 다수 (75점)`,
|
||||
student: `${gu}${dong} 중3 ${s2}○○ 학생`,
|
||||
before: `서술형 감점 다수 (74점)`,
|
||||
after: `100점 만점 달성`,
|
||||
period: '4개월',
|
||||
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
|
||||
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
|
||||
},
|
||||
{
|
||||
student: `${dong} 중1 정○○ 학부모님`,
|
||||
student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
|
||||
before: `자유학기 학습 습관 부족`,
|
||||
after: `스스로 매일 2시간 복습`,
|
||||
period: '5개월',
|
||||
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
|
||||
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
|
||||
|
||||
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
|
||||
ssr: true,
|
||||
routeRules: {
|
||||
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
|
||||
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
|
||||
},
|
||||
|
||||
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화
|
||||
nitro: {
|
||||
@@ -49,9 +53,7 @@ export default defineNuxtConfig({
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
|
||||
{ name: 'format-detection', content: 'telephone=no' },
|
||||
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
|
||||
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || 'd40ee8cd61dab827dcbd7080e54b095339c44222' }
|
||||
{ name: 'format-detection', content: 'telephone=no' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex items-center gap-1.5 text-slate-600 font-medium">
|
||||
<NuxtLink to="/" class="hover:text-blue-600 font-semibold transition-colors">홈</NuxtLink>
|
||||
<span class="text-slate-400">›</span>
|
||||
<span>{{ parsed.dong }} 과외</span>
|
||||
<NuxtLink :to="`/${encodeURIComponent(parsed.dong)}`" class="hover:text-blue-600 transition-colors">{{ parsed.dong }} 과외</NuxtLink>
|
||||
<span class="text-slate-400">›</span>
|
||||
<strong class="font-bold text-blue-700">{{ parsed.shortSido }} {{ parsed.gradeShort }} {{ parsed.subjectName }} 과외</strong>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /admin
|
||||
|
||||
User-agent: Yeti
|
||||
Allow: /
|
||||
Disallow: /admin
|
||||
|
||||
Sitemap: https://soohyun.xyz/sitemap.xml
|
||||
Sitemap: https://soohyun.xyz/sitemap-images.xml
|
||||
+109
-18
@@ -1,33 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title><![CDATA[쌤인증]]></title>
|
||||
<title><![CDATA[쌤인증 - 울산 중등 수학 과외 · 울산 초·중·고 1:1 맞춤 과외]]></title>
|
||||
<link>https://soohyun.xyz/</link>
|
||||
<description><![CDATA[울산 초·중·고 1:1 맞춤 과외 및 방문·화상 시범수업 전문 교육 기관]]></description>
|
||||
<description><![CDATA[울산 중등 수학 과외, 울산 중등 과외, 울산 초·중·고 국영수사과 1:1 방문·화상 맞춤 과외 및 무료 시범수업 전문 쌤인증]]></description>
|
||||
<language>ko</language>
|
||||
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
|
||||
<lastBuildDate>Wed, 04 Nov 2026 00:47:43 GMT</lastBuildDate>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<lastBuildDate>Mon, 09 Nov 2026 01:00:00 GMT</lastBuildDate>
|
||||
<atom:link href="https://soohyun.xyz/rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<title><![CDATA[울산 초·중·고 1:1 맞춤 과외 매칭]]></title>
|
||||
<title><![CDATA[울산 중등 수학 과외 · 울산 중등 과외 | 초·중·고 1:1 맞춤 과외 쌤인증]]></title>
|
||||
<link>https://soohyun.xyz/</link>
|
||||
<description><![CDATA[옥동·신정 등 울산 전지역 과외 비용 비교 및 시범수업 신청]]></description>
|
||||
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
|
||||
<description><![CDATA[울산 전 지역 초·중·고 1:1 방문·화상 맞춤 과외 비용 비교 및 무료 시범수업 신청]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[옥동 중학생 수학과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%98%A5%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
|
||||
<description><![CDATA[옥동 학원가 대비 1:1 밀착 코칭]]></description>
|
||||
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%98%A5%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
|
||||
<title><![CDATA[울산 초등 수학 과외 · 달동 초등학생 수학과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EB%8B%AC%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
|
||||
<description><![CDATA[울산 초등 수학 과외 및 달동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EB%8B%AC%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[신정동 고등학생 영어과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%8B%A0%EC%A0%95%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
|
||||
<description><![CDATA[남구 신정동 내신 서술형 집중 관리]]></description>
|
||||
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%8B%A0%EC%A0%95%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
|
||||
<title><![CDATA[울산 중등 영어 과외 · 대현동 중학생 영어과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EB%8C%80%ED%98%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
|
||||
<description><![CDATA[울산 중등 영어 과외 및 대현동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EB%8C%80%ED%98%84%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 고등 국어 과외 · 무거동 고등학생 국어과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EB%AC%B4%EA%B1%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
|
||||
<description><![CDATA[울산 고등 국어 과외 및 무거동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EB%AC%B4%EA%B1%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 초등 과학 과외 · 삼산동 초등학생 과학과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%82%BC%EC%82%B0%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
|
||||
<description><![CDATA[울산 초등 과학 과외 및 삼산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%82%BC%EC%82%B0%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 중등 사회 과외 · 선암동 중학생 사회과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%84%A0%EC%95%94%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
|
||||
<description><![CDATA[울산 중등 사회 과외 및 선암동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%84%A0%EC%95%94%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 고등 수학 과외 · 수암동 고등학생 수학과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%88%98%EC%95%94%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
|
||||
<description><![CDATA[울산 고등 수학 과외 및 수암동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%88%98%EC%95%94%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 초등 영어 과외 · 신정동 초등학생 영어과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%8B%A0%EC%A0%95%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
|
||||
<description><![CDATA[울산 초등 영어 과외 및 신정동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%8B%A0%EC%A0%95%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 중등 국어 과외 · 야음동 중학생 국어과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%95%BC%EC%9D%8C%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
|
||||
<description><![CDATA[울산 중등 국어 과외 및 야음동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%95%BC%EC%9D%8C%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 고등 과학 과외 · 옥동 고등학생 과학과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%98%A5%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
|
||||
<description><![CDATA[울산 고등 과학 과외 및 옥동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%98%A5%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 초등 사회 과외 · 방어동 초등학생 사회과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EB%B0%A9%EC%96%B4%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
|
||||
<description><![CDATA[울산 초등 사회 과외 및 방어동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EB%B0%A9%EC%96%B4%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 중등 수학 과외 · 서부동 중학생 수학과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%84%9C%EB%B6%80%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
|
||||
<description><![CDATA[울산 중등 수학 과외 및 서부동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%84%9C%EB%B6%80%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 고등 영어 과외 · 일산동 고등학생 영어과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%9D%BC%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
|
||||
<description><![CDATA[울산 고등 영어 과외 및 일산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%9D%BC%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 초등 국어 과외 · 전하동 초등학생 국어과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EC%A0%84%ED%95%98%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
|
||||
<description><![CDATA[울산 초등 국어 과외 및 전하동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EC%A0%84%ED%95%98%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 중등 과학 과외 · 화정동 중학생 과학과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%ED%99%94%EC%A0%95%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
|
||||
<description><![CDATA[울산 중등 과학 과외 및 화정동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%ED%99%94%EC%A0%95%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[울산 고등 사회 과외 · 달천동 고등학생 사회과외 1:1 맞춤 수업]]></title>
|
||||
<link>https://soohyun.xyz/%EB%8B%AC%EC%B2%9C%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
|
||||
<description><![CDATA[울산 고등 사회 과외 및 달천동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
|
||||
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
|
||||
<guid>https://soohyun.xyz/%EB%8B%AC%EC%B2%9C%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
</rss>
|
||||
File diff suppressed because it is too large
Load Diff
+738
-784
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
import regionsData from '../../data/regions.json'
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseUrl = config.public.siteDomain || 'https://soohyun.xyz'
|
||||
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
|
||||
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
|
||||
const subjects = ['수학', '영어', '국어', '과학', '사회']
|
||||
const grades = ['초등학생', '중학생', '고등학생']
|
||||
const subjectImages: Record<string, string> = {
|
||||
'수학': `${baseUrl}/img/math-tutoring.png`,
|
||||
'영어': `${baseUrl}/img/english-tutoring.png`,
|
||||
'국어': `${baseUrl}/img/korean-tutoring.png`,
|
||||
'과학': `${baseUrl}/img/science-tutoring.png`,
|
||||
'사회': `${baseUrl}/img/social-studies-tutoring.png`
|
||||
}
|
||||
|
||||
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
|
||||
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
|
||||
|
||||
// 메인
|
||||
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
|
||||
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
|
||||
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
|
||||
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
|
||||
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
|
||||
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
|
||||
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
|
||||
xml += ` </url>\n`
|
||||
|
||||
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
|
||||
uniqueDongs.forEach(dong => {
|
||||
// 1. 동 대표
|
||||
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
|
||||
|
||||
// 2. 동/학년/과목 조합
|
||||
grades.forEach(g => {
|
||||
subjects.forEach(sub => {
|
||||
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
|
||||
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
xml += `</urlset>`
|
||||
|
||||
setHeader(event, 'content-type', 'application/xml; charset=utf-8')
|
||||
return xml
|
||||
})
|
||||
@@ -4,6 +4,7 @@ export default defineEventHandler((event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseUrl = config.public.siteDomain || 'https://soohyun.xyz'
|
||||
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
|
||||
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
|
||||
const subjects = ['수학', '영어', '국어', '과학', '사회']
|
||||
@@ -29,16 +30,16 @@ export default defineEventHandler((event) => {
|
||||
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
|
||||
xml += ` </url>\n`
|
||||
|
||||
// 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
|
||||
list.forEach(r => {
|
||||
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
|
||||
uniqueDongs.forEach(dong => {
|
||||
// 1. 동 대표
|
||||
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
|
||||
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
|
||||
|
||||
// 2. 동/학년/과목 조합
|
||||
grades.forEach(g => {
|
||||
subjects.forEach(sub => {
|
||||
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
|
||||
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(r.dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
|
||||
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -113,30 +113,35 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
|
||||
} else if (typeof slugInput === 'string' && slugInput.trim()) {
|
||||
tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/)
|
||||
}
|
||||
tokens = tokens.map(t => t.trim()).filter(Boolean)
|
||||
tokens = tokens.map(t => t.trim()).filter(t => Boolean(t) && !/^index\.html?$/i.test(t))
|
||||
|
||||
let matchedRegion: RegionInfo | null = null
|
||||
let matchedTokenIdx = -1
|
||||
let detectedGrade = '중학생'
|
||||
let detectedGradeShort = '중등'
|
||||
let detectedSubjectKey = 'english'
|
||||
let detectedSubjectName = '영어'
|
||||
let detectedSubjectKey = 'math'
|
||||
let detectedSubjectName = '수학'
|
||||
let detectedTerm = '과외'
|
||||
|
||||
// 1. 토큰들 중 동 이름 매칭
|
||||
for (const token of tokens) {
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i]
|
||||
const reg = getRegionByDong(token)
|
||||
if (reg) {
|
||||
matchedRegion = reg
|
||||
matchedTokenIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색
|
||||
if (!matchedRegion && tokens.length > 0) {
|
||||
for (const token of tokens) {
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i]
|
||||
const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token)))
|
||||
if (found) {
|
||||
matchedRegion = found
|
||||
matchedTokenIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -152,9 +157,10 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 학년 추출
|
||||
// 3. 학년 추출 (동 이름 토큰을 제외한 나머지 토큰에서만 검색하여 '고등동', '문학동' 등 별칭 충돌 원천 차단)
|
||||
let hasExplicitGrade = false
|
||||
const fullText = tokens.join(' ')
|
||||
const remainingTokens = tokens.filter((_, idx) => idx !== matchedTokenIdx)
|
||||
const fullText = remainingTokens.join(' ')
|
||||
for (const g of GRADES_LIST) {
|
||||
if (g.aliases.some(a => fullText.includes(a))) {
|
||||
detectedGrade = g.name
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ParsedRouteParams } from './dataHelper'
|
||||
import { getShortRegionName } from './dataHelper'
|
||||
import { getShortRegionName, getShortSido } from './dataHelper'
|
||||
|
||||
const SUBJECT_IMAGE_MAP: Record<string, string> = {
|
||||
math: '/img/math-tutoring.png',
|
||||
@@ -22,20 +22,25 @@ const CORE_GRADES = ['초등학생', '중학생', '고등학생']
|
||||
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '쌤인증', siteDomain = 'https://soohyun.xyz') {
|
||||
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
|
||||
|
||||
const shortSido = parsed.shortSido || sido.replace(/특별시|광역시|특별자치시|도/g, '').trim()
|
||||
const shortSido = parsed.shortSido || getShortSido(sido)
|
||||
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
|
||||
const regionTitle = shortRegionName || getShortRegionName(sido, dong)
|
||||
const regionTitle = shortRegionName || getShortRegionName(sido, dong, sigungu)
|
||||
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
|
||||
|
||||
// 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀
|
||||
const dongHash = Array.from(dong || '').reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
|
||||
const baseSubjects = ['수학', '영어', '국어', '과학', '사회']
|
||||
const rotatedSubjects = baseSubjects.map((_, i) => baseSubjects[(dongHash + i) % baseSubjects.length])
|
||||
const rotatedSubText = rotatedSubjects.map(s => `${dong}${s}과외선생님`).join(', ')
|
||||
|
||||
const pageTitle = isRegionOnly
|
||||
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
|
||||
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
|
||||
|
||||
// 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화)
|
||||
const pageDescription = isRegionOnly
|
||||
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교를 확인하세요.`
|
||||
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.`
|
||||
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교(${rotatedSubText})를 확인하세요.`
|
||||
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업(${rotatedSubText})을 신청하세요.`
|
||||
|
||||
const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user