KISA 제보 취약점(KVE-2026-1914/1919/2019/2029/2041/2042/2043/2044)과 그 수정 과정에서 드러난 자격증명 전송로 결함을 함께 해소한다. globalHeaders 는 데이터소스와 apiCall 핸들러에만 적용되는데, 코어 ApiClient 를 직접 부르는 경로들이 그 사실을 모른 채 게이트된 엔드포인트를 호출하고 있었다. 서버는 정당한 사용자를 거부하고 화면은 이미 버튼을 내준 뒤라, 예외도 로그도 없이 그 자리만 비는 형태로만 드러났다. 전송로 10축을 전수 열거해 6건을 고치고, 같은 실수가 반복되지 않도록 규정과 coverage 에 등재했다. 아웃바운드 프록시가 사이트 자기 자신으로 가는 내부 요청까지 가로채 저장이 수십 초씩 걸리던 문제도 함께 고쳤다. 실패가 폴백으로 삼켜져 화면에는 지연으로만 나타났다.
960 lines
43 KiB
Markdown
960 lines
43 KiB
Markdown
# Basic — 레이아웃
|
|
|
|
> 레이아웃 목록과 라우트 매핑 · 진입점: [AGENTS.md](../AGENTS.md)
|
|
|
|
## 레이아웃 목록
|
|
|
|
<!-- @generated:layouts START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
|
레이아웃 166개 (루트: `layouts`).
|
|
|
|
| 그룹 | 개수 |
|
|
|---|---|
|
|
| `(root)` | 2개 |
|
|
| `auth` | 5개 |
|
|
| `board` | 5개 |
|
|
| `errors` | 6개 |
|
|
| `mypage` | 11개 |
|
|
| `page` | 1개 |
|
|
| `partials` | 124개 |
|
|
| `search` | 1개 |
|
|
| `shop` | 9개 |
|
|
| `users` | 2개 |
|
|
|
|
| 레이아웃 | 그룹 | 종류 | extends |
|
|
|---|---|---|---|
|
|
| `_user_base` | `(root)` | partial | - |
|
|
| `forgot_password` | `auth` | 화면 | `_user_base` |
|
|
| `identity_challenge` | `auth` | 화면 | `_user_base` |
|
|
| `login` | `auth` | 화면 | `_user_base` |
|
|
| `register` | `auth` | 화면 | `_user_base` |
|
|
| `reset_password` | `auth` | 화면 | `_user_base` |
|
|
| `boards` | `board` | 화면 | `_user_base` |
|
|
| `form` | `board` | 화면 | `_user_base` |
|
|
| `index` | `board` | 화면 | `_user_base` |
|
|
| `popular` | `board` | 화면 | `_user_base` |
|
|
| `show` | `board` | 화면 | `_user_base` |
|
|
| `401` | `errors` | 화면 | `_user_base` |
|
|
| `403` | `errors` | 화면 | `_user_base` |
|
|
| `404` | `errors` | 화면 | `_user_base` |
|
|
| `500` | `errors` | 화면 | `_user_base` |
|
|
| `503` | `errors` | 화면 | `_user_base` |
|
|
| `maintenance` | `errors` | 화면 | `_user_base` |
|
|
| `home` | `(root)` | 화면 | `_user_base` |
|
|
| `addresses` | `mypage` | 화면 | `_user_base` |
|
|
| `board` | `mypage` | 화면 | `_user_base` |
|
|
| `change-password` | `mypage` | 화면 | `_user_base` |
|
|
| `inquiries` | `mypage` | 화면 | `_user_base` |
|
|
| `mileage` | `mypage` | 화면 | `_user_base` |
|
|
| `notifications` | `mypage` | 화면 | `_user_base` |
|
|
| `orders` | `mypage` | 화면 | `_user_base` |
|
|
| `show` | `mypage` | 화면 | `_user_base` |
|
|
| `profile-edit` | `mypage` | 화면 | `_user_base` |
|
|
| `profile` | `mypage` | 화면 | `_user_base` |
|
|
| `wishlist` | `mypage` | 화면 | `_user_base` |
|
|
| `show` | `page` | 화면 | `_user_base` |
|
|
| `_identity_challenge_modal` | `partials` | partial | - |
|
|
| `_modal_notification_delete_all_confirm` | `partials` | partial | - |
|
|
| `_login_form` | `partials` | partial | - |
|
|
| `_modal_privacy` | `partials` | partial | - |
|
|
| `_modal_terms` | `partials` | partial | - |
|
|
| `_redirect_if_logged_in` | `partials` | partial | - |
|
|
| `_register_form` | `partials` | partial | - |
|
|
| `_board_card` | `partials` | partial | - |
|
|
| `_parent_post` | `partials` | partial | - |
|
|
| `_password_verify_modal` | `partials` | partial | - |
|
|
| `_post_form` | `partials` | partial | - |
|
|
| `_type_renderer` | `partials` | partial | - |
|
|
| `_admin_links` | `partials` | partial | - |
|
|
| `_empty_states` | `partials` | partial | - |
|
|
| `_loading_error` | `partials` | partial | - |
|
|
| `_type_renderer` | `partials` | partial | - |
|
|
| `_write_button` | `partials` | partial | - |
|
|
| `_popular_item` | `partials` | partial | - |
|
|
| `_popular_list` | `partials` | partial | - |
|
|
| `_comment_input` | `partials` | partial | - |
|
|
| `_comment_item` | `partials` | partial | - |
|
|
| `_comment_section` | `partials` | partial | - |
|
|
| `_navigation` | `partials` | partial | - |
|
|
| `_post_attachments` | `partials` | partial | - |
|
|
| `_reply_section` | `partials` | partial | - |
|
|
| `_type_renderer` | `partials` | partial | - |
|
|
| `_modal_delete` | `partials` | partial | - |
|
|
| `_modal_report` | `partials` | partial | - |
|
|
| `_password_verify_modal` | `partials` | partial | - |
|
|
| `form` | `partials` | 화면 | - |
|
|
| `index` | `partials` | 화면 | - |
|
|
| `show` | `partials` | 화면 | - |
|
|
| `index` | `partials` | 화면 | - |
|
|
| `index` | `partials` | 화면 | - |
|
|
| `_currency_selector` | `partials` | partial | - |
|
|
| `_board_summary` | `partials` | partial | - |
|
|
| `_community_guide` | `partials` | partial | - |
|
|
| `_popular_boards` | `partials` | partial | - |
|
|
| `_recent_posts` | `partials` | partial | - |
|
|
| `_shop_promo` | `partials` | partial | - |
|
|
| `_stat_card_boards` | `partials` | partial | - |
|
|
| `_stat_card_comments` | `partials` | partial | - |
|
|
| `_stat_card_posts` | `partials` | partial | - |
|
|
| `_stat_card_users` | `partials` | partial | - |
|
|
| `_welcome_card` | `partials` | partial | - |
|
|
| `_tab_navigation` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_modal_address` | `partials` | partial | - |
|
|
| `_modal_confirm_delete` | `partials` | partial | - |
|
|
| `_modal_confirm_overwrite` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_my_comments` | `partials` | partial | - |
|
|
| `_my_posts` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_history` | `partials` | partial | - |
|
|
| `_items` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_modal_cancel` | `partials` | partial | - |
|
|
| `_modal_change_address` | `partials` | partial | - |
|
|
| `_modal_confirm_purchase` | `partials` | partial | - |
|
|
| `_modal_write_review` | `partials` | partial | - |
|
|
| `_orderer` | `partials` | partial | - |
|
|
| `_payment` | `partials` | partial | - |
|
|
| `_shipping` | `partials` | partial | - |
|
|
| `_status_header` | `partials` | partial | - |
|
|
| `_edit` | `partials` | partial | - |
|
|
| `_modal_withdraw` | `partials` | partial | - |
|
|
| `_password_verify_section` | `partials` | partial | - |
|
|
| `_view` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_search_filters` | `partials` | partial | - |
|
|
| `_search_input` | `partials` | partial | - |
|
|
| `_search_results` | `partials` | partial | - |
|
|
| `_search_states` | `partials` | partial | - |
|
|
| `_search_tabs` | `partials` | partial | - |
|
|
| `_section` | `partials` | partial | - |
|
|
| `_list` | `partials` | partial | - |
|
|
| `_section` | `partials` | partial | - |
|
|
| `_item_card` | `partials` | partial | - |
|
|
| `_item_list` | `partials` | partial | - |
|
|
| `_section` | `partials` | partial | - |
|
|
| `_cart_item` | `partials` | partial | - |
|
|
| `_cart_list` | `partials` | partial | - |
|
|
| `_cart_summary` | `partials` | partial | - |
|
|
| `_checkout_discount` | `partials` | partial | - |
|
|
| `_checkout_items` | `partials` | partial | - |
|
|
| `_checkout_mileage` | `partials` | partial | - |
|
|
| `_checkout_orderer` | `partials` | partial | - |
|
|
| `_checkout_payment` | `partials` | partial | - |
|
|
| `_checkout_shipping` | `partials` | partial | - |
|
|
| `_checkout_summary` | `partials` | partial | - |
|
|
| `_modal_address_manage` | `partials` | partial | - |
|
|
| `_modal_cart_delete_confirm` | `partials` | partial | - |
|
|
| `_modal_cart_option_change` | `partials` | partial | - |
|
|
| `_modal_cart_unavailable` | `partials` | partial | - |
|
|
| `_modal_coupon_download` | `partials` | partial | - |
|
|
| `_modal_exclusive_coupon_confirm` | `partials` | partial | - |
|
|
| `_modal_temp_order_not_found` | `partials` | partial | - |
|
|
| `_product_purchase_card` | `partials` | partial | - |
|
|
| `_admin_edit_link` | `partials` | partial | - |
|
|
| `_header` | `partials` | partial | - |
|
|
| `_info_summary` | `partials` | partial | - |
|
|
| `_modal_cart_added` | `partials` | partial | - |
|
|
| `_modal_coupon_download_confirm` | `partials` | partial | - |
|
|
| `_modal_inquiry_delete` | `partials` | partial | - |
|
|
| `_modal_login_required` | `partials` | partial | - |
|
|
| `_modal_qna_reply` | `partials` | partial | - |
|
|
| `_modal_qna_write` | `partials` | partial | - |
|
|
| `_modal_review_image` | `partials` | partial | - |
|
|
| `_price_mobile` | `partials` | partial | - |
|
|
| `_purchase_card` | `partials` | partial | - |
|
|
| `_review_avatar` | `partials` | partial | - |
|
|
| `_tab_detail` | `partials` | partial | - |
|
|
| `_tab_qna` | `partials` | partial | - |
|
|
| `_tab_reviews` | `partials` | partial | - |
|
|
| `_category_breadcrumb` | `partials` | partial | - |
|
|
| `_category_filter` | `partials` | partial | - |
|
|
| `_new_products` | `partials` | partial | - |
|
|
| `_popular_products` | `partials` | partial | - |
|
|
| `_product_grid` | `partials` | partial | - |
|
|
| `_recent_products` | `partials` | partial | - |
|
|
| `_search_filter_bar` | `partials` | partial | - |
|
|
| `index` | `search` | 화면 | `_user_base` |
|
|
| `cart` | `shop` | 화면 | `_user_base` |
|
|
| `category` | `shop` | 화면 | `_user_base` |
|
|
| `checkout` | `shop` | 화면 | `_user_base` |
|
|
| `guest_order_form` | `shop` | 화면 | `_user_base` |
|
|
| `guest_order_show` | `shop` | 화면 | `_user_base` |
|
|
| `index` | `shop` | 화면 | `_user_base` |
|
|
| `order_complete` | `shop` | 화면 | `_user_base` |
|
|
| `reorder` | `shop` | 화면 | `_user_base` |
|
|
| `show` | `shop` | 화면 | `_user_base` |
|
|
| `posts` | `users` | 화면 | `_user_base` |
|
|
| `show` | `users` | 화면 | `_user_base` |
|
|
<!-- @generated:layouts END -->
|
|
|
|
<!-- @intent START -->
|
|
166개 중 **124개가 partial** 입니다(화면 42). 이 비율이 이 템플릿의 구조를 그대로 보여줍니다 —
|
|
화면 하나가 여러 조각으로 나뉘어 있고, 조각은 여러 화면이 공유합니다. 화면 하나를 고칠 때는
|
|
그 화면 이름의 partials 디렉토리를 함께 열어야 전체가 보입니다.
|
|
|
|
그룹은 방문자 여정과 1:1 입니다 — `auth`(로그인·가입·비밀번호·본인인증) · `board`(게시판) ·
|
|
`shop`(상점) · `mypage`(마이페이지) · `page`(단일 문서) · `search` · `users` · `errors`.
|
|
`(root)` 둘은 `_user_base`(모든 화면의 베이스)와 `home` 입니다.
|
|
|
|
**모든 화면이 `_user_base` 를 상속합니다.** 헤더·푸터·모바일 네비·토스트·모달 호스트가 거기
|
|
있으므로, `extends` 없이 독립 레이아웃을 새로 만들면 그 화면에서는 `toast` 나 `openModal` 이
|
|
성공으로 기록되지만 **화면에는 아무것도 나타나지 않습니다**(호스트 컴포넌트가 마운트되지 않아서).
|
|
새 화면은 특별한 이유가 없는 한 `_user_base` 를 상속합니다.
|
|
|
|
`errors/` 6종(401·403·404·500·503·maintenance)은 코어가 오류 상황에서 직접 부릅니다. 401 은
|
|
**로그인 리다이렉트를 여기서 구현하지 않습니다** — 코어 `TemplateApp.showRouteError` 가드가
|
|
처리하므로, 이 레이아웃에서 다시 이동시키면 이중 리다이렉트가 됩니다.
|
|
<!-- @intent END -->
|
|
|
|
## 라우트 매핑
|
|
|
|
<!-- @generated:layout-map START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
|
| 경로 | 레이아웃 | 이름 |
|
|
|---|---|---|
|
|
| `/` | `home` | - |
|
|
| `/login` | `auth/login` | - |
|
|
| `/register` | `auth/register` | - |
|
|
| `/forgot-password` | `auth/forgot_password` | - |
|
|
| `/reset-password` | `auth/reset_password` | - |
|
|
| `/identity/challenge` | `auth/identity_challenge` | - |
|
|
| `/boards` | `board/boards` | - |
|
|
| `/boards/popular` | `board/popular` | - |
|
|
| `/board/:slug` | `board/index` | - |
|
|
| `/board/:slug/write` | `board/form` | - |
|
|
| `/board/:slug/:id` | `board/show` | - |
|
|
| `/board/:slug/:id/edit` | `board/form` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/products` | `shop/index` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/category/:slug` | `shop/category` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/products/:product_code` | `shop/show` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/cart` | `shop/cart` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/checkout` | `shop/checkout` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/orders/:id/complete` | `shop/order_complete` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/guest/orders` | `shop/guest_order_form` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/guest/orders/:order_number` | `shop/guest_order_show` | - |
|
|
| `/{{_global.modules?.['sirsoft-ecommerce']?.basic_info?.no_route ? '' : (_global.modules?.['sirsoft-ecommerce']?.basic_info?.route_path ?? 'shop')}}/reorder/:id` | `shop/reorder` | - |
|
|
| `/mypage` | `-` | - |
|
|
| `/mypage/profile` | `mypage/profile` | - |
|
|
| `/mypage/profile/edit` | `mypage/profile-edit` | - |
|
|
| `/mypage/change-password` | `mypage/change-password` | - |
|
|
| `/mypage/orders` | `mypage/orders` | - |
|
|
| `/mypage/orders/:order_number` | `mypage/orders/show` | - |
|
|
| `/mypage/mileage` | `mypage/mileage` | - |
|
|
| `/mypage/wishlist` | `mypage/wishlist` | - |
|
|
| `/mypage/addresses` | `mypage/addresses` | - |
|
|
| `/mypage/notifications` | `mypage/notifications` | - |
|
|
| `/mypage/board` | `mypage/board` | - |
|
|
| `/mypage/inquiries` | `mypage/inquiries` | - |
|
|
| `/users/:userId` | `users/show` | - |
|
|
| `/users/:userId/posts` | `users/posts` | - |
|
|
| `/page/:slug` | `page/show` | - |
|
|
| `/search` | `search/index` | - |
|
|
| `/404` | `errors/404` | - |
|
|
| `/403` | `errors/403` | - |
|
|
| `/500` | `errors/500` | - |
|
|
<!-- @generated:layout-map END -->
|
|
|
|
<!-- @intent START -->
|
|
상점 경로 9개만 **표현식**입니다. 운영자가 이커머스 환경설정에서 상점 경로(`route_path`,
|
|
기본 `shop`)를 바꾸거나 아예 루트로 두면(`no_route`), 그 설정이 라우트 문자열에 그대로
|
|
반영됩니다 — `routes.json` 이 정적 파일이 아니라 **표현식을 담을 수 있다**는 것이 이 템플릿의
|
|
전제입니다.
|
|
|
|
그래서 상점 라우트를 손볼 때는 세 경우를 함께 생각해야 합니다: 기본(`/shop/...`) · 운영자
|
|
지정 경로(`/store/...`) · 루트 배치(`/products`). 문자열을 하드코딩하면 뒤의 둘이 조용히
|
|
깨집니다.
|
|
|
|
`/mypage` 자체는 레이아웃이 `-` 입니다 — 진입하면 하위 탭 중 하나로 넘기는 자리이며 자기
|
|
화면을 갖지 않습니다.
|
|
|
|
라우트를 바꾸면 `php artisan template:update sirsoft-basic --force` 로 반영합니다. 빌드는
|
|
필요 없습니다.
|
|
<!-- @intent END -->
|
|
|
|
## 확장 오버라이드
|
|
|
|
<!-- @generated:template-overrides START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
|
|
| 대상 | 설명 |
|
|
|---|---|
|
|
| `extensions/sirsoft-daum_postcode/user-address-search.json` | 모듈/플러그인 확장 조각을 대체하는 오버라이드 |
|
|
<!-- @generated:template-overrides END -->
|
|
|
|
<!-- @intent START -->
|
|
오버라이드는 **확장이 제공한 조각을 이 템플릿의 것으로 갈아 끼우는** 장치입니다. 지금은 하나
|
|
있습니다 — `sirsoft-daum_postcode` 의 주소 검색 조각.
|
|
|
|
플러그인이 제공하는 원본 조각은 이커머스 관리자 화면에 맞춰져 있어, 방문자 화면의 배송지 입력
|
|
디자인과 어긋납니다. 조각을 고치는 대신 **템플릿이 자기 버전을 얹는** 것이 이 방향입니다 —
|
|
플러그인을 업데이트해도 이 오버라이드는 남고, 다른 템플릿은 원본을 그대로 씁니다.
|
|
|
|
그 대가로 **원본이 바뀌면 이 사본은 따라가지 않습니다.** 플러그인이 조각의 동작(핸들러 이름·
|
|
필드 계약)을 바꾸면 오버라이드만 옛 계약을 붙들고 있게 되고, 증상은 "주소 검색 버튼이
|
|
무반응" 으로만 나타납니다. 해당 플러그인을 업그레이드한 뒤에는 이 오버라이드를 함께 확인합니다.
|
|
<!-- @intent END -->
|
|
|
|
## 이관 원문 상세
|
|
|
|
> 아래는 코어 `docs/frontend/templates/sirsoft-basic/layouts.md` 에 있던 원문을 이 문서로 옮긴 것입니다(#601). 이관 시점 그대로
|
|
> 보존하되, **코드가 SSoT 인 값과 어긋나는 부분에는 정정 주석**을 달았습니다.
|
|
|
|
### sirsoft-basic 레이아웃
|
|
|
|
> **템플릿 식별자**: `sirsoft-basic` (type: user)
|
|
> **관련 문서**: [컴포넌트](components.md) | [핸들러](handlers.md) | [레이아웃 JSON 스키마](../../../../docs/frontend/layout-json.md)
|
|
|
|
> **정정(#601)**: 아래 페이지 맵·개수는 이관 시점 값입니다. 실측 총계와 그룹별 분포는 위
|
|
> 「레이아웃 목록」 블록이 SSoT 이며, 이관 이후 추가·삭제된 레이아웃이 있을 수 있습니다.
|
|
|
|
---
|
|
|
|
### TL;DR (5초 요약)
|
|
|
|
```text
|
|
1. 베이스: _user_base.json (헤더 + 푸터 + 모바일 네비 + 콘텐츠 슬롯)
|
|
2. 코어 페이지 30개+: 인증, 게시판, 쇼핑몰, 마이페이지, 검색, 프로필
|
|
3. Partial 100개+: 폼, 모달, 탭, 카드, 목록, 필터, 타입별 렌더러
|
|
4. 에러 페이지 6개: 401, 403, 404, 500, 503, maintenance
|
|
5. 패턴: 게시판(타입별 렌더러), 쇼핑몰(장바구니/결제 플로우), 마이페이지(탭 네비게이션)
|
|
```
|
|
|
|
---
|
|
|
|
### 목차
|
|
|
|
1. [페이지 맵 (트리 구조)](#페이지-맵-트리-구조)
|
|
2. [카테고리별 가이드](#카테고리별-가이드)
|
|
- [인증 페이지 패턴](#인증-페이지-패턴)
|
|
- [게시판 패턴](#게시판-패턴)
|
|
- [쇼핑몰 패턴](#쇼핑몰-패턴)
|
|
- [마이페이지 패턴](#마이페이지-패턴)
|
|
- [검색 패턴](#검색-패턴)
|
|
- [기타 페이지 패턴](#기타-페이지-패턴)
|
|
- [에러 페이지 패턴](#에러-페이지-패턴)
|
|
|
|
---
|
|
|
|
### 페이지 맵 (트리 구조)
|
|
|
|
```text
|
|
_user_base.json (베이스 레이아웃)
|
|
│
|
|
├── home.json (홈/대시보드)
|
|
│ └── partials/home/
|
|
│ ├── _welcome_card.json (환영 카드)
|
|
│ ├── _stat_card_users.json (통계: 사용자)
|
|
│ ├── _stat_card_posts.json (통계: 게시글)
|
|
│ ├── _stat_card_comments.json (통계: 댓글)
|
|
│ ├── _stat_card_boards.json (통계: 게시판)
|
|
│ ├── _recent_posts.json (최근 게시글)
|
|
│ ├── _popular_boards.json (인기 게시판)
|
|
│ ├── _board_summary.json (게시판 요약)
|
|
│ ├── _community_guide.json (커뮤니티 가이드)
|
|
│ └── _shop_promo.json (쇼핑 프로모션)
|
|
│
|
|
├── 인증 (auth/)
|
|
│ ├── login.json (로그인)
|
|
│ ├── register.json (회원가입)
|
|
│ ├── forgot_password.json (비밀번호 찾기)
|
|
│ ├── reset_password.json (비밀번호 재설정)
|
|
│ └── partials/auth/
|
|
│ ├── _login_form.json (로그인 폼)
|
|
│ ├── _register_form.json (회원가입 폼)
|
|
│ ├── _redirect_if_logged_in.json (로그인 상태 리다이렉트)
|
|
│ ├── _modal_terms.json (이용약관 모달)
|
|
│ └── _modal_privacy.json (개인정보 처리방침 모달)
|
|
│
|
|
├── 게시판 (board/)
|
|
│ ├── boards.json (게시판 목록)
|
|
│ │ └── partials/board/boards/
|
|
│ │ └── _board_card.json (게시판 카드)
|
|
│ ├── index.json (게시글 목록)
|
|
│ │ └── partials/board/index/
|
|
│ │ ├── _type_renderer.json (타입별 렌더러 분기)
|
|
│ │ ├── _empty_states.json (빈 상태)
|
|
│ │ ├── _loading_error.json (로딩/에러)
|
|
│ │ └── _write_button.json (글쓰기 버튼)
|
|
│ ├── show.json (게시글 상세)
|
|
│ │ └── partials/board/show/
|
|
│ │ ├── _type_renderer.json (타입별 렌더러 분기)
|
|
│ │ ├── _comment_section.json (댓글 섹션)
|
|
│ │ ├── _comment_item.json (댓글 아이템)
|
|
│ │ ├── _comment_input.json (댓글 입력)
|
|
│ │ ├── _reply_section.json (답글 섹션)
|
|
│ │ ├── _navigation.json (이전글/다음글)
|
|
│ │ ├── _post_attachments.json (첨부파일)
|
|
│ │ └── modals/
|
|
│ │ ├── _modal_delete.json (삭제 확인)
|
|
│ │ ├── _modal_report.json (신고)
|
|
│ │ └── _password_verify_modal.json (비밀번호 확인)
|
|
│ ├── form.json (게시글 작성/수정)
|
|
│ │ └── partials/board/form/
|
|
│ │ ├── _post_form.json (게시글 폼)
|
|
│ │ ├── _parent_post.json (원글 표시)
|
|
│ │ ├── _type_renderer.json (타입별 렌더러 분기)
|
|
│ │ └── _password_verify_modal.json (비밀번호 확인)
|
|
│ ├── popular.json (인기글)
|
|
│ │ └── partials/board/popular/
|
|
│ │ ├── _popular_list.json (인기글 리스트)
|
|
│ │ └── _popular_item.json (인기글 아이템)
|
|
│ └── partials/board/types/ (게시판 타입별 부분 레이아웃)
|
|
│ ├── basic/
|
|
│ │ ├── index.json (기본 목록)
|
|
│ │ ├── show.json (기본 상세)
|
|
│ │ └── form.json (기본 폼)
|
|
│ ├── card/
|
|
│ │ └── index.json (카드형 목록)
|
|
│ └── gallery/
|
|
│ └── index.json (갤러리형 목록)
|
|
│
|
|
├── 쇼핑몰 (shop/)
|
|
│ ├── index.json (상품 목록/메인)
|
|
│ │ └── partials/shop/list/
|
|
│ │ ├── _product_grid.json (상품 그리드)
|
|
│ │ ├── _popular_products.json (인기 상품)
|
|
│ │ ├── _new_products.json (신상품)
|
|
│ │ ├── _recent_products.json (최근 본 상품)
|
|
│ │ ├── _category_filter.json (카테고리 필터)
|
|
│ │ ├── _category_breadcrumb.json (카테고리 경로)
|
|
│ │ └── _search_filter_bar.json (검색/필터 바)
|
|
│ ├── category.json (카테고리 상품)
|
|
│ ├── show.json (상품 상세)
|
|
│ │ └── partials/shop/detail/
|
|
│ │ ├── _header.json (상품 헤더)
|
|
│ │ ├── _info_summary.json (상품 정보 요약)
|
|
│ │ ├── _purchase_card.json (구매 카드)
|
|
│ │ ├── _price_mobile.json (모바일 가격)
|
|
│ │ ├── _tab_detail.json (상세 탭)
|
|
│ │ ├── _tab_reviews.json (리뷰 탭)
|
|
│ │ ├── _tab_qna.json (Q&A 탭)
|
|
│ │ ├── _review_avatar.json (리뷰 아바타)
|
|
│ │ ├── _modal_cart_added.json (장바구니 추가 확인)
|
|
│ │ ├── _modal_coupon_download_confirm.json (쿠폰 다운로드 확인)
|
|
│ │ ├── _modal_login_required.json (로그인 필요)
|
|
│ │ └── _modal_review_image.json (리뷰 이미지 확대)
|
|
│ ├── cart.json (장바구니)
|
|
│ │ └── partials/shop/
|
|
│ │ ├── _cart_list.json (장바구니 목록)
|
|
│ │ ├── _cart_item.json (장바구니 아이템)
|
|
│ │ ├── _cart_summary.json (장바구니 요약)
|
|
│ │ ├── _product_purchase_card.json (구매 카드)
|
|
│ │ ├── _modal_cart_delete_confirm.json (삭제 확인)
|
|
│ │ ├── _modal_cart_option_change.json (옵션 변경)
|
|
│ │ ├── _modal_cart_unavailable.json (품절 안내)
|
|
│ │ └── _modal_temp_order_not_found.json (임시주문 없음)
|
|
│ ├── checkout.json (결제)
|
|
│ │ └── partials/shop/
|
|
│ │ ├── _checkout_orderer.json (주문자 정보)
|
|
│ │ ├── _checkout_shipping.json (배송 정보)
|
|
│ │ ├── _checkout_items.json (주문 상품)
|
|
│ │ ├── _checkout_discount.json (할인/쿠폰)
|
|
│ │ ├── _checkout_mileage.json (마일리지)
|
|
│ │ ├── _checkout_payment.json (결제 수단)
|
|
│ │ ├── _checkout_summary.json (주문 요약)
|
|
│ │ ├── _modal_address_manage.json (배송지 관리)
|
|
│ │ ├── _modal_coupon_download.json (쿠폰 다운로드)
|
|
│ │ └── _modal_exclusive_coupon_confirm.json (전용 쿠폰 확인)
|
|
│ └── order_complete.json (주문 완료)
|
|
│
|
|
├── 마이페이지 (mypage/)
|
|
│ ├── profile.json (프로필 보기)
|
|
│ ├── profile-edit.json (프로필 수정)
|
|
│ │ └── partials/mypage/profile/
|
|
│ │ ├── _view.json (프로필 보기 섹션)
|
|
│ │ ├── _edit.json (프로필 수정 섹션)
|
|
│ │ ├── _password_verify_section.json (비밀번호 확인)
|
|
│ │ └── _modal_withdraw.json (회원 탈퇴)
|
|
│ ├── orders.json (주문 내역)
|
|
│ │ └── partials/mypage/orders/
|
|
│ │ └── _list.json (주문 목록)
|
|
│ ├── orders/show.json (주문 상세)
|
|
│ │ └── partials/mypage/orders/
|
|
│ │ ├── _status_header.json (주문 상태 헤더)
|
|
│ │ ├── _items.json (주문 상품)
|
|
│ │ ├── _shipping.json (배송 정보)
|
|
│ │ ├── _payment.json (결제 정보)
|
|
│ │ ├── _history.json (주문 이력)
|
|
│ │ ├── _modal_cancel.json (주문 취소)
|
|
│ │ └── _modal_change_address.json (배송지 변경)
|
|
│ ├── board.json (내 게시글/댓글)
|
|
│ │ └── partials/mypage/board/
|
|
│ │ ├── _list.json (탭 목록)
|
|
│ │ ├── _my_posts.json (내 게시글)
|
|
│ │ └── _my_comments.json (내 댓글)
|
|
│ ├── addresses.json (배송지 관리)
|
|
│ │ └── partials/mypage/addresses/
|
|
│ │ ├── _list.json (배송지 목록)
|
|
│ │ ├── _modal_address.json (배송지 추가/수정)
|
|
│ │ ├── _modal_confirm_delete.json (삭제 확인)
|
|
│ │ └── _modal_confirm_overwrite.json (덮어쓰기 확인)
|
|
│ ├── notifications.json (알림)
|
|
│ │ └── partials/mypage/notifications/
|
|
│ │ └── _list.json (알림 목록)
|
|
│ ├── change-password.json (비밀번호 변경)
|
|
│ ├── wishlist.json (위시리스트)
|
|
│ │ └── partials/mypage/wishlist/
|
|
│ │ └── _list.json (위시리스트 목록)
|
|
│ ├── inquiries.json (상품 1:1 문의내역)
|
|
│ │ └── partials/mypage/inquiries/
|
|
│ │ └── _list.json (문의 목록)
|
|
│ └── partials/mypage/
|
|
│ └── _tab_navigation.json (마이페이지 공통 탭)
|
|
│ hiddenTabIds: inquiries 탭은 sirsoft-ecommerce inquiry 설정 시에만 노출
|
|
│
|
|
├── 검색 (search/)
|
|
│ └── index.json (통합 검색)
|
|
│ └── partials/search/
|
|
│ ├── _search_input.json (검색 입력)
|
|
│ ├── _search_tabs.json (검색 탭)
|
|
│ ├── _search_filters.json (검색 필터)
|
|
│ ├── _search_results.json (검색 결과)
|
|
│ ├── _search_states.json (검색 상태)
|
|
│ ├── posts/
|
|
│ │ ├── _section.json (게시글 결과 섹션)
|
|
│ │ └── _list.json (게시글 결과 목록)
|
|
│ ├── products/
|
|
│ │ ├── _section.json (상품 결과 섹션)
|
|
│ │ ├── _item_card.json (상품 카드)
|
|
│ │ └── _item_list.json (상품 리스트)
|
|
│ └── pages/
|
|
│ └── _section.json (페이지 결과 섹션)
|
|
│
|
|
├── 사용자 (users/)
|
|
│ ├── show.json (사용자 프로필)
|
|
│ └── posts.json (사용자 게시글)
|
|
│
|
|
├── 페이지 (page/)
|
|
│ └── show.json (정적 페이지)
|
|
│
|
|
├── 공통 Partial
|
|
│ └── partials/common/
|
|
│ └── _currency_selector.json (통화 선택기)
|
|
│
|
|
└── 에러 페이지 (errors/)
|
|
├── 401.json (인증 필요)
|
|
├── 403.json (접근 거부)
|
|
├── 404.json (페이지 없음)
|
|
├── 500.json (서버 오류)
|
|
├── 503.json (서비스 불가)
|
|
└── maintenance.json (점검 중)
|
|
```
|
|
|
|
---
|
|
|
|
### 카테고리별 가이드
|
|
|
|
#### 인증 페이지 패턴
|
|
|
|
**대표**: `auth/login.json`, `auth/register.json`
|
|
|
|
**구성**:
|
|
```text
|
|
extends: _user_base
|
|
slots.content:
|
|
└── Flex (중앙 정렬)
|
|
├── _redirect_if_logged_in.json (로그인 시 리다이렉트)
|
|
└── Div (카드 스타일 래퍼)
|
|
├── H2 (제목)
|
|
├── partial: _login_form.json 또는 _register_form.json
|
|
└── 하단 링크 (회원가입/로그인 전환, 비밀번호 찾기)
|
|
```
|
|
|
|
**특수사항**:
|
|
- `_redirect_if_logged_in.json`: 이미 로그인한 사용자를 홈으로 리다이렉트하는 partial
|
|
- 로그인 폼은 `SocialLoginButtons` 포함 (소셜 로그인 지원)
|
|
- 회원가입 폼은 `PasswordInput` 사용 (비밀번호 규칙 검증, 확인 일치)
|
|
- 약관 동의 모달: `_modal_terms.json`, `_modal_privacy.json`
|
|
- `blur_until_loaded`: 현재 사용자 정보 로드 전까지 폼 블러 처리
|
|
|
|
**핸들러 패턴**:
|
|
- `apiCall` — 로그인 (POST `/api/auth/login`), 회원가입 (POST `/api/auth/register`)
|
|
- `navigate` — 성공 후 홈/대시보드로 이동
|
|
- `setState` — 폼 에러, 로딩 상태
|
|
|
|
---
|
|
|
|
#### 게시판 패턴
|
|
|
|
**대표**: `board/index.json`, `board/show.json`, `board/form.json`
|
|
|
|
**구성**:
|
|
```text
|
|
extends: _user_base
|
|
data_sources: [posts (게시글 목록/상세)]
|
|
slots.content:
|
|
└── Container
|
|
├── 타입 렌더러 (_type_renderer.json)
|
|
│ → 게시판 타입에 따라 분기:
|
|
│ ├── basic/index.json (기본 테이블형)
|
|
│ ├── card/index.json (카드형)
|
|
│ └── gallery/index.json (갤러리형)
|
|
├── Pagination
|
|
└── 글쓰기 버튼 (_write_button.json)
|
|
```
|
|
|
|
**data_sources**:
|
|
```json
|
|
{
|
|
"id": "posts",
|
|
"endpoint": "/api/modules/sirsoft-board/boards/{{route.slug}}/posts",
|
|
"method": "GET",
|
|
"params": {
|
|
"page": "{{query.page ?? 1}}",
|
|
"search": "{{query.search ?? ''}}",
|
|
"category": "{{query.category ?? ''}}"
|
|
},
|
|
"auto_fetch": true,
|
|
"refetchOnMount": true,
|
|
"errorHandling": {
|
|
"403": { "handler": "showErrorPage", "params": { "target": "content" } }
|
|
}
|
|
}
|
|
```
|
|
|
|
**타입별 렌더러 시스템**:
|
|
게시판은 `board_type` 필드에 따라 다른 레이아웃을 렌더링합니다:
|
|
- `basic` — 테이블형 게시글 목록 (기본)
|
|
- `card` — 카드 그리드형 목록
|
|
- `gallery` — 이미지 갤러리형 목록
|
|
|
|
```text
|
|
_type_renderer.json (조건 분기):
|
|
├── if: board_type === 'basic' → partial: types/basic/index.json
|
|
├── if: board_type === 'card' → partial: types/card/index.json
|
|
└── if: board_type === 'gallery' → partial: types/gallery/index.json
|
|
```
|
|
|
|
**게시글 상세 구성**:
|
|
```text
|
|
show.json:
|
|
└── _type_renderer.json → types/basic/show.json
|
|
├── 게시글 본문 (HtmlContent)
|
|
├── _post_attachments.json (첨부파일)
|
|
├── PostReactions (리액션)
|
|
├── _comment_section.json
|
|
│ ├── _comment_input.json (댓글 입력)
|
|
│ └── _comment_item.json (댓글 아이템, iteration)
|
|
│ └── _reply_section.json (답글)
|
|
├── _navigation.json (이전/다음 글)
|
|
└── modals: 삭제, 신고, 비밀번호 확인
|
|
```
|
|
|
|
**게시글 작성/수정 구성**:
|
|
```text
|
|
form.json:
|
|
├── _parent_post.json (답글 시 원글 표시)
|
|
└── _post_form.json
|
|
├── Input (제목)
|
|
├── Select (카테고리)
|
|
├── HtmlEditor (본문)
|
|
├── FileUploader (첨부파일)
|
|
├── Checkbox (공지, 비밀글 등)
|
|
└── Button (저장/취소)
|
|
```
|
|
|
|
**핸들러 패턴**:
|
|
- `apiCall` — CRUD, 댓글 작성/삭제, 리액션, 신고
|
|
- `navigate` — 게시판 이동, 글 상세/목록 이동
|
|
- `setState` — 검색, 필터, 페이지네이션, 댓글 접기/펼치기
|
|
- `replaceUrl` — 페이지네이션 시 URL만 변경
|
|
|
|
---
|
|
|
|
#### 쇼핑몰 패턴
|
|
|
|
**대표**: `shop/index.json`, `shop/show.json`, `shop/cart.json`, `shop/checkout.json`
|
|
|
|
##### 상품 목록 (shop/index.json)
|
|
|
|
```text
|
|
extends: _user_base
|
|
data_sources: [products (상품 API), categories (카테고리)]
|
|
slots.content:
|
|
└── Container
|
|
├── _category_breadcrumb.json (경로)
|
|
├── _search_filter_bar.json (검색/정렬)
|
|
├── _category_filter.json (카테고리 사이드)
|
|
├── _product_grid.json (상품 그리드, ProductCard iteration)
|
|
├── _popular_products.json (인기 상품)
|
|
├── _new_products.json (신상품)
|
|
├── _recent_products.json (최근 본 상품)
|
|
└── Pagination
|
|
```
|
|
|
|
##### 상품 상세 (shop/show.json)
|
|
|
|
```text
|
|
extends: _user_base
|
|
init_actions: [최근 본 상품 localStorage 저장]
|
|
data_sources: [product, reviews, popularProducts, relatedProducts]
|
|
computed: [displayPrice, displayListPrice, ...]
|
|
slots.content:
|
|
└── ThreeColumnLayout
|
|
├── left: _header.json (이미지 뷰어 — ProductImageViewer)
|
|
├── center: _info_summary.json (상품 정보, 옵션 선택)
|
|
└── right: _purchase_card.json (가격, 수량, 구매 버튼)
|
|
└── _price_mobile.json (모바일 가격 — responsive portable)
|
|
└── TabNavigation
|
|
├── _tab_detail.json (상세 정보 — HtmlContent)
|
|
├── _tab_reviews.json (리뷰 — iteration)
|
|
└── _tab_qna.json (Q&A — iteration)
|
|
└── modals: 장바구니 추가, 쿠폰 다운로드, 로그인 필요, 리뷰 이미지
|
|
```
|
|
|
|
**SEO 특수사항**:
|
|
- `meta.seo.structured_data`: Product, AggregateRating 스키마
|
|
- `meta.seo.og`: 상품 이미지, 가격 정보 포함
|
|
|
|
**핸들러 패턴**:
|
|
- `sirsoft-basic.addSelectedItemIfComplete` — 옵션 선택 완료 시 자동 추가
|
|
- `sirsoft-basic.updateSelectedItemQuantity` — 수량 변경
|
|
- `sirsoft-basic.getDisplayPrice` — 다중 통화 가격 표시
|
|
- `apiCall` — 장바구니 추가, 리뷰 작성, 위시리스트 토글
|
|
- `saveToLocalStorage` — 최근 본 상품 저장
|
|
|
|
##### 장바구니 (shop/cart.json)
|
|
|
|
```text
|
|
extends: _user_base
|
|
state: { selectedItems, allSelected, isCalculating, optionModal, deleteModal, ... }
|
|
data_sources: [cartItems (POST /cart/query)]
|
|
slots.content:
|
|
└── Flex
|
|
├── _cart_list.json (장바구니 목록)
|
|
│ └── _cart_item.json (아이템, iteration)
|
|
│ ├── Checkbox (선택)
|
|
│ ├── 상품 정보/가격
|
|
│ ├── QuantitySelector
|
|
│ └── 삭제/옵션변경 버튼
|
|
└── _cart_summary.json (주문 요약, 결제 버튼)
|
|
└── modals: 삭제 확인, 옵션 변경, 품절 안내
|
|
```
|
|
|
|
**특수사항**:
|
|
- `auth_mode: "optional"`: 비회원도 장바구니 사용 가능 (X-Cart-Key 헤더)
|
|
- `state` 섹션으로 모달/선택 상태 관리
|
|
- 장바구니 핸들러: `toggleCartItemSelection`, `selectAllCartItems`, `recalculateCart`
|
|
|
|
##### 결제 (shop/checkout.json)
|
|
|
|
```text
|
|
extends: _user_base
|
|
data_sources: [tempOrder (임시 주문)]
|
|
slots.content:
|
|
└── Container
|
|
├── _checkout_orderer.json (주문자 정보)
|
|
├── _checkout_shipping.json (배송 정보)
|
|
├── _checkout_items.json (주문 상품 목록)
|
|
├── _checkout_discount.json (할인/쿠폰)
|
|
├── _checkout_mileage.json (마일리지)
|
|
├── _checkout_payment.json (결제 수단 선택)
|
|
└── _checkout_summary.json (최종 요약 + 결제 버튼)
|
|
└── modals: 배송지 관리, 쿠폰 다운로드, 전용 쿠폰 확인
|
|
```
|
|
|
|
**핸들러 패턴**:
|
|
- `apiCall` — 주문 생성, 배송지 조회/저장, 쿠폰 적용
|
|
- `setState` — 배송지 선택, 결제 수단, 쿠폰/마일리지 계산
|
|
|
|
---
|
|
|
|
#### 마이페이지 패턴
|
|
|
|
**대표**: `mypage/profile.json`, `mypage/orders.json`
|
|
|
|
**구성**:
|
|
```text
|
|
extends: _user_base
|
|
computed: { currentTab: "profile" }
|
|
data_sources: [user (인증 사용자 정보)]
|
|
transition_overlay: { target: "mypage_tab_content" }
|
|
slots.content:
|
|
└── Div
|
|
├── _tab_navigation.json (공통 마이페이지 탭)
|
|
│ → 프로필, 주문내역, 게시글, 배송지, 알림, 위시리스트
|
|
└── 탭별 콘텐츠 영역
|
|
```
|
|
|
|
**탭 구조**:
|
|
각 마이페이지 하위 페이지는 동일한 `_tab_navigation.json`을 공유하되, `computed.currentTab`으로 활성 탭을 구분합니다.
|
|
|
|
| 탭 | 레이아웃 | Partial |
|
|
|---|---------|---------|
|
|
| 프로필 | `profile.json` | `_view.json` |
|
|
| 프로필 수정 | `profile-edit.json` | `_edit.json`, `_password_verify_section.json`, `_modal_withdraw.json` |
|
|
| 주문 내역 | `orders.json` | `_list.json` |
|
|
| 주문 상세 | `orders/show.json` | `_status_header.json`, `_items.json`, `_shipping.json`, `_payment.json`, `_history.json` |
|
|
| 내 게시글 | `board.json` | `_my_posts.json`, `_my_comments.json` |
|
|
| 배송지 | `addresses.json` | `_list.json`, `_modal_address.json`, `_modal_confirm_delete.json` |
|
|
| 알림 | `notifications.json` | `_list.json` |
|
|
| 비밀번호 변경 | `change-password.json` | (인라인) |
|
|
| 위시리스트 | `wishlist.json` | `_list.json` |
|
|
| 상품 1:1 문의내역 | `inquiries.json` | `_list.json` (sirsoft-ecommerce 모듈 연동 시에만 탭 표시) |
|
|
|
|
**핸들러 패턴**:
|
|
- `apiCall` — 프로필 수정, 주문 취소, 배송지 CRUD, 알림 읽음 처리
|
|
- `navigate` — 탭 간 이동 (별도 페이지)
|
|
- `setState` — 폼 상태, 모달 제어
|
|
- `openModal`/`closeModal` — 탈퇴, 주소 편집, 삭제 확인
|
|
|
|
---
|
|
|
|
#### 검색 패턴
|
|
|
|
**대표**: `search/index.json`
|
|
|
|
**구성**:
|
|
```text
|
|
extends: _user_base
|
|
state: { q: "" }
|
|
init_actions: [setState — query 파라미터에서 검색어 추출]
|
|
global_state: { searchActiveTab, searchBoardFilter, searchSortBy, searchPage }
|
|
data_sources: [searchPosts, searchProducts, searchPages]
|
|
slots.content:
|
|
└── Container
|
|
├── _search_input.json (검색 입력 — SearchBar)
|
|
├── _search_tabs.json (탭: 전체/게시글/상품/페이지)
|
|
├── _search_filters.json (정렬, 게시판 필터)
|
|
├── _search_states.json (검색 전/로딩/결과없음 상태)
|
|
└── _search_results.json (결과 표시)
|
|
├── posts/_section.json → posts/_list.json
|
|
├── products/_section.json → products/_item_card.json / _item_list.json
|
|
└── pages/_section.json
|
|
```
|
|
|
|
**특수사항**:
|
|
- 통합 검색: 게시글 + 상품 + 페이지 동시 검색
|
|
- `searchActiveTab`으로 탭 전환 시 해당 카테고리만 표시
|
|
- 상품 결과는 카드/리스트 뷰 전환 지원
|
|
- `global_state`로 검색 상태 유지 (탭, 필터, 정렬, 페이지)
|
|
|
|
**핸들러 패턴**:
|
|
- `apiCall` — 검색 API (게시글, 상품, 페이지 별도)
|
|
- `setState` — 탭 전환, 필터, 정렬
|
|
- `replaceUrl` — 검색어/필터 변경 시 URL 갱신
|
|
|
|
---
|
|
|
|
#### 기타 페이지 패턴
|
|
|
|
##### 사용자 프로필 (users/show.json, users/posts.json)
|
|
|
|
```text
|
|
extends: _user_base
|
|
data_sources: [user (공개 프로필)]
|
|
slots.content:
|
|
└── Container
|
|
├── Avatar + 사용자 정보
|
|
└── 게시글 목록 (users/posts.json)
|
|
```
|
|
|
|
##### 정적 페이지 (page/show.json)
|
|
|
|
```text
|
|
extends: _user_base
|
|
data_sources: [page (페이지 콘텐츠)]
|
|
slots.content:
|
|
└── Container
|
|
└── HtmlContent (본문 렌더링)
|
|
```
|
|
|
|
##### 홈 (home.json)
|
|
|
|
```text
|
|
extends: _user_base
|
|
data_sources: [stats, recent_posts, popular_boards]
|
|
meta.seo: { structured_data: WebSite }
|
|
slots.content:
|
|
└── Container
|
|
├── _welcome_card.json (환영 배너)
|
|
├── Grid (통계 카드 4개)
|
|
│ ├── _stat_card_users.json
|
|
│ ├── _stat_card_posts.json
|
|
│ ├── _stat_card_comments.json
|
|
│ └── _stat_card_boards.json
|
|
├── _recent_posts.json (최근 게시글)
|
|
├── _popular_boards.json (인기 게시판)
|
|
├── _board_summary.json (게시판 요약)
|
|
├── _community_guide.json (가이드)
|
|
└── _shop_promo.json (쇼핑 프로모션)
|
|
```
|
|
|
|
---
|
|
|
|
#### 에러 페이지 패턴
|
|
|
|
**대표**: `errors/404.json`
|
|
|
|
**구성**:
|
|
```text
|
|
(extends 없음 — 독립 레이아웃)
|
|
components:
|
|
└── Div (전체 화면 중앙 정렬)
|
|
├── Icon (에러 아이콘)
|
|
├── H1 (에러 코드)
|
|
├── P (에러 메시지)
|
|
└── Button (홈으로 이동)
|
|
```
|
|
|
|
**핸들러 패턴**:
|
|
- `navigate` — 홈으로 이동
|
|
|
|
---
|
|
|
|
### 베이스 레이아웃 구조
|
|
|
|
#### _user_base.json
|
|
|
|
모든 사용자 페이지의 공통 구조를 정의합니다.
|
|
|
|
```text
|
|
_user_base.json
|
|
├── globalHeaders: [X-Cart-Key (이커머스·인증 API) / X-Guest-Order-Token (비회원 주문 후속 액션) / X-Board-Secret-View-Token (게시판 API)]
|
|
├── transition_overlay: { style: "skeleton", target: "main_content_area" }
|
|
├── init_actions: [initTheme, initCartKey, loadPreferredCurrency, setState(shopBase)]
|
|
├── data_sources:
|
|
│ ├── boards (게시판 메뉴 — progressive, fallback)
|
|
│ ├── current_user (인증 사용자 — auth_required, suppress 401)
|
|
│ └── cart (장바구니 카운트 — optional auth)
|
|
├── components:
|
|
│ ├── Toast (전역 알림)
|
|
│ ├── PageTransitionIndicator
|
|
│ ├── Div (min-h-screen flex flex-col)
|
|
│ │ ├── mobile_overlay (모바일 메뉴 오버레이 — responsive portable)
|
|
│ │ ├── MobileNav (모바일 네비게이션 드로어)
|
|
│ │ ├── Header (사이트 헤더 — 로고, 메뉴, 검색, 사용자)
|
|
│ │ ├── Div#main_content_area (콘텐츠 영역)
|
|
│ │ │ └── slot: "content" (← 하위 레이아웃이 채움)
|
|
│ │ └── Footer (사이트 푸터)
|
|
│ └── PageTransitionBlur (전환 블러)
|
|
```
|
|
|
|
**슬롯**:
|
|
- `content` — 각 페이지의 메인 콘텐츠가 삽입되는 위치
|
|
|
|
**특수사항**:
|
|
- `globalHeaders`: 패턴별 공통 헤더 자동 첨부
|
|
- `/api/modules/sirsoft-ecommerce/*` — `X-Cart-Key`, `X-Currency`, `X-Shipping-Country`
|
|
- `/api/modules/sirsoft-ecommerce/guest/orders/*` — `X-Guest-Order-Token` (비회원 주문 후속 액션)
|
|
- `/api/modules/sirsoft-board/*` — `X-Board-Secret-View-Token`
|
|
비밀번호를 입력해 비밀글 원문을 연 사실을 다음 요청으로 넘기는 값입니다. 서버는 이
|
|
사실을 검증 응답 하나에만 담고 있어서, 토큰을 싣지 않으면 원문을 연 사용자도 댓글·답글·
|
|
신고에서 전부 거부됩니다. 토큰은 게시글에 결속되므로 다른 글에는 통하지 않고, 비밀글이
|
|
아닌 요청에서는 서버가 아예 평가하지 않습니다. `board/types/basic/show.json` 의
|
|
비밀번호 검증 `onSuccess` 가 `_global.secretViewToken` 에 넣습니다.
|
|
- `transition_overlay.style: "skeleton"`: 페이지 전환 시 PageSkeleton 표시
|
|
- `responsive`: 모바일 오버레이/네비게이션은 `portable` breakpoint에서만 표시
|
|
- `auth_mode: "optional"`: 비회원도 장바구니 카운트 조회 가능
|
|
- `errorHandling.401.handler: "suppress"`: 비회원의 인증 API 401은 에러 전파 방지
|
|
|
|
---
|
|
|
|
### 관련 문서
|
|
|
|
- [sirsoft-basic 컴포넌트](components.md)
|
|
- [sirsoft-basic 핸들러](handlers.md)
|
|
- [sirsoft-admin_basic 레이아웃](../../sirsoft-admin_basic/docs/layouts.md)
|
|
- [레이아웃 JSON 스키마](../../../../docs/frontend/layout-json.md)
|
|
- [레이아웃 상속](../../../../docs/frontend/layout-json-inheritance.md)
|