페이지 템플릿 시스템의 사용 방법을 단계별로 설명합니다.


1. JSON 필드 추가하고 적용하는 방법

필드 설정 방법

템플릿 생성/수정 페이지의 "템플릿 설정" 탭에서 "필드 설정 (JSON)" 영역에 필드를 정의합니다.

필드 설정 예시:

{
"title": {
"type": "text",
"label": "제목",
"required": true,
"description": "페이지의 제목을 입력하세요"
},
"ceo_name": {
"type": "text",
"label": "대표이사 이름",
"required": false
},
"message": {
"type": "textarea",
"label": "인사말 내용",
"required": true,
"description": "여러 줄로 작성할 수 있습니다"
},
"ceo_image": {
"type": "image",
"label": "대표이사 사진",
"required": false,
"description": "이미지 URL을 입력하세요"
}
}

필드 타입

타입 설명 사용 예시
text 한 줄 입력 제목, 이름, URL 등
textarea 여러 줄 입력 긴 내용, 설명 등
image 이미지 URL 사진, 로고, 배너 등

필드 속성

속성 타입 설명
type string 필드 타입 (text/textarea/image)
label string 사용자에게 표시될 라벨
required boolean 필수 입력 여부 (true/false)
description string 필드 설명 (선택사항)

자동 적용되는 방법

  1. 필드 설정을 저장하면 간편 편집기가 자동으로 생성됩니다
  2. 간편 편집기에서 입력한 내용은 자동으로 **기본 데이터 (JSON)**로 동기화됩니다
  3. 페이지 생성 시 해당 템플릿을 선택하면 정의된 필드들이 입력 폼으로 나타납니다

2. 내용에서 사용 가능한 HTML 태그

허용되는 태그

템플릿 내용에서 다음 HTML 태그들을 사용하여 스타일링할 수 있습니다:

텍스트 서식

  • <strong><b> - 굵은 글씨
  • <em><i> - 이탤릭체
  • <u> - 밑줄
  • <mark> - 하이라이트
  • <small> - 작은 글씨
  • <del><s> - 취소선
  • <sup> - 위 첨자
  • <sub> - 아래 첨자

구조

  • <p> - 문단
  • <div> - 블록 영역
  • <span> - 인라인 영역
  • <br> - 줄바꿈
  • <hr> - 수평선

제목

  • <h1><h2><h3><h4><h5><h6> - 제목 태그

목록

  • <ul><ol><li> - 순서 없는/있는 목록
  • <dl><dt><dd> - 정의 목록

이미지 및 미디어

  • <img> - 이미지
  • <a> - 링크
  • <iframe> - 외부 콘텐츠 (YouTube 등)

  • <table><thead><tbody><tr><th><td> - 표

기타

  • <blockquote> - 인용문
  • <cite> - 출처
  • <code><pre> - 코드 블록

사용 예시

<div class="greeting-message">
<h2>환영합니다!</h2>
<p>저희 회사를 <strong>방문해 주셔서</strong> 감사합니다.</p>
 
<p>주요 특징:</p>
<ul>
<li><strong>전문성</strong>: 20년 이상의 경험</li>
<li><strong>신뢰성</strong>: 고객 만족도 95%</li>
<li><em>혁신</em>: 최신 기술 도입</li>
</ul>
 
<blockquote>
"고객의 성공이 우리의 성공입니다"
</blockquote>
</div>

WARNING

<script> 태그나 위험한 JavaScript는 보안상 제한될 수 있습니다.


3. 외부 CSS로 스타일링하는 방법

현재 방식: 인라인 CSS (템플릿 내부)

현재 시스템은 각 템플릿 파일에 <style> 태그를 사용하여 CSS를 포함합니다:

<div class="greeting-template">
<!-- 내용 -->
</div>
 
<style>
.greeting-template {
padding: 60px 0;
}
 
.greeting-header {
display: flex;
align-items: center;
margin-bottom: 40px;
}
/* ... 더 많은 스타일 */
</style>

권장 방식: 템플릿별 CSS 파일

템플릿마다 전용 CSS 파일을 만들어 관리하는 것이 더 좋습니다:

1. CSS 파일 구조 제안

templates/
page_templates/
greeting_basic.php
greeting_basic.css ← 템플릿 전용 CSS
history_timeline.php
history_timeline.css ← 템플릿 전용 CSS
contact_info.php
contact_info.css ← 템플릿 전용 CSS

2. 템플릿에서 CSS 로드하기

템플릿 PHP 파일에서 CSS 파일을 연결:

<?php
// 템플릿 데이터 가져오기
$pageData = $data['data'] ?? [];
$templateConfig = $data['config'] ?? [];
?>
 
<!-- CSS 파일 로드 -->
<link rel="stylesheet" href="/templates/page_templates/greeting_basic.css">
 
<div class="greeting-template">
<div class="container">
<!-- 템플릿 내용 -->
</div>
</div>

3. CSS 파일 예시 (greeting_basic.css)

.greeting-template {
padding: 60px 0;
background: #f8f9fa;
}
 
.greeting-header {
display: flex;
align-items: center;
margin-bottom: 40px;
gap: 30px;
}
 
.ceo-image img {
width: 150px;
height: 150px;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
 
.greeting-content h1 {
margin: 0 0 10px 0;
color: #333;
font-size: 2.5rem;
font-weight: 700;
}
 
@media (max-width: 768px) {
.greeting-header {
flex-direction: column;
text-align: center;
}
}

전역 CSS vs 템플릿 CSS

구분 위치 용도
전역 CSS
 
assets/css/common.css
모든 페이지에 공통 적용되는 스타일
템플릿 CSS templates/page_templates/[템플릿명].css 특정 템플릿에만 적용되는 스타일

전역 CSS 예시

/* assets/css/common.css */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
 
.btn {
padding: 10px 20px;
border-radius: 4px;
border: none;
cursor: pointer;
}

4. 이미지 타입 필드 사용 방법

이미지 필드 정의

{
"ceo_image": {
"type": "image",
"label": "대표이사 사진",
"required": false,
"description": "이미지 URL을 입력하거나 업로드하세요"
},
"company_logo": {
"type": "image",
"label": "회사 로고",
"required": true
}
}

템플릿에서 이미지 사용

<?php if (!empty($pageData["ceo_image"])): ?>
<div class="ceo-image">
<img src="<?= htmlspecialchars($pageData["ceo_image"]) ?>"
alt="대표이사"
class="img-responsive">
</div>
<?php endif; ?>

이미지 업로드 기능 추가 (권장 개선사항)

현재는 URL을 직접 입력하는 방식이지만, 다음과 같이 개선할 수 있습니다:

개선된 이미지 입력 UI

<div class="form-group">
<label>대표이사 사진</label>
<div class="image-upload-wrapper">
<input type="text"
class="easy-input"
data-key="ceo_image"
value=""
placeholder="이미지 URL을 입력하거나 파일을 업로드하세요">
 
<button type="button"
class="btn btn-secondary"
onclick="openImageUpload('ceo_image')">
<i class="fas fa-upload"></i> 파일 선택
</button>
</div>
 
<!-- 이미지 미리보기 -->
<div class="image-preview" id="preview_ceo_image" style="display:none;">
<img src="" alt="미리보기" style="max-width: 200px; margin-top: 10px;">
<button type="button"
class="btn btn-sm btn-danger"
onclick="removeImage('ceo_image')">
<i class="fas fa-times"></i> 제거
</button>
</div>
</div>

이미지 업로드 JavaScript

function openImageUpload(fieldKey) {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
 
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
 
// 파일 업로드
const formData = new FormData();
formData.append('image', file);
 
try {
const response = await fetch('/ajax/upload_image.php', {
method: 'POST',
body: formData
});
 
const result = await response.json();
 
if (result.success) {
// 업로드된 이미지 URL을 필드에 설정
const inputField = document.querySelector(`[data-key="${fieldKey}"]`);
inputField.value = result.url;
 
// 미리보기 표시
showImagePreview(fieldKey, result.url);
 
// JSON 동기화
syncToJSON();
} else {
alert('이미지 업로드 실패: ' + result.message);
}
} catch (error) {
alert('업로드 중 오류가 발생했습니다.');
}
};
 
input.click();
}
 
function showImagePreview(fieldKey, imageUrl) {
const preview = document.getElementById(`preview_${fieldKey}`);
const img = preview.querySelector('img');
 
img.src = imageUrl;
preview.style.display = 'block';
}
 
function removeImage(fieldKey) {
const inputField = document.querySelector(`[data-key="${fieldKey}"]`);
inputField.value = '';
 
const preview = document.getElementById(`preview_${fieldKey}`);
preview.style.display = 'none';
 
syncToJSON();
}

실전 예시: 회사 소개 템플릿

1. 필드 설정

{
"company_name": {
"type": "text",
"label": "회사명",
"required": true
},
"slogan": {
"type": "text",
"label": "슬로건",
"required": false
},
"company_logo": {
"type": "image",
"label": "회사 로고",
"required": false
},
"introduction": {
"type": "textarea",
"label": "회사 소개",
"required": true,
"description": "HTML 태그를 사용하여 스타일링할 수 있습니다"
},
"ceo_image": {
"type": "image",
"label": "대표이사 사진",
"required": false
},
"ceo_name": {
"type": "text",
"label": "대표이사 이름",
"required": false
},
"ceo_message": {
"type": "textarea",
"label": "대표 인사말",
"required": false
}
}

2. 템플릿 PHP 파일 (company_intro.php)

<?php
$pageData = $data['data'] ?? [];
$templateConfig = $data['config'] ?? [];
?>
 
<link rel="stylesheet" href="/templates/page_templates/company_intro.css">
 
<div class="company-intro-template">
<div class="container">
<!-- 헤더 섹션 -->
<div class="company-header">
<?php if (!empty($pageData["company_logo"])): ?>
<img src="<?= htmlspecialchars($pageData["company_logo"]) ?>"
alt="<?= htmlspecialchars($pageData["company_name"] ?? '') ?>"
class="company-logo">
<?php endif; ?>
 
<h1><?= htmlspecialchars($pageData["company_name"] ?? "회사명") ?></h1>
 
<?php if (!empty($pageData["slogan"])): ?>
<p class="slogan"><?= htmlspecialchars($pageData["slogan"]) ?></p>
<?php endif; ?>
</div>
 
<!-- 회사 소개 -->
<div class="company-introduction">
<?= $pageData["introduction"] ?? "" ?>
</div>
 
<!-- CEO 인사말 -->
<?php if (!empty($pageData["ceo_message"])): ?>
<div class="ceo-section">
<h2>대표 인사말</h2>
 
<div class="ceo-content">
<?php if (!empty($pageData["ceo_image"])): ?>
<div class="ceo-image">
<img src="<?= htmlspecialchars($pageData["ceo_image"]) ?>"
alt="<?= htmlspecialchars($pageData["ceo_name"] ?? '대표이사') ?>">
</div>
<?php endif; ?>
 
<div class="ceo-message">
<?= nl2br(htmlspecialchars($pageData["ceo_message"])) ?>
 
<?php if (!empty($pageData["ceo_name"])): ?>
<p class="ceo-name"><?= htmlspecialchars($pageData["ceo_name"]) ?></p>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>

3. 템플릿 CSS 파일 (company_intro.css)

.company-intro-template {
padding: 80px 0;
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
}
 
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
 
/* 헤더 */
.company-header {
text-align: center;
margin-bottom: 60px;
}
 
.company-logo {
max-width: 200px;
height: auto;
margin-bottom: 20px;
}
 
.company-header h1 {
font-size: 3rem;
font-weight: 700;
color: #1a1a1a;
margin: 20px 0 10px;
}
 
.slogan {
font-size: 1.3rem;
color: #666;
font-style: italic;
}
 
/* 회사 소개 */
.company-introduction {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 60px;
line-height: 1.8;
font-size: 1.1rem;
}
 
/* CEO 섹션 */
.ceo-section {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
 
.ceo-section h2 {
text-align: center;
font-size: 2rem;
margin-bottom: 40px;
color: #333;
}
 
.ceo-content {
display: flex;
gap: 40px;
align-items: flex-start;
}
 
.ceo-image img {
width: 200px;
height: 200px;
border-radius: 50%;
object-fit: cover;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
 
.ceo-message {
flex: 1;
font-size: 1.1rem;
line-height: 1.8;
color: #555;
}
 
.ceo-name {
margin-top: 30px;
font-size: 1.3rem;
font-weight: 600;
color: #333;
text-align: right;
}
 
/* 반응형 */
@media (max-width: 768px) {
.company-header h1 {
font-size: 2rem;
}
 
.company-introduction,
.ceo-section {
padding: 20px;
}
 
.ceo-content {
flex-direction: column;
align-items: center;
text-align: center;
}
 
.ceo-name {
text-align: center;
}
}

4. 사용자가 입력하는 내용 예시

<div class="intro-content">
<h2>회사 비전</h2>
<p><strong>혁신</strong><strong>신뢰</strong>를 바탕으로 <em>더 나은 미래</em>를 만들어갑니다.</p>
 
<h3>핵심 가치</h3>
<ul>
<li><strong>고객 중심</strong>: 고객의 성공이 우리의 성공</li>
<li><strong>창의성</strong>: 끊임없는 혁신과 도전</li>
<li><strong>책임감</strong>: 약속을 지키는 기업</li>
</ul>
 
<blockquote>
"우리는 단순히 제품을 판매하는 것이 아니라, <mark>가치를 전달</mark>합니다."
</blockquote>
</div>

요약

✅ 해야 할 것

  1. 필드 설정을 먼저 정의 - JSON 형식으로 필드와 타입 지정
  2. HTML 태그 활용 - 내용에 다양한 태그를 사용하여 스타일링
  3. 템플릿별 CSS 파일 분리 - 유지보수가 쉽고 깔끔한 코드
  4. 이미지 타입 필드 사용 - URL 입력 또는 파일 업로드 (개선 필요)

⚠️ 주의할 것

  • JSON 형식이 정확해야 간편 편집기가 작동합니다
  • 보안상 위험한 <script> 태그는 사용하지 마세요
  • 이미지는 절대 URL 또는 상대 경로를 정확히 입력하세요
  • CSS 클래스명은 템플릿끼리 충돌하지 않도록 고유하게 지정하세요

🚀 개선 제안

  1. 이미지 업로드 기능 추가 - URL 입력 대신 파일 업로드 버튼
  2. WYSIWYG 에디터 통합 - textarea 필드에 TinyMCE 연동
  3. CSS 파일 자동 연결 - 템플릿 ID와 동일한 이름의 CSS 자동 로드
  4. 필드 타입 확장 - select, checkbox, date 등 추가