kbank 소스 일부 복원
This commit is contained in:
@@ -7,6 +7,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@Component
|
||||
@Aspect
|
||||
@@ -16,9 +17,9 @@ public class JpaErrorLoggingAspect {
|
||||
private EntityManager em;
|
||||
|
||||
@AfterThrowing(pointcut = "@annotation(org.springframework.transaction.annotation.Transactional)", throwing = "ex")
|
||||
public void onError(Exception ex) {
|
||||
public void onError(SQLException ex) {
|
||||
try {
|
||||
String user = (String) em.createNativeQuery("select user from user").getSingleResult();
|
||||
String user = (String) em.createNativeQuery("select user from dual").getSingleResult();
|
||||
log.error("DB USER = {}, SQL ERROR = {}", user, ex.getMessage() );
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to fetch DB User", e);
|
||||
|
||||
@@ -1,90 +1,104 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
|
||||
<body>
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<!-- Login Header with Logo -->
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">로그인</h1>
|
||||
<p class="login-subtitle">광주은행 개발자 포털에 오신 것을 환영합니다</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Messages -->
|
||||
<div th:if="${param.logout}" class="login-alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span>로그아웃되었습니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form id="loginForm" role="form" name="loginForm" th:action="@{/actionLogin.do}" method="post" class="login-form">
|
||||
<section layout:fragment="contentFragment" class="content">
|
||||
<div class="content_wrap">
|
||||
<div class="sub_title2">
|
||||
<h2 class="title add7">로그인</h2>
|
||||
</div>
|
||||
<div class="inner i_cs9 h_inner9">
|
||||
<div class="form_type">
|
||||
<form id="loginForm" role="form" name="loginForm" th:action="@{/actionLogin.do}" method="post">
|
||||
<input type="hidden" id="message" name="message" th:value="${loginMessage}">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
|
||||
<input type="hidden" id="loginId" th:value="${loginId}">
|
||||
<div class="form_type_box">
|
||||
<div class="info1 form_top">
|
||||
<p class="title">
|
||||
<span>아이디</span>
|
||||
</p>
|
||||
<div class="info_line info_add">
|
||||
<div class="info_box1 add add2">
|
||||
<input type="text" id="id" name="id" th:value="${loginId}"
|
||||
class="common_input_type_1 h_inp"
|
||||
placeholder="이메일 아이디를 입력해 주세요." required>
|
||||
<input type="hidden" id="loginId" th:value="${loginId}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Field -->
|
||||
<div class="form-group">
|
||||
<label for="id" class="form-label">이메일</label>
|
||||
<input type="text"
|
||||
id="id"
|
||||
name="id"
|
||||
th:value="${loginId}"
|
||||
class="form-input"
|
||||
placeholder="이메일 아이디를 입력해 주세요"
|
||||
required>
|
||||
<div class="info1">
|
||||
<p class="title">
|
||||
<span>비밀번호</span>
|
||||
</p>
|
||||
<div class="info_line info_add">
|
||||
<div class="info_box1 add add2">
|
||||
<input type="password" id="password" name="password"
|
||||
class="common_input_type_1 h_inp"
|
||||
placeholder="비밀번호를 입력해 주세요." required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="form-label">비밀번호</label>
|
||||
<input type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-input"
|
||||
placeholder="비밀번호를 입력해 주세요"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me Checkbox -->
|
||||
<div class="form-checkbox">
|
||||
<input id="checkId" name="checkId" type="checkbox">
|
||||
<label for="checkId">아이디 저장</label>
|
||||
<div class="box_inp">
|
||||
<span class="inp_check f_size">
|
||||
<input id="checkId" name="checkId" type="checkbox">
|
||||
<label for="checkId">아이디 저장</label>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Login Button -->
|
||||
<button type="button" class="login-button" id="actionLogin">
|
||||
로그인
|
||||
</button>
|
||||
|
||||
<!-- Additional Links -->
|
||||
<div class="login-links">
|
||||
<a th:href="@{/account_recovery(tab='findId')}">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>아이디 찾기</span>
|
||||
</a>
|
||||
<span class="link-separator">|</span>
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}">
|
||||
<i class="fas fa-lock"></i>
|
||||
<span>비밀번호 초기화</span>
|
||||
</a>
|
||||
<span class="link-separator">|</span>
|
||||
<a th:href="@{/signup}">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
<span>회원가입</span>
|
||||
</a>
|
||||
<div class="btn_login">
|
||||
<a href="#" class="common_btn_type_1 h_btn" id="actionLogin"><span>로그인</span></a>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div class="login-loading" id="loginLoading">
|
||||
<div class="spinner"></div>
|
||||
<div class="login_list pc-only">
|
||||
<ul>
|
||||
<li class="icon_search">
|
||||
<img th:src="@{/img/icon/icon_login_search.png}" alt="">
|
||||
<a th:href="@{/account_recovery(tab='findId')}">아이디 찾기</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_lock">
|
||||
<img th:src="@{/img/icon/icon_login_lock.png}" alt="">
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}">비밀번호 초기화</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_join">
|
||||
<img th:src="@{/img/icon/icon_login_join.png}" alt="">
|
||||
<a th:href="@{/signup}">회원가입</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="login_list m-only">
|
||||
<ul>
|
||||
<li class="icon_search">
|
||||
<a th:href="@{/account_recovery(tab='findId')}">아이디 찾기</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_lock">
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}">비밀번호 초기화</a>
|
||||
</li>
|
||||
<li>
|
||||
<span>|</span>
|
||||
</li>
|
||||
<li class="icon_join">
|
||||
<a th:href="@{/signup}">회원가입</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="alert alert-info mt-3">
|
||||
<p>로그아웃되었습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
</section>
|
||||
</body>
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script th:inline="javascript">
|
||||
@@ -165,10 +179,6 @@
|
||||
|
||||
let actionLogin = function (event) {
|
||||
let form = document.loginForm;
|
||||
|
||||
// Show loading state
|
||||
$('#loginLoading').addClass('active');
|
||||
|
||||
if ($('#checkId').is(':checked')) {
|
||||
setCookie('saveid', $('#id').val(), new Date(new Date().getTime() + 1000 * 3600 * 24 * 30));
|
||||
} else {
|
||||
@@ -178,11 +188,9 @@
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$('#loginLoading').removeClass('active');
|
||||
customPopups.showAlert('입력 정보 확인');
|
||||
} else {
|
||||
if (form.password.value.length < 8) {
|
||||
$('#loginLoading').removeClass('active');
|
||||
customPopups.showAlert('[[#{login.passLengthShort}]]');
|
||||
} else {
|
||||
const originalPassword = form.password.value;
|
||||
@@ -193,10 +201,7 @@
|
||||
form.classList.add('was-validated');
|
||||
};
|
||||
|
||||
$('#actionLogin').on('click', function(event) {
|
||||
event.preventDefault();
|
||||
actionLogin(event);
|
||||
});
|
||||
$('#actionLogin').on('click', actionLogin);
|
||||
$('#password').on('keydown', function (event) {
|
||||
if (event.key === 'Enter' || event.keyCode === 13 || event.code === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -1,372 +1,459 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_index_layout}">
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_index_layout}">
|
||||
|
||||
|
||||
<body>
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<section class="hero-section">
|
||||
<div class="hero-patterns">
|
||||
<div class="pattern-circle pattern-1"></div>
|
||||
<div class="pattern-circle pattern-2"></div>
|
||||
<div class="pattern-dots"></div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="hero-content">
|
||||
<div class="hero-badge">
|
||||
<span class="badge-icon">🚀</span>
|
||||
<span>Wa bank 생태계에 오신 것을 환영합니다!</span>
|
||||
<section layout:fragment="contentFragment" class="content main">
|
||||
<script th:src="@{/js/htmx.min.js}"></script>
|
||||
<!-- main slick -->
|
||||
<div class="slider-container">
|
||||
<div class="slider m-only">
|
||||
<div class="slider_box">
|
||||
|
||||
<img th:src="@{/img/img_mainslick_m.png}" alt="슬라이드 1">
|
||||
<div class="slick_txt">
|
||||
<p>디지털 금융 리더 <span>케이뱅크</span>가<br/>새로운 비즈니스 기회를<br/>제공합니다.</p>
|
||||
</div>
|
||||
<h1 class="main-headline">
|
||||
<span class="headline-wa">Wa한</span> 금융 혁신,<br>
|
||||
여러분의 아이디어로 시작됩니다
|
||||
</h1>
|
||||
<h2 class="sub-headline">
|
||||
광주은행 오픈 API로 새로운 금융 서비스를 만들어보세요.<br>
|
||||
안전하고 빠르게, 그리고 쉽게 연동할 수 있습니다.
|
||||
</h2>
|
||||
<div class="hero-buttons">
|
||||
<a href="/apis" class="btn btn-primary">
|
||||
<i class="fas fa-code"></i> API 둘러보기
|
||||
</a>
|
||||
<a href="#get-started" class="btn btn-secondary">
|
||||
<i class="fas fa-play"></i> 5분 만에 시작하기
|
||||
</a>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick2_m.png}" alt="슬라이드 2">
|
||||
<div class="slick_txt">
|
||||
<p>차별화된 <span>API와<br> 편리한 테스트 환경</span>을<br> 지원합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick3_m.png}" alt="슬라이드 3">
|
||||
<div class="slick_txt">
|
||||
<p><span>디지털 금융 혁신</span>을<br> 케이뱅크가<br> 함께합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- API 검색 섹션 -->
|
||||
<section class="api-search-section">
|
||||
<div class="container">
|
||||
<div class="search-container">
|
||||
<form th:action="@{/apis}" method="get" class="search-form">
|
||||
<div class="search-wrapper">
|
||||
<input type="text" class="search-input" placeholder="원하는 API를 검색해보세요" id="apiSearchInput" name="keyword">
|
||||
<button class="search-button" type="submit">
|
||||
<i class="fas fa-search"></i>
|
||||
<span>검색하기</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="hashtag-suggestions" th:if="${hashtags != null and not #lists.isEmpty(hashtags)}">
|
||||
<span class="hashtag-label">추천 검색어:</span>
|
||||
<a href="#" class="hashtag" th:each="tag : ${hashtags}"
|
||||
th:data-search="${tag}"
|
||||
th:text="${'#' + tag}">
|
||||
</a>
|
||||
<div class="slider pc-only">
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick_pc.png}" alt="슬라이드 1">
|
||||
<div class="slick_txt">
|
||||
<p><span>케이뱅크</span>가 새로운 비즈니스<br> 기회를 제공합니다</p>
|
||||
</div>
|
||||
<!-- Fallback hashtags if no hashtags from controller -->
|
||||
<div class="hashtag-suggestions" th:unless="${hashtags != null and not #lists.isEmpty(hashtags)}">
|
||||
<span class="hashtag-label">추천 검색어:</span>
|
||||
<a href="#" class="hashtag" data-search="계좌조회">#계좌조회</a>
|
||||
<a href="#" class="hashtag" data-search="송금API">#송금API</a>
|
||||
<a href="#" class="hashtag" data-search="실시간결제">#실시간결제</a>
|
||||
<a href="#" class="hashtag" data-search="오픈뱅킹">#오픈뱅킹</a>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick2_pc.png}" alt="슬라이드 2">
|
||||
<div class="slick_txt">
|
||||
<p>차별화된 <span>API와</span><br/>
|
||||
<span>편리한 테스트 환경</span>을<br/>지원합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider_box">
|
||||
<img th:src="@{/img/img_mainslick3_pc.png}" alt="슬라이드 3">
|
||||
<div class="slick_txt">
|
||||
<p><span>디지털 금융 혁신</span>을<br>케이뱅크가<br>함께합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<button class="slick-prev" aria-label="Previous" type="button">이전</button>
|
||||
<button class="slick-next" aria-label="Next" type="button">다음</button>
|
||||
<ul class="slick-dots">
|
||||
<li>
|
||||
<button type="button">1</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button">2</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button">3</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- // main slick -->
|
||||
|
||||
<section class="api-showcase">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
<span class="title-highlight">인기 API</span>로 바로 시작하세요
|
||||
</h2>
|
||||
<p class="section-subtitle">가장 많이 사용되는 API를 확인하고 바로 테스트해보세요</p>
|
||||
<div class="main-container">
|
||||
<div class="search_type">
|
||||
<form th:action="@{/apis}" id="searchForm" method="get">
|
||||
<div class="search-container">
|
||||
<input type="text" name="keyword" class="search-box" id="search-box" placeholder="어떤 API를 찾고 계신가요?">
|
||||
<button class="search-button" type="submit"></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="hashtag">
|
||||
<ul>
|
||||
<li th:each="tag: ${hashtags}"><a href="#" th:text="${tag}">#인증</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main_keyword">
|
||||
<div class="api_tit">
|
||||
<h2>가장 많이 찾는 API</h2>
|
||||
<p class="total_view">
|
||||
<a href="/apis">전체보기</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="api-grid">
|
||||
<!-- Dynamic API Cards from Controller (max 4) -->
|
||||
<div th:each="api, iterStat : ${apis}"
|
||||
th:if="${iterStat.index < 4}"
|
||||
class="api-card"
|
||||
th:classappend="${iterStat.index == 0} ? 'api-card-popular' : (${iterStat.index == 3} ? 'api-card-new' : '')">
|
||||
|
||||
<!-- Show badge for first (인기) and last (NEW) items -->
|
||||
<div th:if="${iterStat.index == 0}" class="api-badge">인기</div>
|
||||
<div th:if="${iterStat.index == 3}" class="api-badge new">NEW</div>
|
||||
|
||||
<!-- API Icon - using different icons based on index or API type -->
|
||||
<div class="api-icon">
|
||||
<!-- Icon selection based on API name or service type -->
|
||||
<i th:if="${api.apiName != null}"
|
||||
th:class="${#strings.containsIgnoreCase(api.apiName, '계좌') or #strings.containsIgnoreCase(api.apiName, '조회')} ? 'fas fa-wallet' :
|
||||
(${#strings.containsIgnoreCase(api.apiName, '송금') or #strings.containsIgnoreCase(api.apiName, '이체')} ? 'fas fa-exchange-alt' :
|
||||
(${#strings.containsIgnoreCase(api.apiName, '카드') or #strings.containsIgnoreCase(api.apiName, '결제')} ? 'fas fa-credit-card' :
|
||||
(${#strings.containsIgnoreCase(api.apiName, 'AI') or #strings.containsIgnoreCase(api.apiName, '분석')} ? 'fas fa-chart-line' :
|
||||
(${iterStat.index == 0} ? 'fas fa-wallet' :
|
||||
(${iterStat.index == 1} ? 'fas fa-exchange-alt' :
|
||||
(${iterStat.index == 2} ? 'fas fa-credit-card' : 'fas fa-chart-line'))))))"></i>
|
||||
</div>
|
||||
|
||||
<!-- API Name -->
|
||||
<h3 class="api-name" th:text="${api.apiName}">API 이름</h3>
|
||||
|
||||
<!-- API Description -->
|
||||
<p class="api-description" th:text="${api.apiSimpleDescription != null ? api.apiSimpleDescription : api.description}">API 설명</p>
|
||||
|
||||
<!-- API Features/Method Info -->
|
||||
<div class="api-features">
|
||||
<span class="feature-tag" th:if="${api.apiMethod != null}" th:text="${api.apiMethod}">GET</span>
|
||||
<span class="feature-tag" th:if="${api.service != null}" th:text="${api.service}">서비스</span>
|
||||
</div>
|
||||
|
||||
<!-- Test Link -->
|
||||
<a th:href="@{/apis/detail(id=${api.apiId})}" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
<div class="api_keyword">
|
||||
<ul class="keyword_list tab_list">
|
||||
<li>
|
||||
<a href="#"
|
||||
hx-get="/api_fragments"
|
||||
hx-target="#api_spec_list"
|
||||
hx-trigger="click"
|
||||
onclick="setActive(this)">전체<span class="number" th:text="${totalApiCount}"></span></a>
|
||||
</li>
|
||||
<li th:each="item : ${services}">
|
||||
<a href="#"
|
||||
th:hx-get="@{/api_fragments(id=${item.id})}"
|
||||
hx-target="#api_spec_list"
|
||||
hx-trigger="click"
|
||||
onclick="setActive(this)">
|
||||
[[${item.groupName}]]<span class="number" th:text="${item.apiGroupApiList.size()}"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="more_type m-only">
|
||||
<button class="showmore" type="button">
|
||||
<img th:src="@{/img/icon/icon_more_btn.png}" alt="더보기 버튼">
|
||||
</button>
|
||||
</div>
|
||||
<div class="more_type pc-only">
|
||||
<button class="showmore" type="button">
|
||||
<img th:src="@{/img/icon/icon_more_btn2.png}" alt="더보기 버튼">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fallback: Show static cards if no APIs from controller -->
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card api-card-popular">
|
||||
<div class="api-badge">인기</div>
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-wallet"></i>
|
||||
<div class="api_info_container box_list" id="api_spec_list">
|
||||
<div class="api_info_box" th:each="api : ${apis}" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<div class="api_list" th:data-href="@{/apis/detail(id=${api.apiId})}">
|
||||
<p class="txt1" th:text="${api.apiName}"></p>
|
||||
<p class="txt2" th:text="${api.apiSimpleDescription}"></p>
|
||||
</div>
|
||||
<h3 class="api-name">계좌 조회 API</h3>
|
||||
<p class="api-description">실시간 잔액 조회와 거래내역을 안전하게 확인</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">실시간</span>
|
||||
<span class="feature-tag">OAuth 2.0</span>
|
||||
<div class="api_img" style="height: 48px;">
|
||||
<a th:href="@{/apis/detail(id=${api.apiId})}" class="btn btn-primary">
|
||||
<img th:if="${#strings.isEmpty(api.mainIcon) == false}" th:src="${api.mainIcon}" alt="">
|
||||
</a>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card">
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</div>
|
||||
<h3 class="api-name">송금 API</h3>
|
||||
<p class="api-description">간편하고 안전한 송금 서비스 구현</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">간편송금</span>
|
||||
<span class="feature-tag">실명확인</span>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card">
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-credit-card"></i>
|
||||
</div>
|
||||
<h3 class="api-name">카드 결제 API</h3>
|
||||
<p class="api-description">온/오프라인 결제 솔루션 통합</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">PG연동</span>
|
||||
<span class="feature-tag">간편결제</span>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div th:if="${apis == null or #lists.isEmpty(apis)}" class="api-card api-card-new">
|
||||
<div class="api-badge new">NEW</div>
|
||||
<div class="api-icon">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
<h3 class="api-name">AI 신용평가 API</h3>
|
||||
<p class="api-description">머신러닝 기반 실시간 신용 분석</p>
|
||||
<div class="api-features">
|
||||
<span class="feature-tag">AI/ML</span>
|
||||
<span class="feature-tag">실시간</span>
|
||||
</div>
|
||||
<a href="#" class="api-link">
|
||||
테스트하기 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 정보 섹션 -->
|
||||
<section class="info-section">
|
||||
<div class="container">
|
||||
<div class="info-wrapper">
|
||||
<div class="image-container">
|
||||
<div class="image-placeholder">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
<span>금융 혁신</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main_con">
|
||||
<p class="con_tit m-only"><img th:src="@{/img/img_con_tit.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_tit pc-only"><img th:src="@{/img/img_con_tit2.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_img m-only"><img th:src="@{/img/img_mask_group.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_img pc-only"><img th:src="@{/img/img_mask_group2.png}" alt="Kbank API PORTAL"></p>
|
||||
<p class="con_txt m-only">
|
||||
<span>Kbank API Portal</span>은 기업이 금융서비스를<br>
|
||||
편리하게 개발할 수 있도록 차별화된<br>
|
||||
API와 테스트환경을 제공합니다.<br>
|
||||
</p>
|
||||
<p class="con_txt pc-only">
|
||||
<span>Kbank API Portal</span>은 기업이 금융서비스를 편리하게 개발할 수 있도록 차별화된 API와 테스트환경을 제공합니다.
|
||||
</p>
|
||||
<div class="main_btn">
|
||||
<a th:href="@{/intro}" class="common_btn_type_5"><span>처음 만나는 케이뱅크 API</span></a>
|
||||
<a th:href="@{/partnership}" class="common_btn_type_5 m_btn"><span>사업 제휴 문의</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-content">
|
||||
<p class="highlight-text">
|
||||
광주은행 <span class="text-accent">API Portal</span>은 기업이 금융서비스를 편리하게 개발할 수 있도록<br>
|
||||
차별화된 API와 테스트환경을 제공합니다.
|
||||
<div class="tiding_box">
|
||||
<div class="api_type">
|
||||
<div class="api_tiding">
|
||||
<div class="api_tit">
|
||||
<h2>새로운 케이뱅크 API 소식을<br> 만나보세요</h2>
|
||||
<p class="total_view">
|
||||
<a href="/portalnotice">전체보기</a>
|
||||
</p>
|
||||
|
||||
<div class="action-buttons">
|
||||
<a href="#" class="action-btn">
|
||||
<span class="btn-number">1</span>
|
||||
<span class="btn-text">처음 만나는 광주뱅크 API</span>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
<div class="api_tiding_list">
|
||||
<ul>
|
||||
<li th:each="notice : ${portalNotices}">
|
||||
<a th:href="@{/portalnotice/detail(id=${notice.id})}">
|
||||
<p th:text="${notice.noticeSubject}"></p>
|
||||
</a>
|
||||
<span th:text="${#temporals.format(notice.createdDate, 'yyyy.MM.dd')}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api_info">
|
||||
<ul>
|
||||
<li class="img1">
|
||||
<a th:href="@{/faq_list}">
|
||||
<p>FAQ</p>
|
||||
<span>자주 묻는 질문과 답변을<br> 확인해보세요</span>
|
||||
<img th:src="@{/img/icon/icon_petals01.png}" alt="faq 이미지">
|
||||
</a>
|
||||
<a href="#" class="action-btn">
|
||||
<span class="btn-number">2</span>
|
||||
<span class="btn-text">사업 제휴 문의</span>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</li>
|
||||
<li class="img2">
|
||||
<a th:href="@{/inquiry}">
|
||||
<p>Q&A</p>
|
||||
<span>문의사항에<br> 친절하게 답변 드리겠습니다</span>
|
||||
<img th:src="@{/img/icon/icon_petals02.png}" alt="Q&A 이미지">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="img3">
|
||||
<a th:href="@{/partnership}">
|
||||
<p>사업제휴</p>
|
||||
<span>온라인 비즈니스 혁신을 위한<br> 사업 제안을 환영합니다</span>
|
||||
<img th:src="@{/img/icon/icon_petals03.png}" alt="사업제휴 이미지">
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 지원 센터 섹션 -->
|
||||
<section class="support-center">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
도움이 <span class="title-highlight">필요하신가요?</span>
|
||||
</h2>
|
||||
<p class="section-subtitle">다양한 방법으로 필요한 정보를 찾아보세요</p>
|
||||
</div>
|
||||
<div class="support-grid">
|
||||
<a th:href="@{/portalnotice}" class="support-card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-bullhorn"></i>
|
||||
</div>
|
||||
<h3>공지사항</h3>
|
||||
<p>최신 업데이트와 중요한 소식을 확인하세요</p>
|
||||
<div class="card-arrow">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
<a th:href="@{/faq_list}" class="support-card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
</div>
|
||||
<h3>자주 묻는 질문</h3>
|
||||
<p>자주 묻는 질문과 답변을 빠르게 찾아보세요</p>
|
||||
<div class="card-arrow">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
<a th:href="@{/inquiry}" class="support-card">
|
||||
<div class="card-icon">
|
||||
<i class="fas fa-comments"></i>
|
||||
</div>
|
||||
<h3>문의하기</h3>
|
||||
<p>추가 도움이 필요하시면 직접 문의해주세요</p>
|
||||
<div class="card-arrow">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
</a>
|
||||
<div class="api_count">
|
||||
<div class="api_total_box">
|
||||
<p class="txt m-only">우리곁의 수많은 서비스들이<br> <em>케이뱅크API</em>와<br> 함께하고 있습니다</p>
|
||||
<p class="txt pc-only">우리곁의 수많은 서비스들이<br> <em>케이뱅크API와 함께</em>하고 있습니다</p>
|
||||
<div class="api_total">
|
||||
<ul>
|
||||
<li>
|
||||
<img th:src="@{/img/icon/icon_api01.png}" alt="서비스 이용 수">
|
||||
<div class="api_num">
|
||||
<p th:text="${serviceCount}"></p>
|
||||
<span>서비스 이용</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<img th:src="@{/img/icon/icon_api02.png}" alt="API 이용건수">
|
||||
<div class="api_num">
|
||||
<p th:text="${selectedApiCount}"></p>
|
||||
<span>API 이용</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<img th:src="@{/img/icon/icon_api03.png}" alt="API 활용기업">
|
||||
<div class="api_num">
|
||||
<p th:text="${approvedOrgCount}"></p>
|
||||
<span>API 활용 기업</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- API 활용 현황 섹션 -->
|
||||
<section class="api-stats-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">
|
||||
우리곁의 수많은 서비스들이<br>
|
||||
<span class="title-highlight">광주은행 API</span>와 함께하고 있습니다
|
||||
</h2>
|
||||
</div>
|
||||
<div class="stats-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-store"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" th:attr="data-target=${serviceCount != null ? serviceCount : 0}" th:text="${serviceCount != null ? serviceCount : 0}">0</div>
|
||||
<div class="stat-label">서비스 이용 수</div>
|
||||
<div class="stat-description">다양한 분야의 서비스가 활용중</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" th:attr="data-target=${totalApiCount != null ? totalApiCount : 0}" th:text="${totalApiCount != null ? totalApiCount : 0}">0</div>
|
||||
<div class="stat-label">API 이용 건수</div>
|
||||
<div class="stat-description">일일 평균 API 호출 수</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-building"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number" th:attr="data-target=${approvedOrgCount != null ? approvedOrgCount : 0}" th:text="${approvedOrgCount != null ? approvedOrgCount : 0}">0</div>
|
||||
<div class="stat-label">API 활용 기업</div>
|
||||
<div class="stat-description">혁신적인 파트너사와 함께</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</th:block>
|
||||
<div id="main_bg"></div>
|
||||
</section>
|
||||
</body>
|
||||
<th:block layout:fragment="contentScript">
|
||||
<script>
|
||||
// 숫자 카운트업 애니메이션
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const observerOptions = {
|
||||
threshold: 0.5,
|
||||
rootMargin: '0px 0px -100px 0px'
|
||||
};
|
||||
|
||||
const animateNumbers = (entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const numbers = entry.target.querySelectorAll('.stat-number');
|
||||
|
||||
numbers.forEach(number => {
|
||||
const target = parseInt(number.getAttribute('data-target'));
|
||||
const duration = 2000; // 2초
|
||||
const increment = target / (duration / 16);
|
||||
let current = 0;
|
||||
|
||||
const updateNumber = () => {
|
||||
current += increment;
|
||||
if (current < target) {
|
||||
number.textContent = Math.floor(current).toLocaleString('ko-KR');
|
||||
requestAnimationFrame(updateNumber);
|
||||
} else {
|
||||
number.textContent = target.toLocaleString('ko-KR');
|
||||
}
|
||||
};
|
||||
|
||||
updateNumber();
|
||||
});
|
||||
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(animateNumbers, observerOptions);
|
||||
const statsSection = document.querySelector('.api-stats-section');
|
||||
|
||||
if (statsSection) {
|
||||
observer.observe(statsSection);
|
||||
<script th:inline="javascript">
|
||||
$(document).ready(function() {
|
||||
// 기존 success 메시지 처리
|
||||
var successMsg = [[${success}]];
|
||||
console.log("Success message:", successMsg);
|
||||
if (successMsg) {
|
||||
console.log("Showing alert");
|
||||
customPopups.showAlert(successMsg);
|
||||
}
|
||||
|
||||
// API 검색 기능
|
||||
const searchInput = document.getElementById('apiSearchInput');
|
||||
const searchForm = document.querySelector('.search-form');
|
||||
const hashtags = document.querySelectorAll('.hashtag');
|
||||
// 장기미사용 계정 체크
|
||||
const dormantAccount = [[${session.dormantAccount}]];
|
||||
if (dormantAccount) {
|
||||
const dormantMsg = /*[[${session.success}]]*/ '';
|
||||
const redirectUrl = /*[[${session.redirectUrl}]]*/ '';
|
||||
|
||||
// 해시태그 클릭 이벤트
|
||||
hashtags.forEach(tag => {
|
||||
tag.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const searchTerm = this.getAttribute('data-search');
|
||||
searchInput.value = searchTerm;
|
||||
// 폼 제출
|
||||
if (searchForm) {
|
||||
searchForm.submit();
|
||||
console.log('dormantMsg:', dormantMsg);
|
||||
console.log('redirectUrl:', redirectUrl);
|
||||
|
||||
customPopups.showAlert(dormantMsg);
|
||||
$('#customAlertOkButton').off('click.custom').on('click.custom', function () {
|
||||
customPopups.hideAlert('#customAlert');
|
||||
window.location.href = redirectUrl;
|
||||
});
|
||||
return; // 다른 체크 중단
|
||||
}
|
||||
|
||||
// 비밀번호 만료 체크 추가
|
||||
const passwordExpired = [[${session.passwordExpired}]];
|
||||
if (passwordExpired) {
|
||||
const pwdExpiredMsg = /*[[${session.success}]]*/ '';
|
||||
const formattedMsg = pwdExpiredMsg.replace(/\n/g, '<br>');
|
||||
const redirectUrl = /*[[${session.redirectUrl}]]*/ '';
|
||||
|
||||
console.log('pwdExpiredMsg:', pwdExpiredMsg);
|
||||
console.log('redirectUrl:', redirectUrl);
|
||||
|
||||
$('#customAlertMessage').html(formattedMsg);
|
||||
$('#customAlert').css('display','flex');
|
||||
|
||||
$('#customAlertOkButton').off('click.custom').on('click.custom', function () {
|
||||
customPopups.hideAlert('#customAlert');
|
||||
window.location.href = redirectUrl;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
function setActive(element) {
|
||||
// Remove 'active' class from all links
|
||||
document.querySelectorAll('.keyword_list a').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
// Add 'active' class to clicked link
|
||||
element.classList.add('active');
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const ITEMS_PER_PAGE = 4;
|
||||
let isExpanded = false;
|
||||
|
||||
// 더보기 버튼 초기화
|
||||
function initMoreButton() {
|
||||
const mobileMoreBtn = document.querySelector('.more_type.m-only .showmore');
|
||||
const pcMoreBtn = document.querySelector('.more_type.pc-only .showmore');
|
||||
const apiInfoBoxes = document.querySelectorAll('.api_info_box');
|
||||
|
||||
// 5개 미만이면 더보기 버튼 숨기기
|
||||
const mobileMoreContainer = document.querySelector('.more_type.m-only');
|
||||
const pcMoreContainer = document.querySelector('.more_type.pc-only');
|
||||
|
||||
console.log('더보기 버튼 초기화:', {
|
||||
항목개수: apiInfoBoxes.length,
|
||||
보여질상태: apiInfoBoxes.length >= 5 ? 'block' : 'none'
|
||||
});
|
||||
|
||||
// 더보기 버튼 표시 여부 설정
|
||||
if (apiInfoBoxes.length < 5) {
|
||||
mobileMoreContainer.style.setProperty('display', 'none', 'important');
|
||||
pcMoreContainer.style.setProperty('display', 'none', 'important');
|
||||
} else {
|
||||
mobileMoreContainer.style.setProperty('display', 'block', 'important');
|
||||
pcMoreContainer.style.setProperty('display', 'block', 'important');
|
||||
}
|
||||
|
||||
// 초기 상태 설정
|
||||
updateApiListVisibility();
|
||||
|
||||
// 더보기 버튼 이벤트 리스너 설정
|
||||
[mobileMoreBtn, pcMoreBtn].forEach(btn => {
|
||||
if (btn) {
|
||||
btn.removeEventListener('click', handleMoreClick);
|
||||
btn.addEventListener('click', handleMoreClick);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleMoreClick(e) {
|
||||
e.preventDefault();
|
||||
console.log('더보기 버튼 클릭');
|
||||
isExpanded = !isExpanded; // 상태 토글
|
||||
|
||||
// 버튼 이미지/텍스트 변경
|
||||
const mobileMoreBtn = document.querySelector('.more_type.m-only .showmore img');
|
||||
const pcMoreBtn = document.querySelector('.more_type.pc-only .showmore img');
|
||||
if (mobileMoreBtn) {
|
||||
mobileMoreBtn.alt = isExpanded ? "접기 버튼" : "더보기 버튼";
|
||||
}
|
||||
if (pcMoreBtn) {
|
||||
pcMoreBtn.alt = isExpanded ? "접기 버튼" : "더보기 버튼";
|
||||
}
|
||||
|
||||
updateApiListVisibility();
|
||||
}
|
||||
|
||||
// API 리스트 표시 상태 업데이트
|
||||
function updateApiListVisibility() {
|
||||
const apiInfoBoxes = document.querySelectorAll('.api_info_box');
|
||||
const endIndex = isExpanded ? apiInfoBoxes.length : ITEMS_PER_PAGE;
|
||||
|
||||
console.log('업데이트 - API 목록:', {
|
||||
총개수: apiInfoBoxes.length,
|
||||
표시상태: isExpanded ? '전체표시' : '일부표시',
|
||||
표시할개수: endIndex
|
||||
});
|
||||
|
||||
apiInfoBoxes.forEach((box, index) => {
|
||||
box.style.display = index < endIndex ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// HTMX 로드 완료 이벤트 리스너
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target.id === 'api_spec_list') {
|
||||
console.log('HTMX 컨텐츠 로드됨 - 페이지 초기화');
|
||||
isExpanded = false;
|
||||
initMoreButton();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:afterSettle', function(evt) {
|
||||
const apiListItems = document.querySelectorAll('.api_list');
|
||||
apiListItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
const apiItems = document.querySelectorAll('.api_info_box');
|
||||
apiItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 초기 실행
|
||||
console.log('초기 실행');
|
||||
initMoreButton();
|
||||
|
||||
// All 카테고리를 기본으로 active 설정
|
||||
document.querySelector('.keyword_list li:first-child a').classList.add('active');
|
||||
|
||||
const apiListItems = document.querySelectorAll('.api_list');
|
||||
apiListItems.forEach(function(item) {
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const apiItems = document.querySelectorAll('.api_info_box');
|
||||
apiItems.forEach(function(item) {
|
||||
item.removeEventListener('click',null);
|
||||
item.addEventListener('click', function() {
|
||||
const href = this.getAttribute('data-href');
|
||||
if (href) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const hashtagItems = document.querySelectorAll('.hashtag ul li a');
|
||||
const searchBox = document.getElementById('search-box');
|
||||
|
||||
hashtagItems.forEach(function(item) {
|
||||
item.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
// Remove the # symbol if present and set the text in search box
|
||||
const searchText = this.textContent.replace('#', '').trim();
|
||||
searchBox.value = searchText;
|
||||
|
||||
document.getElementById("searchForm").submit();
|
||||
});
|
||||
});
|
||||
|
||||
const infoItems = document.querySelectorAll('.api_info ul li');
|
||||
infoItems.forEach(function (item) {
|
||||
item.style.cursor = 'pointer';
|
||||
item.addEventListener('click', function() {
|
||||
const link = this.querySelector('a');
|
||||
if (link) {
|
||||
location.href = link.getAttribute('href');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
Reference in New Issue
Block a user