시술후기 구현
This commit is contained in:
42
docs/작업내역_20260225.md
Normal file
42
docs/작업내역_20260225.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 메이드유 CRM 작업내역 — 2026년 2월 25일
|
||||
|
||||
### 1. 코드 관리 기능 추가
|
||||
- CRM에서 사용하는 **공통코드**(단위, 분류 등)를 조회·등록·수정·삭제할 수 있는 **코드 관리 화면**이 새로 추가되었습니다.
|
||||
|
||||
### 2. 진료유형 설정 화면 수정
|
||||
- 진료유형 설정의 각 단계(1~5뎁스) 팝업 화면이 수정되었습니다.
|
||||
- 진료유형 목록 화면의 기능이 개선되었습니다.
|
||||
|
||||
---
|
||||
|
||||
## 직접 작업한 내역
|
||||
|
||||
### 1. 사용약품 검색 및 등록 기능
|
||||
- 용량/출력(4뎁스) 카테고리에서 **사용 약품을 검색하여 등록**할 수 있는 기능을 새로 만들었습니다.
|
||||
- "약품 추가(검색)" 버튼을 누르면 **검색 팝업**이 열리고, 제품명으로 검색할 수 있습니다.
|
||||
- 검색 결과에서 **제품명을 클릭**하면 해당 약품이 바로 등록됩니다.
|
||||
|
||||
### 2. 사용약품 관리 표(그리드) 구성
|
||||
- 등록된 약품들이 아래 항목으로 구성된 표에 표시됩니다.
|
||||
|
||||
| 항목 | 설명 |
|
||||
|------|------|
|
||||
| 거래처 | 약품을 공급하는 거래처 이름 |
|
||||
| 약품명 | 등록된 약품 이름 |
|
||||
| 재고 | 현재 남아있는 재고 수량 |
|
||||
| 재고단위 | 재고의 단위 (ml, cc 등) |
|
||||
| **사용량** | 1회 시술 시 사용하는 양 (클릭하여 수정 가능) |
|
||||
| **사용단위** | 사용량의 단위 (클릭하여 선택 가능) |
|
||||
| 삭제 | 약품을 목록에서 제거 |
|
||||
|
||||
- **사용량**과 **사용단위**는 표에서 바로 클릭하여 수정할 수 있으며, 수정 즉시 자동 저장됩니다.
|
||||
- 사용단위는 드롭다운 목록에서 선택하는 방식이며, 기본값은 "선택"입니다.
|
||||
- 수정 가능한 칸은 연한 파란색 배경으로 표시되어 있습니다.
|
||||
|
||||
### 3. 신규 등록 화면에서도 약품관리 표시
|
||||
- 기존에는 수정할 때만 약품관리가 보였으나, 이제 **새로 등록하는 화면에서도** 약품관리 영역이 보입니다.
|
||||
- 단, 카테고리를 먼저 저장한 뒤에 약품을 추가할 수 있으며, 저장 전에는 안내 문구가 표시됩니다.
|
||||
|
||||
### 4. 약품 데이터 저장 구조 신규 생성
|
||||
- 시술별 사용 약품 정보를 저장하기 위한 **데이터베이스 테이블을 새로 생성**하였습니다.
|
||||
- 약품명, 거래처, 사용량, 단위, 단가 등의 정보가 저장됩니다.
|
||||
36
rules.md
Normal file
36
rules.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 프로젝트 코딩 가이드라인 (Java Backend)
|
||||
|
||||
AI 에디터(Agent)는 다음 규칙을 항상 준수하여 코드를 작성하고 수정해야 합니다.
|
||||
|
||||
## 0. 기본 소통 규칙 (Communication)
|
||||
- **언어**: 사용자에 대한 모든 답변과 코드 설명은 항상 **한글(Korean)**로만 작성해야 합니다.
|
||||
|
||||
## 1. 패키지 구성 (Package Structure)
|
||||
- **컨트롤러 (Controller)**: `ctrl`
|
||||
- **서비스 (Service)**: `svc`
|
||||
- **DTO (Data Transfer Object)**: `dto`
|
||||
- **매퍼 (Mapper)**: `mapper`
|
||||
|
||||
## 2. 파일 명명 규칙 및 구성 (File Naming Conventions)
|
||||
- **컨트롤러 (Controller)**: `[도메인명]Controller.java` (예: `ABCDController.java`)
|
||||
- **서비스 (Service)**: `[도메인명]Service.java` (인터페이스와 구현체(impl)를 분리하지 않고 Service 클래스 파일 하나로만 구현, 예: `ABCDService.java`)
|
||||
- **DTO**: `[도메인명]DTO.java` (예: `ABCDDTO.java`)
|
||||
- **매퍼 (Mapper)**: `[도메인명]Mapper.java` (예: `ABCDMapper.java`)
|
||||
|
||||
## 3. URL 및 메소드 명명 규칙 (RequestMapping & Method Naming)
|
||||
|
||||
### 1) RequestMapping (URL) 및 컨트롤러 메소드명
|
||||
- 페이지 이동하는 url : `moveXXXX.do`
|
||||
- 팝업 오픈하는 url : `openXXXX.do`
|
||||
- 조회 url : `getXXXX.do`
|
||||
- 저장 url : `putXXXX.do`
|
||||
- 수정 url : `modXXXX.do`
|
||||
- 삭제 url : `delXXXX.do`
|
||||
- **단, 컨트롤러 메소드명은 위 url에서 `.do`를 제외한 이름과 동일하게 명명합니다.**
|
||||
|
||||
### 2) 서비스 메소드명
|
||||
- 단일조회 : `selectXXXX`
|
||||
- 리스트조회 : `selectListXXXX`
|
||||
- insert : `insertXXXX`
|
||||
- update : `updateXXXX`
|
||||
- delete : `deleteXXXX`
|
||||
38
sql/create_mu_procedure_review.sql
Normal file
38
sql/create_mu_procedure_review.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- =====================================================
|
||||
-- MU_PROCEDURE_REVIEW : 시술후기 전용 테이블
|
||||
-- 기존 HP_BEFORE_AFTER_PHOTO_BBS 대신 사용
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE `MU_PROCEDURE_REVIEW` (
|
||||
`MU_PROCEDURE_REVIEW_ID` varchar(25) NOT NULL COMMENT '시술후기 식별자',
|
||||
`MU_MEMBER_ID` varchar(25) NOT NULL COMMENT '작성자 식별자',
|
||||
`CATEGORY_NO` int(11) DEFAULT NULL COMMENT '카테고리 번호',
|
||||
`TITLE` varchar(200) NOT NULL COMMENT '제목',
|
||||
`CONTENT` text NOT NULL COMMENT '내용 (HTML - Quill 에디터)',
|
||||
`HASHTAG` varchar(500) DEFAULT NULL COMMENT '해시태그',
|
||||
`BEFORE_PHOTO_ATTACHFILE_ID` varchar(30) DEFAULT NULL COMMENT 'Before 사진 첨부파일 식별자',
|
||||
`AFTER_PHOTO_ATTACHFILE_ID` varchar(30) DEFAULT NULL COMMENT 'After 사진 첨부파일 식별자',
|
||||
`VIEW_COUNT` int(11) NOT NULL DEFAULT 0 COMMENT '조회수',
|
||||
`USE_YN` char(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 (Y:사용, N:삭제)',
|
||||
`REG_ID` varchar(25) NOT NULL COMMENT '등록자',
|
||||
`REG_DATE` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '등록일자',
|
||||
`MOD_ID` varchar(25) NOT NULL COMMENT '수정자',
|
||||
`MOD_DATE` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '수정일자',
|
||||
PRIMARY KEY (`MU_PROCEDURE_REVIEW_ID`),
|
||||
KEY `idx_mu_procedure_review_category` (`CATEGORY_NO`),
|
||||
KEY `idx_mu_procedure_review_reg_date` (`REG_DATE`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='시술후기 정보';
|
||||
|
||||
-- 시퀀스 테이블
|
||||
CREATE TABLE `MU_PROCEDURE_REVIEW_SEQ` (
|
||||
`next_not_cached_value` bigint(21) NOT NULL,
|
||||
`minimum_value` bigint(21) NOT NULL,
|
||||
`maximum_value` bigint(21) NOT NULL,
|
||||
`start_value` bigint(21) NOT NULL COMMENT 'start value when sequences is created or value if RESTART is used',
|
||||
`increment` bigint(21) NOT NULL COMMENT 'increment value',
|
||||
`cache_size` bigint(21) unsigned NOT NULL,
|
||||
`cycle_option` tinyint(1) unsigned NOT NULL COMMENT '0 if no cycles are allowed, 1 if the sequence should begin a new cycle when maximum_value is passed',
|
||||
`cycle_count` bigint(21) NOT NULL COMMENT 'How many cycles have been done'
|
||||
) ENGINE=InnoDB SEQUENCE=1;
|
||||
|
||||
INSERT INTO `MU_PROCEDURE_REVIEW_SEQ` VALUES (1,1,99999999999,1,1,0,1,0);
|
||||
@@ -0,0 +1,473 @@
|
||||
package com.madeu.crm.procedureReview.ctrl;
|
||||
|
||||
import com.madeu.constants.Constants;
|
||||
import com.madeu.init.ManagerDraftAction;
|
||||
import com.madeu.crm.procedureReview.svc.ProcedureReviewService;
|
||||
import com.madeu.service.web.webloghistory.WebLogHistoryService;
|
||||
import com.madeu.util.HttpUtil;
|
||||
import com.madeu.util.RequestLogUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
public class ProcedureReviewController extends ManagerDraftAction {
|
||||
|
||||
@Autowired
|
||||
private ProcedureReviewService procedureReviewService;
|
||||
|
||||
@Autowired
|
||||
private WebLogHistoryService webLogHistoryService;
|
||||
|
||||
@RequestMapping(value = "/procedureReview/moveProcedureReviewList.do")
|
||||
public String moveProcedureReviewList(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response, Model model) {
|
||||
log.debug("ProcedureReviewController moveProcedureReviewList START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return "/web/login/logout";
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.selectProcedureReviewListIntro(paramMap);
|
||||
|
||||
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "/web/login/logout";
|
||||
}
|
||||
log.debug("ProcedureReviewController moveProcedureReviewList END");
|
||||
return "/crm/procedureReview/procedureReviewSelectList";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/getProcedureReviewList.do")
|
||||
public ModelAndView getProcedureReviewList(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("ProcedureReviewController getProcedureReviewList START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.selectListProcedureReview(paramMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (Constants.OK == map.get("msgCode")) {
|
||||
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("ProcedureReviewController getProcedureReviewList END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/getProcedureReview.do")
|
||||
public ModelAndView getProcedureReview(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("ProcedureReviewController getProcedureReview START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.selectProcedureReview(paramMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (Constants.OK == map.get("msgCode")) {
|
||||
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("ProcedureReviewController getProcedureReview END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/getCategoryList.do")
|
||||
public ModelAndView getCategoryList(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
map = procedureReviewService.selectListCategory(paramMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (Constants.OK == map.get("msgCode")) {
|
||||
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("ProcedureReviewController getCategoryList END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/putProcedureReviewFile.do")
|
||||
public ModelAndView putProcedureReviewFile(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@RequestParam(value = "file", required = false) MultipartFile file) {
|
||||
log.debug("ProcedureReviewController putProcedureReviewFile START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
StringBuffer errorMsg = new StringBuffer();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.insertProcedureReviewFile(paramMap, file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
errorMsg.append(e);
|
||||
} finally {
|
||||
if (Constants.OK == map.get("msgCode")) {
|
||||
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||
|
||||
insertMap.put("url", "/procedureReview/putProcedureReviewFile.do");
|
||||
insertMap.put("func", "putProcedureReviewFile");
|
||||
insertMap.put("funcName", "홈페이지 시술후기 상세 이미지파일 저장");
|
||||
insertMap.put("service", "procedureReviewService");
|
||||
insertMap.put("serviceName", "홈페이지 시술후기 등록");
|
||||
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||
insertMap.put("responseValue", String.valueOf(map));
|
||||
insertMap.put("tId", map.get("tId"));
|
||||
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||
|| String.valueOf(errorMsg).length() == 0) {
|
||||
insertMap.put("resultCode", "SUCCESS");
|
||||
} else {
|
||||
insertMap.put("resultCode", "ERROR");
|
||||
}
|
||||
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||
|
||||
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
log.debug("ProcedureReviewController putProcedureReviewFile END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/moveProcedureReviewInsert.do")
|
||||
public String moveProcedureReviewInsert(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Model model) {
|
||||
log.debug("ProcedureReviewController moveProcedureReviewInsert START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return "/web/login/logout";
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.selectProcedureReviewInsertIntro(paramMap);
|
||||
|
||||
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||
model.addAttribute("categorylist", map.get("categorylist"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "/web/login/logout";
|
||||
}
|
||||
log.debug("ProcedureReviewController moveProcedureReviewInsert END");
|
||||
return "/crm/procedureReview/procedureReviewInsert";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/putProcedureReview.do")
|
||||
public ModelAndView putProcedureReview(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("ProcedureReviewController putProcedureReview START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
StringBuffer errorMsg = new StringBuffer();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.insertProcedureReview(paramMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
errorMsg.append(e);
|
||||
} finally {
|
||||
if (Constants.OK == map.get("msgCode")) {
|
||||
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||
|
||||
insertMap.put("url", "/procedureReview/putProcedureReview.do");
|
||||
insertMap.put("func", "putProcedureReview");
|
||||
insertMap.put("funcName", "홈페이지 시술후기 등록");
|
||||
insertMap.put("service", "procedureReviewService");
|
||||
insertMap.put("serviceName", "홈페이지 시술후기 등록");
|
||||
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||
insertMap.put("responseValue", String.valueOf(map));
|
||||
insertMap.put("tId", map.get("tId"));
|
||||
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||
|| String.valueOf(errorMsg).length() == 0) {
|
||||
insertMap.put("resultCode", "SUCCESS");
|
||||
} else {
|
||||
insertMap.put("resultCode", "ERROR");
|
||||
}
|
||||
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||
|
||||
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
log.debug("ProcedureReviewController putProcedureReview END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/moveProcedureReviewUpdate.do")
|
||||
public String moveProcedureReviewUpdate(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Model model) {
|
||||
log.debug("ProcedureReviewController moveProcedureReviewUpdate START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return "/web/login/logout";
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.selectProcedureReviewUpdateIntro(paramMap);
|
||||
|
||||
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||
model.addAttribute("categorylist", map.get("categorylist"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "/web/login/logout";
|
||||
}
|
||||
log.debug("ProcedureReviewController moveProcedureReviewUpdate END");
|
||||
return "/crm/procedureReview/procedureReviewUpdate";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/modProcedureReview.do")
|
||||
public ModelAndView modProcedureReview(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("ProcedureReviewController modProcedureReview START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
StringBuffer errorMsg = new StringBuffer();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.updateProcedureReview(paramMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
errorMsg.append(e);
|
||||
} finally {
|
||||
if (Constants.OK == map.get("msgCode")) {
|
||||
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||
|
||||
insertMap.put("url", "/procedureReview/modProcedureReview.do");
|
||||
insertMap.put("func", "modProcedureReview");
|
||||
insertMap.put("funcName", "홈페이지 시술후기 수정");
|
||||
insertMap.put("service", "procedureReviewService");
|
||||
insertMap.put("serviceName", "홈페이지 시술후기 수정");
|
||||
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||
insertMap.put("responseValue", String.valueOf(map));
|
||||
insertMap.put("tId", map.get("tId"));
|
||||
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||
|| String.valueOf(errorMsg).length() == 0) {
|
||||
insertMap.put("resultCode", "SUCCESS");
|
||||
} else {
|
||||
insertMap.put("resultCode", "ERROR");
|
||||
}
|
||||
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||
|
||||
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
log.debug("ProcedureReviewController modProcedureReview END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/procedureReview/delProcedureReview.do")
|
||||
public ModelAndView delProcedureReview(HttpSession session, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.debug("ProcedureReviewController delProcedureReview START");
|
||||
|
||||
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
StringBuffer errorMsg = new StringBuffer();
|
||||
|
||||
try {
|
||||
if (!webCheckLogin(session)) {
|
||||
return null;
|
||||
} else {
|
||||
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||
map = procedureReviewService.deleteProcedureReview(paramMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
errorMsg.append(e);
|
||||
} finally {
|
||||
if (Constants.OK == map.get("msgCode")) {
|
||||
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("success", false);
|
||||
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||
|
||||
insertMap.put("url", "/procedureReview/delProcedureReview.do");
|
||||
insertMap.put("func", "delProcedureReview");
|
||||
insertMap.put("funcName", "홈페이지 시술후기 삭제");
|
||||
insertMap.put("service", "procedureReviewService");
|
||||
insertMap.put("serviceName", "홈페이지 시술후기 삭제");
|
||||
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||
insertMap.put("responseValue", String.valueOf(map));
|
||||
insertMap.put("tId", map.get("tId"));
|
||||
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||
|| String.valueOf(errorMsg).length() == 0) {
|
||||
insertMap.put("resultCode", "SUCCESS");
|
||||
} else {
|
||||
insertMap.put("resultCode", "ERROR");
|
||||
}
|
||||
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||
|
||||
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
log.debug("ProcedureReviewController delProcedureReview END");
|
||||
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.madeu.crm.procedureReview.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ProcedureReviewDTO {
|
||||
// WebPhotoDiet uses HashMap for data transfer, creating this DTO as placeholder
|
||||
// to follow project conventions.
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.madeu.crm.procedureReview.mapper;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.mybatis.spring.support.SqlSessionDaoSupport;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Repository
|
||||
public class ProcedureReviewMapper extends SqlSessionDaoSupport {
|
||||
|
||||
@Autowired
|
||||
private SqlSessionTemplate sqlSessionTemplate;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
setSqlSessionTemplate(sqlSessionTemplate);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> selectTotalProcedureReviewCount(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper selectTotalProcedureReviewCount START");
|
||||
String sqlId = "ProcedureReview.selectTotalProcedureReviewCount";
|
||||
logger.debug("ProcedureReviewMapper selectTotalProcedureReviewCount END");
|
||||
return getSqlSession().selectList(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> selectListProcedureReview(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper selectListProcedureReview START");
|
||||
String sqlId = "ProcedureReview.selectListProcedureReview";
|
||||
logger.debug("ProcedureReviewMapper selectListProcedureReview END");
|
||||
return getSqlSession().selectList(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> selectProcedureReview(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper selectProcedureReview START");
|
||||
String sqlId = "ProcedureReview.selectProcedureReview";
|
||||
logger.debug("ProcedureReviewMapper selectProcedureReview END");
|
||||
return getSqlSession().selectList(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public int insertProcedureReview(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper insertProcedureReview START");
|
||||
String sqlId = "ProcedureReview.insertProcedureReview";
|
||||
logger.debug("ProcedureReviewMapper insertProcedureReview END");
|
||||
return getSqlSession().insert(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public int updateProcedureReview(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper updateProcedureReview START");
|
||||
String sqlId = "ProcedureReview.updateProcedureReview";
|
||||
logger.debug("ProcedureReviewMapper updateProcedureReview END");
|
||||
return getSqlSession().update(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public int deleteProcedureReview(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper deleteProcedureReview START");
|
||||
String sqlId = "ProcedureReview.deleteProcedureReview";
|
||||
logger.debug("ProcedureReviewMapper deleteProcedureReview END");
|
||||
return getSqlSession().update(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> selectListPhotoCategory(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper selectListPhotoCategory START");
|
||||
String sqlId = "ProcedureReview.selectListPhotoCategory";
|
||||
logger.debug("ProcedureReviewMapper selectListPhotoCategory END");
|
||||
return getSqlSession().selectList(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public int insertProcedureReviewBeforeFile(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper insertProcedureReviewBeforeFile START");
|
||||
String sqlId = "ProcedureReview.insertProcedureReviewBeforeFile";
|
||||
logger.debug("ProcedureReviewMapper insertProcedureReviewBeforeFile END");
|
||||
return getSqlSession().insert(sqlId, paramMap);
|
||||
}
|
||||
|
||||
public int insertProcedureReviewAfterFile(HashMap<String, Object> paramMap)
|
||||
throws DataAccessException {
|
||||
logger.debug("ProcedureReviewMapper insertProcedureReviewAfterFile START");
|
||||
String sqlId = "ProcedureReview.insertProcedureReviewAfterFile";
|
||||
logger.debug("ProcedureReviewMapper insertProcedureReviewAfterFile END");
|
||||
return getSqlSession().insert(sqlId, paramMap);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
package com.madeu.crm.procedureReview.svc;
|
||||
|
||||
import com.madeu.constants.Constants;
|
||||
import com.madeu.crm.procedureReview.mapper.ProcedureReviewMapper;
|
||||
import com.madeu.dao.web.webauthmenurelation.WebAuthMenuRelationSqlMapDAO;
|
||||
import com.madeu.dao.web.webmember.WebMemberSqlMapDAO;
|
||||
import com.madeu.common.service.AttachFileService;
|
||||
import com.madeu.util.ValidationCheckUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service("ProcedureReviewService")
|
||||
public class ProcedureReviewService {
|
||||
|
||||
@Autowired
|
||||
private ProcedureReviewMapper procedureReviewMapper;
|
||||
|
||||
@Autowired
|
||||
private WebMemberSqlMapDAO webMemberSqlMapDAO;
|
||||
|
||||
@Autowired
|
||||
private WebAuthMenuRelationSqlMapDAO webAuthMenuRelationSqlMapDAO;
|
||||
|
||||
@Autowired
|
||||
private AttachFileService afs;
|
||||
|
||||
@Value("${url.cdn}")
|
||||
String CDN_URL;
|
||||
|
||||
public HashMap<String, Object> selectProcedureReviewListIntro(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService selectProcedureReviewListIntro START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
|
||||
String menuClass = String.valueOf(paramMap.get("menuClass"));
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(menuClass)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "메뉴 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||
|
||||
if (1 == authMenuRelationlistMapSize) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
|
||||
map.put("selectUseYn", authMenuRelationlistMap.get(0).get("selectUseYn"));
|
||||
map.put("insertUseYn", authMenuRelationlistMap.get(0).get("insertUseYn"));
|
||||
map.put("updateUseYn", authMenuRelationlistMap.get(0).get("updateUseYn"));
|
||||
map.put("deleteUseYn", authMenuRelationlistMap.get(0).get("deleteUseYn"));
|
||||
map.put("downloadUseYn", authMenuRelationlistMap.get(0).get("downloadUseYn"));
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService selectProcedureReviewListIntro END");
|
||||
return map;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> selectListProcedureReview(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService selectListProcedureReview START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
|
||||
if (1 == authMenuRelationlistMap.size()) {
|
||||
|
||||
if (("Y").equals(authMenuRelationlistMap.get(0).get("selectUseYn"))) {
|
||||
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "조회 권한 정보가 없습니다.");
|
||||
}
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
paramMap.put("categoryDivCd", "07");
|
||||
|
||||
if (true == check) {
|
||||
if (null == paramMap.get("procedureReviewDir") || ("").equals(paramMap.get("procedureReviewDir"))) {
|
||||
|
||||
} else {
|
||||
String dir = String.valueOf(paramMap.get("procedureReviewDir"));
|
||||
if (("A").equals(dir)) {
|
||||
paramMap.put("procedureReviewDir", "DESC");
|
||||
} else if (("B").equals(dir)) {
|
||||
paramMap.put("procedureReviewDir", "ASC");
|
||||
} else {
|
||||
paramMap.put("procedureReviewDir", "DESC");
|
||||
}
|
||||
}
|
||||
|
||||
paramMap.put("useYn", "Y");
|
||||
|
||||
List<Map<String, Object>> totalCountListMap = procedureReviewMapper
|
||||
.selectTotalProcedureReviewCount(paramMap);
|
||||
int totalCount = Integer.parseInt(String.valueOf(totalCountListMap.get(0).get("totalCount")));
|
||||
|
||||
if (0 < totalCount) {
|
||||
listMap = procedureReviewMapper.selectListProcedureReview(paramMap);
|
||||
}
|
||||
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("totalCount", totalCount);
|
||||
map.put("rows", listMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService selectListProcedureReview END");
|
||||
return map;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> selectProcedureReview(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService selectProcedureReview START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
String muProcedureReviewId = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(muProcedureReviewId)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "시술후기 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
|
||||
if (1 == authMenuRelationlistMap.size()) {
|
||||
|
||||
if (("Y").equals(authMenuRelationlistMap.get(0).get("selectUseYn"))) {
|
||||
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "조회 권한 정보가 없습니다.");
|
||||
}
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> listMap = procedureReviewMapper.selectProcedureReview(paramMap);
|
||||
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
map.put("rows", listMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService selectProcedureReview END");
|
||||
return map;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRES_NEW)
|
||||
public HashMap<String, Object> insertProcedureReviewFile(
|
||||
HashMap<String, Object> paramMap, MultipartFile file) throws Exception {
|
||||
log.debug("ProcedureReviewService insertProcedureReviewFile START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
String tId = String.valueOf(System.currentTimeMillis());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Calendar c1 = Calendar.getInstance();
|
||||
String tDate = sdf.format(c1.getTime());
|
||||
|
||||
if (null == file || file.isEmpty()) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "이미지 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||
|
||||
if (1 == authMenuRelationlistMapSize) {
|
||||
|
||||
if (("Y").equals(authMenuRelationlistMap.get(0).get("insertUseYn"))) {
|
||||
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "등록 권한 정보가 없습니다.");
|
||||
}
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
if (null != file && !file.isEmpty()) {
|
||||
Map<String, Object> fileMap = afs.saveAttachFile("img", file);
|
||||
String filePath = String.valueOf(fileMap.get("filePath"));
|
||||
String fileName = String.valueOf(fileMap.get("attachfileNm"));
|
||||
String chgFileName = String.valueOf(fileMap.get("orgAttachfileNm"));
|
||||
|
||||
String formattedCdnUrl = CDN_URL.endsWith("/") ? CDN_URL : CDN_URL + "/";
|
||||
paramMap.put("filePath", formattedCdnUrl + filePath);
|
||||
paramMap.put("fileName", fileName);
|
||||
paramMap.put("originalFileName", chgFileName);
|
||||
paramMap.put("attachfileId", fileMap.get("attachfileId"));
|
||||
}
|
||||
paramMap.put("tDate", tDate);
|
||||
paramMap.put("tId", tId);
|
||||
|
||||
map.put("rows", paramMap);
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("msgDesc", "등록되었습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService insertProcedureReviewFile END");
|
||||
return map;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> selectListCategory(HashMap<String, Object> paramMap) throws Exception {
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
paramMap.put("categoryDivCd", "07");
|
||||
List<Map<String, Object>> listMap = procedureReviewMapper.selectListPhotoCategory(paramMap);
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("rows", listMap);
|
||||
return map;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> selectProcedureReviewInsertIntro(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService selectProcedureReviewInsertIntro START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
|
||||
String menuClass = String.valueOf(paramMap.get("menuClass"));
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(menuClass)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "메뉴 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||
|
||||
if (1 == authMenuRelationlistMapSize) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
|
||||
map.put("selectUseYn", authMenuRelationlistMap.get(0).get("selectUseYn"));
|
||||
map.put("insertUseYn", authMenuRelationlistMap.get(0).get("insertUseYn"));
|
||||
map.put("updateUseYn", authMenuRelationlistMap.get(0).get("updateUseYn"));
|
||||
map.put("deleteUseYn", authMenuRelationlistMap.get(0).get("deleteUseYn"));
|
||||
map.put("downloadUseYn", authMenuRelationlistMap.get(0).get("downloadUseYn"));
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
paramMap.put("categoryDivCd", "07");
|
||||
List<Map<String, Object>> listMap = procedureReviewMapper.selectListPhotoCategory(paramMap);
|
||||
map.put("categorylist", listMap);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService selectProcedureReviewInsertIntro END");
|
||||
return map;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRES_NEW)
|
||||
public HashMap<String, Object> insertProcedureReview(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService insertProcedureReview START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
String tId = String.valueOf(System.currentTimeMillis());
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Calendar c1 = Calendar.getInstance();
|
||||
String tDate = sdf.format(c1.getTime());
|
||||
|
||||
String title = String.valueOf(paramMap.get("title"));
|
||||
String content = String.valueOf(paramMap.get("content"));
|
||||
String isEncoded = String.valueOf(paramMap.get("isEncoded"));
|
||||
if ("Y".equals(isEncoded) && content != null) {
|
||||
try {
|
||||
byte[] decodedBytes = java.util.Base64.getDecoder().decode(content);
|
||||
content = java.net.URLDecoder.decode(new String(decodedBytes, "UTF-8"), "UTF-8");
|
||||
paramMap.put("content", content);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(title)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "제목 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(content)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "시술후기 상세 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||
|
||||
if (1 == authMenuRelationlistMapSize) {
|
||||
if (("Y").equals(authMenuRelationlistMap.get(0).get("insertUseYn"))) {
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "등록 권한 정보가 없습니다.");
|
||||
}
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
paramMap.put("tDate", tDate);
|
||||
paramMap.put("tId", tId);
|
||||
paramMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||
procedureReviewMapper.insertProcedureReview(paramMap);
|
||||
paramMap.put("muProcedureReviewId", paramMap.get("id"));
|
||||
map.put("muProcedureReviewId", paramMap.get("muProcedureReviewId"));
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("msgDesc", "등록되었습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService insertProcedureReview END");
|
||||
return map;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> selectProcedureReviewUpdateIntro(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService selectProcedureReviewUpdateIntro START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
|
||||
String menuClass = String.valueOf(paramMap.get("menuClass"));
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(menuClass)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "메뉴 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||
|
||||
if (1 == authMenuRelationlistMapSize) {
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("success", "true");
|
||||
|
||||
map.put("selectUseYn", authMenuRelationlistMap.get(0).get("selectUseYn"));
|
||||
map.put("insertUseYn", authMenuRelationlistMap.get(0).get("insertUseYn"));
|
||||
map.put("updateUseYn", authMenuRelationlistMap.get(0).get("updateUseYn"));
|
||||
map.put("deleteUseYn", authMenuRelationlistMap.get(0).get("deleteUseYn"));
|
||||
map.put("downloadUseYn", authMenuRelationlistMap.get(0).get("downloadUseYn"));
|
||||
} else {
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
paramMap.put("categoryDivCd", "07");
|
||||
List<Map<String, Object>> listMap = procedureReviewMapper.selectListPhotoCategory(paramMap);
|
||||
map.put("categorylist", listMap);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService selectProcedureReviewUpdateIntro END");
|
||||
return map;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRES_NEW)
|
||||
public HashMap<String, Object> updateProcedureReview(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService updateProcedureReview START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
|
||||
String muProcedureReviewId = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||
String title = String.valueOf(paramMap.get("title"));
|
||||
String content = String.valueOf(paramMap.get("content"));
|
||||
String isEncoded = String.valueOf(paramMap.get("isEncoded"));
|
||||
if ("Y".equals(isEncoded) && content != null) {
|
||||
try {
|
||||
byte[] decodedBytes = java.util.Base64.getDecoder().decode(content);
|
||||
content = java.net.URLDecoder.decode(new String(decodedBytes, "UTF-8"), "UTF-8");
|
||||
paramMap.put("content", content);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(muProcedureReviewId)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "시술후기 식별자 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(title)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "제목 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(content)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "시술후기 상세 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||
|
||||
if (1 == authMenuRelationlistMapSize) {
|
||||
|
||||
if (("Y").equals(authMenuRelationlistMap.get(0).get("updateUseYn"))) {
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "수정 권한 정보가 없습니다.");
|
||||
}
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
procedureReviewMapper.updateProcedureReview(paramMap);
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("msgDesc", "수정되었습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService updateProcedureReview END");
|
||||
return map;
|
||||
}
|
||||
|
||||
public HashMap<String, Object> deleteProcedureReview(
|
||||
HashMap<String, Object> paramMap) throws Exception {
|
||||
log.debug("ProcedureReviewService deleteProcedureReview START");
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
|
||||
try {
|
||||
boolean check = true;
|
||||
|
||||
String muProcedureReviewId = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||
|
||||
if (true != ValidationCheckUtil.emptyCheck(muProcedureReviewId)) {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "시술후기 식별자 정보가 없습니다.");
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||
int userListMapSize = userListMap.size();
|
||||
|
||||
if (1 == userListMapSize) {
|
||||
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
if (true == check) {
|
||||
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||
.selectAuthMenuRelation(authCheckParamMap);
|
||||
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||
|
||||
if (1 == authMenuRelationlistMapSize) {
|
||||
|
||||
if (("Y").equals(authMenuRelationlistMap.get(0).get("deleteUseYn"))) {
|
||||
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "삭제 권한 정보가 없습니다.");
|
||||
}
|
||||
} else {
|
||||
check = false;
|
||||
map.put("msgCode", Constants.FAIL);
|
||||
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
String muProcedureReviewIdStr = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||
if (true == check) {
|
||||
String[] idArray = muProcedureReviewIdStr.split(",");
|
||||
for (String id : idArray) {
|
||||
paramMap.put("muProcedureReviewId", id.trim());
|
||||
procedureReviewMapper.deleteProcedureReview(paramMap);
|
||||
}
|
||||
|
||||
map.put("msgCode", Constants.OK);
|
||||
map.put("msgDesc", "삭제되었습니다.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
log.debug("ProcedureReviewService deleteProcedureReview END");
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
server:
|
||||
tomcat:
|
||||
max-swallow-size: -1
|
||||
max-http-form-post-size: -1
|
||||
port: 8080
|
||||
servlet:
|
||||
session:
|
||||
timeout: -1
|
||||
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 500MB
|
||||
max-request-size: 500MB
|
||||
datasource:
|
||||
hikari:
|
||||
driver-class-name: org.mariadb.jdbc.Driver
|
||||
|
||||
@@ -10,8 +10,8 @@ spring:
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
maxFileSize: 500MB
|
||||
maxRequestSize: 500MB
|
||||
max-file-size: 500MB
|
||||
max-request-size: 500MB
|
||||
|
||||
aop:
|
||||
proxy-target-class: false
|
||||
@@ -30,6 +30,8 @@ spring:
|
||||
|
||||
|
||||
server:
|
||||
tomcat:
|
||||
max-swallow-size: -1
|
||||
encoding:
|
||||
charset: UTF-8
|
||||
enabled: true
|
||||
@@ -56,6 +58,7 @@ logging:
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
p6spy: debug
|
||||
org.springframework.aop.framework.CglibAopProxy: ERROR
|
||||
|
||||
decorator:
|
||||
datasource:
|
||||
|
||||
209
src/main/resources/mappers/ProcedureReviewSqlMap.xml
Normal file
209
src/main/resources/mappers/ProcedureReviewSqlMap.xml
Normal file
@@ -0,0 +1,209 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="ProcedureReview">
|
||||
|
||||
<select id="selectTotalProcedureReviewCount" parameterType="hashmap" resultType="hashmap">
|
||||
SELECT COUNT(*) AS "totalCount"
|
||||
FROM MU_PROCEDURE_REVIEW MPR
|
||||
WHERE MPR.USE_YN = 'Y'
|
||||
<if test="procedureReviewSearchKeywordParam1 != null and procedureReviewSearchKeywordParam1 != ''">
|
||||
AND MPR.TITLE LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam1}), '%')
|
||||
</if>
|
||||
<if test="procedureReviewSearchKeywordParam2 != null and procedureReviewSearchKeywordParam2 != ''">
|
||||
AND IFNULL((SELECT MM.NAME FROM MU_MEMBER AS MM
|
||||
WHERE MM.USE_YN = 'Y'
|
||||
AND MM.MU_MEMBER_ID = MPR.REG_ID
|
||||
LIMIT 0, 1
|
||||
),'') LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam2}), '%')
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="selectListProcedureReview" parameterType="hashmap" resultType="hashmap">
|
||||
SELECT MPR.*
|
||||
FROM (
|
||||
SELECT MPR.*
|
||||
,CAST(@RNUM:=@RNUM + 1 AS CHAR) AS "rowNum"
|
||||
FROM (
|
||||
SELECT MPR.MU_PROCEDURE_REVIEW_ID AS "muProcedureReviewId"
|
||||
,MPR.TITLE AS "title"
|
||||
,MPR.CONTENT AS "content"
|
||||
,MPR.HASHTAG AS "hashtag"
|
||||
,MPR.VIEW_COUNT AS "viewCount"
|
||||
,DATE_FORMAT(MPR.REG_DATE, '%Y-%m-%d') AS "writeDate"
|
||||
,IFNULL((SELECT MM.NAME
|
||||
FROM MU_MEMBER AS MM
|
||||
WHERE MM.USE_YN = 'Y'
|
||||
AND MM.MU_MEMBER_ID = MPR.REG_ID
|
||||
LIMIT 0, 1
|
||||
),'') AS "writeName"
|
||||
FROM MU_PROCEDURE_REVIEW MPR
|
||||
WHERE MPR.USE_YN = 'Y'
|
||||
<if test="procedureReviewSearchKeywordParam1 != null and procedureReviewSearchKeywordParam1 != ''">
|
||||
AND MPR.TITLE LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam1}), '%')
|
||||
</if>
|
||||
<if test="procedureReviewSearchKeywordParam2 != null and procedureReviewSearchKeywordParam2 != ''">
|
||||
AND IFNULL((SELECT MM.NAME FROM MU_MEMBER AS MM
|
||||
WHERE MM.USE_YN = 'Y'
|
||||
AND MM.MU_MEMBER_ID = MPR.REG_ID
|
||||
LIMIT 0, 1
|
||||
),'') LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam2}), '%')
|
||||
</if>
|
||||
<choose>
|
||||
<when test="procedureReviewSort != null and procedureReviewSort != ''">
|
||||
ORDER BY ${procedureReviewSort}
|
||||
</when>
|
||||
<otherwise>
|
||||
ORDER BY MPR.REG_DATE DESC
|
||||
</otherwise>
|
||||
</choose>
|
||||
LIMIT 18446744073709551615
|
||||
) MPR, (SELECT @RNUM:=0) R
|
||||
WHERE 1 = 1
|
||||
) MPR
|
||||
WHERE 1 = 1
|
||||
LIMIT ${procedureReviewStart}, ${procedureReviewLimit}
|
||||
</select>
|
||||
|
||||
<select id="selectProcedureReview" parameterType="hashmap" resultType="hashmap">
|
||||
SELECT MPR.MU_PROCEDURE_REVIEW_ID AS "muProcedureReviewId"
|
||||
,MPR.CATEGORY_NO AS "categoryno"
|
||||
,MPR.TITLE AS "title"
|
||||
,MPR.CONTENT AS "content"
|
||||
,MPR.HASHTAG AS "hashtag"
|
||||
,HAF.FILE_PATH AS "beforefile"
|
||||
,HAF2.FILE_PATH AS "afterfile"
|
||||
FROM MU_PROCEDURE_REVIEW MPR
|
||||
LEFT OUTER JOIN HP_ATTACH_FILE HAF ON HAF.ATTACHFILE_ID = MPR.BEFORE_PHOTO_ATTACHFILE_ID
|
||||
LEFT OUTER JOIN HP_ATTACH_FILE HAF2 ON HAF2.ATTACHFILE_ID = MPR.AFTER_PHOTO_ATTACHFILE_ID
|
||||
WHERE MPR.USE_YN = 'Y'
|
||||
AND MPR.MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
|
||||
LIMIT 0, 1
|
||||
</select>
|
||||
|
||||
<insert id="insertProcedureReview" parameterType="hashmap">
|
||||
<selectKey resultType="string" keyProperty="id" order="BEFORE">
|
||||
SELECT CONCAT('PR-', LPAD(NEXTVAL(MU_PROCEDURE_REVIEW_SEQ), 21, '0'))
|
||||
</selectKey>
|
||||
INSERT INTO MU_PROCEDURE_REVIEW (
|
||||
MU_PROCEDURE_REVIEW_ID
|
||||
,MU_MEMBER_ID
|
||||
,CATEGORY_NO
|
||||
,TITLE
|
||||
,CONTENT
|
||||
,HASHTAG
|
||||
,BEFORE_PHOTO_ATTACHFILE_ID
|
||||
,AFTER_PHOTO_ATTACHFILE_ID
|
||||
,VIEW_COUNT
|
||||
,USE_YN
|
||||
,REG_ID
|
||||
,REG_DATE
|
||||
,MOD_ID
|
||||
,MOD_DATE
|
||||
) VALUES (
|
||||
#{id}
|
||||
,#{muMemberId}
|
||||
,#{categoryno, jdbcType=VARCHAR}
|
||||
,#{title}
|
||||
,#{content}
|
||||
,#{hashtag}
|
||||
,#{beforeId, jdbcType=VARCHAR}
|
||||
,#{afterId, jdbcType=VARCHAR}
|
||||
,0
|
||||
,'Y'
|
||||
,#{regId}
|
||||
,NOW()
|
||||
,#{modId}
|
||||
,NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertProcedureReviewBeforeFile" parameterType="hashmap">
|
||||
<selectKey resultType="string" keyProperty="beforeId" order="BEFORE">
|
||||
SELECT CONCAT('ATF-', LPAD(NEXTVAL(seq_attachfile_id), 21, '0'))
|
||||
</selectKey>
|
||||
INSERT INTO HP_ATTACH_FILE (
|
||||
ATTACHFILE_ID
|
||||
,ATTACHFILE_NM
|
||||
,FILE_PATH
|
||||
,FILE_SIZE
|
||||
,FILE_TYPE
|
||||
,REG_ID
|
||||
,REG_DATE
|
||||
,MOD_ID
|
||||
,MOD_DATE
|
||||
) VALUES (
|
||||
#{beforeId}
|
||||
,#{beforeFileName}
|
||||
,#{beforeFilePath}
|
||||
,'0'
|
||||
,'img'
|
||||
,#{regId}
|
||||
,NOW()
|
||||
,#{modId}
|
||||
,NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertProcedureReviewAfterFile" parameterType="hashmap">
|
||||
<selectKey resultType="string" keyProperty="afterId" order="BEFORE">
|
||||
SELECT CONCAT('ATF-', LPAD(NEXTVAL(seq_attachfile_id), 21, '0'))
|
||||
</selectKey>
|
||||
INSERT INTO HP_ATTACH_FILE (
|
||||
ATTACHFILE_ID
|
||||
,ATTACHFILE_NM
|
||||
,FILE_PATH
|
||||
,FILE_SIZE
|
||||
,FILE_TYPE
|
||||
,REG_ID
|
||||
,REG_DATE
|
||||
,MOD_ID
|
||||
,MOD_DATE
|
||||
) VALUES (
|
||||
#{afterId}
|
||||
,#{afterFileName}
|
||||
,#{afterFilePath}
|
||||
,'0'
|
||||
,'img'
|
||||
,#{regId}
|
||||
,NOW()
|
||||
,#{modId}
|
||||
,NOW()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateProcedureReview" parameterType="hashmap">
|
||||
UPDATE MU_PROCEDURE_REVIEW
|
||||
SET TITLE = #{title}
|
||||
,CONTENT = #{content}
|
||||
,HASHTAG = #{hashtag}
|
||||
<if test="beforeId != null and beforeId != ''">
|
||||
,BEFORE_PHOTO_ATTACHFILE_ID = #{beforeId}
|
||||
</if>
|
||||
<if test="afterId != null and afterId != ''">
|
||||
,AFTER_PHOTO_ATTACHFILE_ID = #{afterId}
|
||||
</if>
|
||||
,MOD_ID = #{modId}
|
||||
,MOD_DATE = NOW()
|
||||
WHERE USE_YN = 'Y'
|
||||
AND MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
|
||||
</update>
|
||||
|
||||
<update id="deleteProcedureReview" parameterType="hashmap">
|
||||
UPDATE MU_PROCEDURE_REVIEW
|
||||
SET MOD_ID = #{modId}
|
||||
,MOD_DATE = NOW()
|
||||
,USE_YN = 'N'
|
||||
WHERE USE_YN = 'Y'
|
||||
AND MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
|
||||
</update>
|
||||
|
||||
<select id="selectListPhotoCategory" parameterType="hashmap" resultType="hashmap">
|
||||
SELECT ROW_NUMBER() OVER (ORDER BY HC.REG_DATE DESC) AS "rowNum"
|
||||
,HC.CATEGORY_NO AS "categoryNo"
|
||||
,HC.CATEGORY_NM AS "categoryNm"
|
||||
FROM HP_CATEGORY AS HC
|
||||
WHERE HC.USE_YN = 'Y'
|
||||
AND HC.CATEGORY_DIV_CD = #{categoryDivCd}
|
||||
ORDER BY HC.CATEGORY_NO ASC
|
||||
</select>
|
||||
</mapper>
|
||||
321
src/main/resources/static/css/web/procedureReview.css
Normal file
321
src/main/resources/static/css/web/procedureReview.css
Normal file
@@ -0,0 +1,321 @@
|
||||
/* ================================================
|
||||
시술후기 등록/수정 전용 스타일
|
||||
================================================ */
|
||||
|
||||
/* ── 전체 컨테이너 ── */
|
||||
.procedure-review-form {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
height: calc(100% - 10px);
|
||||
}
|
||||
|
||||
/* ── 좌측 패널 (폼 영역) ── */
|
||||
.procedure-review-form .form-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
/* ── 우측 패널 (이미지 영역) ── */
|
||||
.procedure-review-form .image-panel {
|
||||
flex: 0 0 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* ── 폼 그룹 ── */
|
||||
.procedure-review-form .pr-form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-form-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-form-row .pr-form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-form-row .pr-form-group.pr-category {
|
||||
flex: 0 0 180px;
|
||||
}
|
||||
|
||||
/* ── 라벨 ── */
|
||||
.procedure-review-form .pr-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-label .required {
|
||||
color: #EF4444;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ── Input / Select 공통 ── */
|
||||
.procedure-review-form input[type="text"],
|
||||
.procedure-review-form select {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: #1F2937;
|
||||
background: #fff;
|
||||
outline: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.procedure-review-form input[type="text"]:focus,
|
||||
.procedure-review-form select:focus {
|
||||
border-color: #3985EA;
|
||||
box-shadow: 0 0 0 3px rgba(57, 133, 234, 0.12);
|
||||
}
|
||||
|
||||
.procedure-review-form input[type="text"]::placeholder {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
/* ── Quill 에디터 ── */
|
||||
.procedure-review-form .editor-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.procedure-review-form .editor-container .ql-toolbar.ql-snow {
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 6px 6px 0 0;
|
||||
background: #F9FAFB;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.procedure-review-form .editor-container .ql-container.ql-snow {
|
||||
border: 1px solid #D1D5DB;
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.procedure-review-form .editor-container .ql-editor {
|
||||
min-height: 240px;
|
||||
line-height: 1.7;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.procedure-review-form .editor-container .ql-editor.ql-blank::before {
|
||||
color: #9CA3AF;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* ── 이미지 카드 ── */
|
||||
.procedure-review-form .image-card {
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
background: #F3F4F6;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header span {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header .image-card-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header .image-card-actions label {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header .image-card-actions label:hover {
|
||||
background: #E5E7EB;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header .image-card-actions label img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header .image-card-actions .img-delete-btn {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: #9CA3AF;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-header .image-card-actions .img-delete-btn:hover {
|
||||
background: #FEE2E2;
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-body {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
background: #FAFAFA;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-body .placeholder-text {
|
||||
color: #D1D5DB;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.procedure-review-form .image-card .image-card-body img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── 가이드 박스 ── */
|
||||
.procedure-review-form .photo-guide {
|
||||
padding: 14px 16px;
|
||||
background: #F0F5FF;
|
||||
border: 1px solid #DBEAFE;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: #4B5563;
|
||||
}
|
||||
|
||||
.procedure-review-form .photo-guide p {
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
color: #3985EA;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.procedure-review-form .photo-guide ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.procedure-review-form .photo-guide ul li {
|
||||
position: relative;
|
||||
padding-left: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.procedure-review-form .photo-guide ul li::before {
|
||||
content: '•';
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
color: #3985EA;
|
||||
}
|
||||
|
||||
/* ── 액션 버튼 영역 ── */
|
||||
.procedure-review-form .pr-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-actions .pr-btn {
|
||||
width: 90px;
|
||||
height: 38px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-actions .pr-btn-primary {
|
||||
background: #3985EA;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-actions .pr-btn-primary:hover {
|
||||
background: #2563EB;
|
||||
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.25);
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-actions .pr-btn-cancel {
|
||||
background: #F3F4F6;
|
||||
color: #6B7280;
|
||||
border: 1px solid #D1D5DB;
|
||||
}
|
||||
|
||||
.procedure-review-form .pr-actions .pr-btn-cancel:hover {
|
||||
background: #E5E7EB;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* ── 반응형 ── */
|
||||
@media only screen and (max-width: 1400px) {
|
||||
.procedure-review-form .image-panel {
|
||||
flex: 0 0 340px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.procedure-review-form {
|
||||
flex-direction: column;
|
||||
}
|
||||
.procedure-review-form .image-panel {
|
||||
flex: none;
|
||||
}
|
||||
.procedure-review-form .image-panel .images-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
.procedure-review-form .image-panel .images-row .image-card {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
/****************************************************************************
|
||||
* Quill 에디터 인스턴스
|
||||
****************************************************************************/
|
||||
let quill;
|
||||
|
||||
/****************************************************************************
|
||||
* Quill Image blot - data: URL (base64) 허용
|
||||
****************************************************************************/
|
||||
const QuillImage = Quill.import('formats/image');
|
||||
const originalSanitize = QuillImage.sanitize;
|
||||
QuillImage.sanitize = function (url) {
|
||||
if (url && url.startsWith('data:')) {
|
||||
return '';
|
||||
}
|
||||
return originalSanitize.call(this, url);
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Quill 에디터 초기화
|
||||
****************************************************************************/
|
||||
function fn_initQuillEditor() {
|
||||
quill = new Quill('#quillEditor', {
|
||||
theme: 'snow',
|
||||
placeholder: '내용을 입력해주세요.',
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
||||
[{ 'font': [] }],
|
||||
[{ 'size': ['small', false, 'large', 'huge'] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
[{ 'script': 'sub' }, { 'script': 'super' }],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'list': 'check' }],
|
||||
[{ 'indent': '-1' }, { 'indent': '+1' }],
|
||||
[{ 'direction': 'rtl' }],
|
||||
[{ 'align': [] }],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image', 'video', 'formula'],
|
||||
['clean']
|
||||
],
|
||||
handlers: {
|
||||
image: imageHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quill.root.addEventListener('paste', function (e) {
|
||||
try {
|
||||
console.log("[Paste Event] Triggered");
|
||||
let clipboardData = e.clipboardData || window.clipboardData || (e.originalEvent && e.originalEvent.clipboardData);
|
||||
console.log("[Paste Event] clipboardData:", clipboardData);
|
||||
if (!clipboardData || !clipboardData.items) {
|
||||
console.warn("[Paste Event] No clipboardData or items found");
|
||||
return;
|
||||
}
|
||||
|
||||
let items = clipboardData.items;
|
||||
console.log("[Paste Event] items length:", items.length);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
console.log(`[Paste Event] item[${i}] kind: ${item.kind}, type: ${item.type}`);
|
||||
if (item.kind === 'file' && item.type.match('^image/')) {
|
||||
console.log(`[Paste Event] Image file detected. Prevent default.`);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let file = item.getAsFile();
|
||||
console.log(`[Paste Event] getAsFile() result:`, file);
|
||||
if (file) {
|
||||
if (!file.name || !file.name.includes('.')) {
|
||||
let ext = 'png';
|
||||
if (file.type === 'image/jpeg') ext = 'jpg';
|
||||
else if (file.type === 'image/gif') ext = 'gif';
|
||||
file = new File([file], "clipboard_image." + ext, { type: file.type });
|
||||
console.log(`[Paste Event] Renamed missing file info:`, file.name);
|
||||
}
|
||||
console.log(`[Paste Event] Call fn_uploadImageAndInsertToQuill() for:`, file.name);
|
||||
fn_uploadImageAndInsertToQuill(file);
|
||||
} else {
|
||||
console.error("[Paste Event] getAsFile() returned null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Paste Event] Error parsing paste logic:", err);
|
||||
}
|
||||
}, true);
|
||||
|
||||
quill.root.addEventListener('drop', function (e) {
|
||||
try {
|
||||
console.log("[Drop Event] Triggered");
|
||||
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||
let hasImage = false;
|
||||
console.log("[Drop Event] files length:", e.dataTransfer.files.length);
|
||||
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
let file = e.dataTransfer.files[i];
|
||||
console.log(`[Drop Event] file[${i}] type: ${file.type}`);
|
||||
if (file.type.match('^image/')) {
|
||||
console.log(`[Drop Event] Image file detected. Prevent default.`);
|
||||
hasImage = true;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
fn_uploadImageAndInsertToQuill(file);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("[Drop Event] No files found in drop event");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Drop Event] Error parsing drop logic:", err);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
function imageHandler() {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('accept', 'image/*');
|
||||
input.click();
|
||||
|
||||
input.onchange = async () => {
|
||||
const file = input.files[0];
|
||||
fn_uploadImageAndInsertToQuill(file);
|
||||
};
|
||||
}
|
||||
|
||||
function fn_uploadImageAndInsertToQuill(file) {
|
||||
if (!file) {
|
||||
console.error("[Upload] file is undefined or null");
|
||||
return;
|
||||
}
|
||||
console.log("[Upload] fn_uploadImageAndInsertToQuill Start. File name:", file.name, "size:", file.size);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append("menuClass", menuClass);
|
||||
|
||||
$.ajax({
|
||||
url: '/procedureReview/putProcedureReviewFile.do',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (typeof res === 'string') {
|
||||
try { res = JSON.parse(res); } catch (e) { }
|
||||
}
|
||||
console.log("[Upload] API Success response:", res);
|
||||
if (res.msgCode == 0 || res.msgCode === '0') {
|
||||
let range = quill.getSelection(true);
|
||||
let index = range ? range.index : quill.getLength();
|
||||
console.log(`[Upload] Inserting image at index = ${index}, path = ${res.rows.filePath}`);
|
||||
quill.insertEmbed(index, 'image', res.rows.filePath);
|
||||
} else {
|
||||
console.error("[Upload] API logic error msgDesc:", res.msgDesc);
|
||||
modalEvent.danger("업로드 오류", res.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error("[Upload] AJAX HTTP error. status:", status, "error:", error, "responseText:", xhr.responseText);
|
||||
modalEvent.danger("업로드 오류", "이미지 업로드 중 오류가 발생했습니다.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Quill 콘텐츠를 Delta JSON 문자열로 반환 (저장용)
|
||||
****************************************************************************/
|
||||
function fn_getQuillContentForSave() {
|
||||
return JSON.stringify(quill.getContents());
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 저장된 콘텐츠를 Quill에 로드 (Delta JSON / 레거시 HTML 호환)
|
||||
****************************************************************************/
|
||||
function fn_loadQuillContent(content) {
|
||||
if (!content) return;
|
||||
|
||||
if (typeof content === 'object') {
|
||||
if (Array.isArray(content.ops)) quill.setContents(content.ops);
|
||||
else if (Array.isArray(content)) quill.setContents(content);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof content === 'string') {
|
||||
// base64인지 판별해 디코딩 시도 (기존 데이터와 새 데이터 호환을 위해)
|
||||
const isBase64 = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(content);
|
||||
if (isBase64 && content.length > 0 && !content.trim().startsWith('{') && !content.trim().startsWith('[')) {
|
||||
try {
|
||||
content = decodeURIComponent(escape(atob(content)));
|
||||
} catch (e) {
|
||||
// base64 디코딩 실패면 그냥 넘어감
|
||||
}
|
||||
}
|
||||
|
||||
if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
|
||||
let parsed = JSON.parse(content);
|
||||
if (typeof parsed === 'string') {
|
||||
parsed = JSON.parse(parsed);
|
||||
}
|
||||
|
||||
if (typeof parsed === 'object' && parsed !== null && Array.isArray(parsed.ops)) {
|
||||
quill.setContents(parsed.ops);
|
||||
return;
|
||||
} else if (Array.isArray(parsed)) {
|
||||
quill.setContents(parsed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Quill Delta Parsing Failed - Fallback to HTML : ", e);
|
||||
}
|
||||
|
||||
quill.clipboard.dangerouslyPasteHTML(content);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 시술후기 등록
|
||||
****************************************************************************/
|
||||
function fn_insertProcedureReview() {
|
||||
if ("Y" != insertUseYn) {
|
||||
modalEvent.warning("", "등록 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
let title = $("#title").val();
|
||||
let content = fn_getQuillContentForSave();
|
||||
let hashtag = $("#hashtag").val();
|
||||
|
||||
// Quill 에디터 빈 값 체크
|
||||
let quillText = quill.getText().trim();
|
||||
|
||||
if (true != fn_emptyCheck(title)) {
|
||||
modalEvent.warning("등록", "제목을 입력하세요.");
|
||||
return;
|
||||
}
|
||||
if (!quillText || quillText.length === 0) {
|
||||
modalEvent.warning("등록", "내용을 입력하세요.");
|
||||
return;
|
||||
}
|
||||
if (true != fn_emptyCheck(hashtag)) {
|
||||
modalEvent.warning("등록", "해시태그를 입력하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
modalEvent.info("등록", "시술후기 정보를 등록하시겠습니까?", function () {
|
||||
let formData = new FormData();
|
||||
formData.append("menuClass", menuClass);
|
||||
formData.append("title", title);
|
||||
|
||||
let encodedContent = btoa(unescape(encodeURIComponent(content)));
|
||||
formData.append("content", encodedContent);
|
||||
formData.append("isEncoded", "Y"); // base64 인코딩 플래그 추가
|
||||
formData.append("hashtag", hashtag);
|
||||
|
||||
$.ajax({
|
||||
url: encodeURI('/procedureReview/putProcedureReview.do'),
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if ('0' == data.msgCode) {
|
||||
modalEvent.success("등록 성공", data.msgDesc, function () {
|
||||
fn_selectListProcedureReviewIntro();
|
||||
});
|
||||
}
|
||||
else {
|
||||
modalEvent.danger("등록 오류", data.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
modalEvent.danger("등록 오류", "등록 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 리스트 화면으로 이동.
|
||||
****************************************************************************/
|
||||
function fn_selectListProcedureReviewIntro() {
|
||||
if ("Y" == selectUseYn) {
|
||||
let pagingParam = "?menuClass=" + menuClass;
|
||||
fn_leftFormAction("/procedureReview/moveProcedureReviewList.do" + pagingParam);
|
||||
} else {
|
||||
modalEvent.warning("", "조회 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 도움말 모달
|
||||
****************************************************************************/
|
||||
function fn_initHelpModal() {
|
||||
$('#btnHelp').on('click', function () {
|
||||
$('#helpModal').addClass('active');
|
||||
});
|
||||
$('#helpModalClose').on('click', function () {
|
||||
$('#helpModal').removeClass('active');
|
||||
});
|
||||
$('#helpModal').on('click', function (e) {
|
||||
if (e.target === this) {
|
||||
$(this).removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 미리보기 모달
|
||||
****************************************************************************/
|
||||
function fn_initPreviewModal() {
|
||||
$('#previewModalClose').on('click', function () {
|
||||
$('#previewModal').removeClass('active');
|
||||
});
|
||||
$('#previewModal').on('click', function (e) {
|
||||
if (e.target === this) {
|
||||
$(this).removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fn_showPreview() {
|
||||
let title = $("#title").val();
|
||||
let content = quill.root.innerHTML; // 미리보기는 렌더링된 HTML 사용
|
||||
let hashtag = $("#hashtag").val();
|
||||
|
||||
// 미리보기 데이터 설정
|
||||
$('#previewTitle').text(title || '(제목 없음)');
|
||||
$('#previewContent').html(content);
|
||||
|
||||
if (hashtag) {
|
||||
let tags = hashtag.split(/\s+/).map(function (tag) {
|
||||
tag = tag.trim();
|
||||
if (tag && !tag.startsWith('#')) tag = '#' + tag;
|
||||
return tag;
|
||||
}).filter(function (tag) { return tag; });
|
||||
$('#previewHashtag').html(tags.join(' '));
|
||||
$('#previewHashtagArea').show();
|
||||
} else {
|
||||
$('#previewHashtagArea').hide();
|
||||
}
|
||||
|
||||
$('#previewModal').addClass('active');
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 페이지 init
|
||||
****************************************************************************/
|
||||
function fn_pageInit() {
|
||||
fn_initQuillEditor();
|
||||
fn_initHelpModal();
|
||||
fn_initPreviewModal();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 페이지 event
|
||||
****************************************************************************/
|
||||
function fn_pageProcedureReview() {
|
||||
$('.btnCancle').on("click", function () {
|
||||
fn_selectListProcedureReviewIntro();
|
||||
});
|
||||
$('.btnSave').on("click", function () {
|
||||
fn_insertProcedureReview();
|
||||
});
|
||||
$('.btnPreview').on("click", function () {
|
||||
fn_showPreview();
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
fn_pageInit();
|
||||
fn_pageProcedureReview();
|
||||
});
|
||||
@@ -0,0 +1,413 @@
|
||||
/* 페이징 관련 변수 */
|
||||
let procedureReviewTotalCount = 0;
|
||||
let procedureReviewTotalPages = 0;
|
||||
|
||||
/*aggird*/
|
||||
let procedureReviewAgGridData = [];
|
||||
|
||||
let procedureReviewSelectId = "";
|
||||
let procedureReviewSelectCategoryNo = "";
|
||||
|
||||
/****************************************************************************
|
||||
* 시술후기 정보 리스트 조회
|
||||
****************************************************************************/
|
||||
function fn_selectListProcedureReviewJson() {
|
||||
let formData = new FormData();
|
||||
formData.append("menuClass", menuClass);
|
||||
formData.append("procedureReviewSearchKeywordParam0", procedureReviewSearchKeywordParam0);
|
||||
formData.append("procedureReviewSearchKeywordParam1", procedureReviewSearchKeywordParam1);
|
||||
formData.append("procedureReviewSearchKeywordParam2", procedureReviewSearchKeywordParam2);
|
||||
formData.append("procedureReviewSearchKeywordParam3", procedureReviewSearchKeywordParam3);
|
||||
formData.append("procedureReviewSort", procedureReviewSort);
|
||||
formData.append("procedureReviewDir", procedureReviewDir);
|
||||
formData.append("procedureReviewStart", procedureReviewStart);
|
||||
formData.append("procedureReviewLimit", procedureReviewLimit);
|
||||
formData.append("procedureReviewSearchStartDate", procedureReviewSearchStartDate);
|
||||
formData.append("procedureReviewSearchEndDate", procedureReviewSearchEndDate);
|
||||
formData.append("procedureReviewSearchDateType", procedureReviewSearchDateType);
|
||||
|
||||
$.ajax({
|
||||
url: encodeURI('/procedureReview/getProcedureReviewList.do'),
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if ('0' == data.msgCode) {
|
||||
// 페이징 처리
|
||||
procedureReviewTotalCount = data.totalCount;
|
||||
|
||||
procedureReviewTotalPages = Math.ceil(procedureReviewTotalCount / procedureReviewLimit);
|
||||
|
||||
// 리스트 조회
|
||||
procedureReviewAgGridData = data.rows;
|
||||
procedureReviewGridOptions.api.setRowData(procedureReviewAgGridData);
|
||||
|
||||
if (0 < data.rows.length) {
|
||||
//페이징 처리
|
||||
window.pagObj = $('#procedureReviewPagination').twbsPagination({
|
||||
startPage: ((procedureReviewStart / procedureReviewLimit) + 1),
|
||||
totalPages: (procedureReviewTotalPages == 0) ? 1 : procedureReviewTotalPages,
|
||||
visiblePages: 10,
|
||||
initiateStartPageClick: false,
|
||||
prev: '<img src="/image/web/page_navigation_arrow.svg" alt="prev"/>',
|
||||
next: '<img src="/image/web/page_navigation_arrow.svg" alt="next"/>',
|
||||
first: '',
|
||||
last: '',
|
||||
onPageClick: function (event, page) {
|
||||
fn_procedureReviewPagination(page);
|
||||
}
|
||||
}).on('page', function (event, page) {
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
modalEvent.danger("조회 오류", data.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||
},
|
||||
beforeSend: function () {
|
||||
// 로딩열기
|
||||
procedureReviewGridOptions.api.showLoadingOverlay();
|
||||
},
|
||||
complete: function () {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 검색하기
|
||||
****************************************************************************/
|
||||
function fn_procedureReviewSearch(param) {
|
||||
if ("A" != param && "Y" != selectUseYn) {
|
||||
modalEvent.warning("", "조회 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
fn_procedureReviewPaginReset();
|
||||
|
||||
procedureReviewSearchKeywordParam0 = $("#procedureReviewSearchKeyword0").val();
|
||||
procedureReviewSearchKeywordParam1 = $("#procedureReviewSearchKeyword1").val();
|
||||
procedureReviewSearchKeywordParam2 = ""; // 작성자 검색 제거
|
||||
procedureReviewSearchKeywordParam3 = $("#procedureReviewSearchKeywordParam3").val();
|
||||
|
||||
procedureReviewSearchDateType = $("#procedureReviewSearchDateType").val();
|
||||
procedureReviewSearchStartDate = $("#procedureReviewSearchStartDate").val();
|
||||
procedureReviewSearchEndDate = $("#procedureReviewSearchEndDate").val();
|
||||
|
||||
fn_selectListProcedureReviewJson();
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* 초기화하기
|
||||
****************************************************************************/
|
||||
function fn_procedureReviewReset() {
|
||||
$("#procedureReviewSearchKeyword0").val("");
|
||||
$("#procedureReviewSearchKeyword1").val("");
|
||||
$("#procedureReviewSearchKeyword2").val("");
|
||||
$("#procedureReviewSearchKeyword3").val("");
|
||||
|
||||
fn_procedureReviewSearch();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 페이징 처리
|
||||
****************************************************************************/
|
||||
function fn_procedureReviewPagination(param) {
|
||||
procedureReviewStart = (parseInt(param) - 1) * procedureReviewLimit;
|
||||
|
||||
fn_selectListProcedureReviewJson();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 페이징 리셋
|
||||
****************************************************************************/
|
||||
function fn_procedureReviewPaginReset() {
|
||||
procedureReviewSearchKeywordParam0 = '';
|
||||
procedureReviewSearchKeywordParam1 = '';
|
||||
procedureReviewSearchKeywordParam2 = '';
|
||||
procedureReviewSearchKeywordParam3 = '';
|
||||
|
||||
procedureReviewStart = 0;
|
||||
procedureReviewLimit = 100;
|
||||
procedureReviewTotalCount = 0;
|
||||
procedureReviewTotalPages = 0;
|
||||
|
||||
//페이징 초기화
|
||||
if ($("#procedureReviewPagination").data("twbs-pagination")) {
|
||||
$("#procedureReviewPagination").twbsPagination("destroy");
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 시술후기 삭제
|
||||
****************************************************************************/
|
||||
function fn_deleteProcedureReview() {
|
||||
if ("Y" != deleteUseYn) {
|
||||
modalEvent.warning("", "삭제 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!procedureReviewSelectId) {
|
||||
modalEvent.warning("", "삭제할 대상을 선택하세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
modalEvent.info("삭제", "선택한 시술후기 정보를 삭제하시겠습니까?", function () {
|
||||
let formData = new FormData();
|
||||
formData.append("menuClass", menuClass);
|
||||
formData.append("muProcedureReviewId", procedureReviewSelectId);
|
||||
|
||||
$.ajax({
|
||||
url: encodeURI('/procedureReview/delProcedureReview.do'),
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if ('0' == data.msgCode) {
|
||||
modalEvent.success("삭제 성공", data.msgDesc, function () {
|
||||
fn_procedureReviewOk();
|
||||
});
|
||||
}
|
||||
else {
|
||||
modalEvent.danger("삭제 오류", data.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
modalEvent.danger("삭제 오류", "삭제 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||
},
|
||||
beforeSend: function () {
|
||||
|
||||
},
|
||||
complete: function () {
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 검색 엔터 시술후기
|
||||
****************************************************************************/
|
||||
function fn_procedureReviewEnter(e) {
|
||||
if (e.which) {
|
||||
if (13 == e.which) {
|
||||
fn_procedureReviewSearch();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (13 == e.keyCode) {
|
||||
fn_procedureReviewSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 완료
|
||||
****************************************************************************/
|
||||
function fn_procedureReviewOk() {
|
||||
fn_procedureReviewReset();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 등록 화면으로 이동.
|
||||
****************************************************************************/
|
||||
function fn_insertProcedureReviewIntro() {
|
||||
if ("Y" == insertUseYn) {
|
||||
let pagingParam = "?menuClass=" + menuClass;
|
||||
fn_leftFormAction("/procedureReview/moveProcedureReviewInsert.do" + pagingParam);
|
||||
} else {
|
||||
modalEvent.warning("", "등록 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 수정 화면으로 이동.
|
||||
****************************************************************************/
|
||||
function fn_updateProcedureReviewIntro(muProcedureReviewId) {
|
||||
if ("Y" == updateUseYn) {
|
||||
let pagingParam = "?menuClass=" + menuClass;
|
||||
pagingParam += "&muProcedureReviewId=" + muProcedureReviewId;
|
||||
fn_leftFormAction("/procedureReview/moveProcedureReviewUpdate.do" + pagingParam);
|
||||
} else {
|
||||
modalEvent.warning("", "수정 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let procedureReviewColumnDefs = [
|
||||
{ field: "checkbox", headerName: "", minWidth: 55, maxWidth: 55, headerCheckboxSelection: true, checkboxSelection: true },
|
||||
{ field: "rowNum", headerName: "번호", minWidth: 60, maxWidth: 60, sortable: false, cellStyle: { textAlign: 'center' } },
|
||||
{ field: "title", headerName: "제목", minWidth: 150, cellStyle: { cursor: 'pointer', color: '#3985EA' } },
|
||||
{
|
||||
field: "content",
|
||||
headerName: "내용요약",
|
||||
minWidth: 150,
|
||||
valueFormatter: function (params) {
|
||||
let val = params.value;
|
||||
if (!val) return "";
|
||||
|
||||
try {
|
||||
let delta = JSON.parse(val);
|
||||
if (typeof delta === 'string') {
|
||||
delta = JSON.parse(delta);
|
||||
}
|
||||
|
||||
if (delta && delta.ops) {
|
||||
let text = "";
|
||||
delta.ops.forEach(function (op) {
|
||||
if (op.insert) {
|
||||
if (typeof op.insert === 'string') text += op.insert;
|
||||
else if (op.insert.image) text += "[이미지] ";
|
||||
}
|
||||
});
|
||||
return text.replace(/\n/g, ' ').trim();
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to strip HTML
|
||||
}
|
||||
return String(val).replace(/<[^>]*>?/gm, '').replace(/\n/g, ' ').trim();
|
||||
}
|
||||
},
|
||||
{ field: "hashtag", headerName: "해시태그", minWidth: 150 },
|
||||
{ field: "writeDate", headerName: "등록일", minWidth: 100, maxWidth: 150 },
|
||||
{ field: "writeName", headerName: "작성자", minWidth: 100, maxWidth: 150 },
|
||||
];
|
||||
|
||||
let procedureReviewGridOptions = {
|
||||
suppressRowTransform: true,
|
||||
columnDefs: procedureReviewColumnDefs,
|
||||
defaultColDef: {
|
||||
flex: 1,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
editable: true,
|
||||
cellStyle: { textAlign: 'left', fontSize: '14px', padding: '0' },
|
||||
enablePivot: true,
|
||||
enableValue: true
|
||||
},
|
||||
headerHeight: 41,
|
||||
rowHeight: 41,
|
||||
rowData: procedureReviewAgGridData,
|
||||
suppressRowClickSelection: true,
|
||||
localeText: {
|
||||
noRowsToShow: '조회 결과가 없습니다.'
|
||||
},
|
||||
rowSelection: 'multiple',
|
||||
debug: false,
|
||||
onCellClicked: function (event) {
|
||||
if ('title' == event.column.colId) {
|
||||
fn_updateProcedureReviewIntro(event.data.muProcedureReviewId);
|
||||
}
|
||||
},
|
||||
onSelectionChanged: function (event) {
|
||||
let selectRows = [];
|
||||
selectRows = event.api.getSelectedRows();
|
||||
|
||||
procedureReviewSelectId = '';
|
||||
for (let i = 0; i < selectRows.length; i++) {
|
||||
procedureReviewSelectId += selectRows[i].muProcedureReviewId + ",";
|
||||
}
|
||||
procedureReviewSelectId = procedureReviewSelectId.substring(0, procedureReviewSelectId.lastIndexOf(','));
|
||||
},
|
||||
onSortChanged: function (event) {
|
||||
procedureReviewSort = '';
|
||||
let columnArr = event.columnApi.getColumnState();
|
||||
if (0 < columnArr.length) {
|
||||
columnArr.sort(function (a, b) {
|
||||
return a.sortIndex - b.sortIndex;
|
||||
});
|
||||
|
||||
let nullCnt = 0;
|
||||
for (let i = 0; i < columnArr.length; i++) {
|
||||
let gridSortModel = columnArr[i].colId;
|
||||
let gridSort = columnArr[i].sort;
|
||||
|
||||
if (gridSort != null) {
|
||||
procedureReviewStart = 0;
|
||||
procedureReviewSort += gridSortModel + ' ' + gridSort + ',';
|
||||
}
|
||||
else {
|
||||
nullCnt++;
|
||||
if (nullCnt == columnArr.length) {
|
||||
procedureReviewSort = '';
|
||||
procedureReviewDir = '';
|
||||
procedureReviewStart = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
procedureReviewSort = procedureReviewSort.substring(0, procedureReviewSort.lastIndexOf(","));
|
||||
|
||||
fn_procedureReviewSearch();
|
||||
}
|
||||
};
|
||||
|
||||
let procedureReviewGridDiv = document.querySelector('#procedureReviewGrid');
|
||||
new agGrid.Grid(procedureReviewGridDiv, procedureReviewGridOptions);
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* 페이지 init
|
||||
****************************************************************************/
|
||||
function fn_pageInit() {
|
||||
if (!procedureReviewSearchStartDate && !procedureReviewSearchEndDate) {
|
||||
} else {
|
||||
$("#procedureReviewSearchStartDate").val(procedureReviewSearchStartDate).trigger("change");
|
||||
$("#procedureReviewSearchEndDate").val(procedureReviewSearchEndDate).trigger("change");
|
||||
}
|
||||
|
||||
$("#procedureReviewSearchKeyword0").val(procedureReviewSearchKeywordParam0);
|
||||
$("#procedureReviewSearchKeyword1").val(procedureReviewSearchKeywordParam1);
|
||||
$("#procedureReviewSearchKeyword2").val(procedureReviewSearchKeywordParam2);
|
||||
$("#procedureReviewSearchKeyword3").val(procedureReviewSearchKeywordParam3);
|
||||
|
||||
fn_procedureReviewSearch("A");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* 페이지 event
|
||||
****************************************************************************/
|
||||
function fn_pageProcedureReview() {
|
||||
|
||||
$(document).on('keypress', '#procedureReviewSearchKeyword1', function (e) {
|
||||
fn_procedureReviewEnter(e);
|
||||
});
|
||||
|
||||
$(document).on('keypress', '#procedureReviewSearchKeyword3', function (e) {
|
||||
fn_procedureReviewEnter(e);
|
||||
});
|
||||
|
||||
$("#btnSearchProcedureReview").click(function () {
|
||||
fn_procedureReviewSearch();
|
||||
});
|
||||
|
||||
$("#btnInsertProcedureReview").click(function () {
|
||||
fn_insertProcedureReviewIntro();
|
||||
});
|
||||
|
||||
$("#btnDeleteProcedureReview").click(function () {
|
||||
fn_deleteProcedureReview();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
$(function () {
|
||||
fn_pageInit();
|
||||
fn_pageProcedureReview();
|
||||
});
|
||||
@@ -0,0 +1,421 @@
|
||||
/****************************************************************************
|
||||
* Quill 에디터 인스턴스
|
||||
****************************************************************************/
|
||||
let quill;
|
||||
|
||||
/****************************************************************************
|
||||
* Quill Image blot - data: URL (base64) 허용
|
||||
****************************************************************************/
|
||||
const QuillImage = Quill.import('formats/image');
|
||||
const originalSanitize = QuillImage.sanitize;
|
||||
QuillImage.sanitize = function (url) {
|
||||
if (url && url.startsWith('data:')) {
|
||||
return '';
|
||||
}
|
||||
return originalSanitize.call(this, url);
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Quill 에디터 초기화
|
||||
****************************************************************************/
|
||||
function fn_initQuillEditor() {
|
||||
quill = new Quill('#quillEditor', {
|
||||
theme: 'snow',
|
||||
placeholder: '내용을 입력해주세요.',
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
||||
[{ 'font': [] }],
|
||||
[{ 'size': ['small', false, 'large', 'huge'] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
[{ 'script': 'sub' }, { 'script': 'super' }],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'list': 'check' }],
|
||||
[{ 'indent': '-1' }, { 'indent': '+1' }],
|
||||
[{ 'direction': 'rtl' }],
|
||||
[{ 'align': [] }],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image', 'video', 'formula'],
|
||||
['clean']
|
||||
],
|
||||
handlers: {
|
||||
image: imageHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quill.root.addEventListener('paste', function (e) {
|
||||
try {
|
||||
console.log("[Paste Event] Triggered");
|
||||
let clipboardData = e.clipboardData || window.clipboardData || (e.originalEvent && e.originalEvent.clipboardData);
|
||||
console.log("[Paste Event] clipboardData:", clipboardData);
|
||||
if (!clipboardData || !clipboardData.items) {
|
||||
console.warn("[Paste Event] No clipboardData or items found");
|
||||
return;
|
||||
}
|
||||
|
||||
let items = clipboardData.items;
|
||||
console.log("[Paste Event] items length:", items.length);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
console.log(`[Paste Event] item[${i}] kind: ${item.kind}, type: ${item.type}`);
|
||||
if (item.kind === 'file' && item.type.match('^image/')) {
|
||||
console.log(`[Paste Event] Image file detected. Prevent default.`);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let file = item.getAsFile();
|
||||
console.log(`[Paste Event] getAsFile() result:`, file);
|
||||
if (file) {
|
||||
if (!file.name || !file.name.includes('.')) {
|
||||
let ext = 'png';
|
||||
if (file.type === 'image/jpeg') ext = 'jpg';
|
||||
else if (file.type === 'image/gif') ext = 'gif';
|
||||
file = new File([file], "clipboard_image." + ext, { type: file.type });
|
||||
console.log(`[Paste Event] Renamed missing file info:`, file.name);
|
||||
}
|
||||
console.log(`[Paste Event] Call fn_uploadImageAndInsertToQuill() for:`, file.name);
|
||||
fn_uploadImageAndInsertToQuill(file);
|
||||
} else {
|
||||
console.error("[Paste Event] getAsFile() returned null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Paste Event] Error parsing paste logic:", err);
|
||||
}
|
||||
}, true);
|
||||
|
||||
quill.root.addEventListener('drop', function (e) {
|
||||
try {
|
||||
console.log("[Drop Event] Triggered");
|
||||
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||
let hasImage = false;
|
||||
console.log("[Drop Event] files length:", e.dataTransfer.files.length);
|
||||
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
let file = e.dataTransfer.files[i];
|
||||
console.log(`[Drop Event] file[${i}] type: ${file.type}`);
|
||||
if (file.type.match('^image/')) {
|
||||
console.log(`[Drop Event] Image file detected. Prevent default.`);
|
||||
hasImage = true;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
fn_uploadImageAndInsertToQuill(file);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("[Drop Event] No files found in drop event");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Drop Event] Error parsing drop logic:", err);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
function imageHandler() {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('accept', 'image/*');
|
||||
input.click();
|
||||
|
||||
input.onchange = async () => {
|
||||
const file = input.files[0];
|
||||
fn_uploadImageAndInsertToQuill(file);
|
||||
};
|
||||
}
|
||||
|
||||
function fn_uploadImageAndInsertToQuill(file) {
|
||||
if (!file) {
|
||||
console.error("[Upload] file is undefined or null");
|
||||
return;
|
||||
}
|
||||
console.log("[Upload] fn_uploadImageAndInsertToQuill Start. File name:", file.name, "size:", file.size);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append("menuClass", menuClass);
|
||||
|
||||
$.ajax({
|
||||
url: '/procedureReview/putProcedureReviewFile.do',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (typeof res === 'string') {
|
||||
try { res = JSON.parse(res); } catch (e) { }
|
||||
}
|
||||
console.log("[Upload] API Success response:", res);
|
||||
if (res.msgCode == 0 || res.msgCode === '0') {
|
||||
let range = quill.getSelection(true);
|
||||
let index = range ? range.index : quill.getLength();
|
||||
console.log(`[Upload] Inserting image at index = ${index}, path = ${res.rows.filePath}`);
|
||||
quill.insertEmbed(index, 'image', res.rows.filePath);
|
||||
} else {
|
||||
console.error("[Upload] API logic error msgDesc:", res.msgDesc);
|
||||
modalEvent.danger("업로드 오류", res.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error("[Upload] AJAX HTTP error. status:", status, "error:", error, "responseText:", xhr.responseText);
|
||||
modalEvent.danger("업로드 오류", "이미지 업로드 중 오류가 발생했습니다.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Quill 콘텐츠를 Delta JSON 문자열로 반환 (저장용)
|
||||
****************************************************************************/
|
||||
function fn_getQuillContentForSave() {
|
||||
return JSON.stringify(quill.getContents());
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 저장된 콘텐츠를 Quill에 로드 (Delta JSON / 레거시 HTML 호환)
|
||||
****************************************************************************/
|
||||
function fn_loadQuillContent(content) {
|
||||
if (!content) return;
|
||||
|
||||
if (typeof content === 'object') {
|
||||
if (Array.isArray(content.ops)) quill.setContents(content.ops);
|
||||
else if (Array.isArray(content)) quill.setContents(content);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof content === 'string') {
|
||||
// base64인지 판별해 디코딩 시도 (기존 데이터와 새 데이터 호환을 위해)
|
||||
const isBase64 = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(content);
|
||||
if (isBase64 && content.length > 0 && !content.trim().startsWith('{') && !content.trim().startsWith('[')) {
|
||||
try {
|
||||
content = decodeURIComponent(escape(atob(content)));
|
||||
} catch (e) {
|
||||
// base64 디코딩 실패면 그냥 넘어감
|
||||
}
|
||||
}
|
||||
|
||||
if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
|
||||
let parsed = JSON.parse(content);
|
||||
if (typeof parsed === 'string') {
|
||||
parsed = JSON.parse(parsed);
|
||||
}
|
||||
|
||||
if (typeof parsed === 'object' && parsed !== null && Array.isArray(parsed.ops)) {
|
||||
quill.setContents(parsed.ops);
|
||||
return;
|
||||
} else if (Array.isArray(parsed)) {
|
||||
quill.setContents(parsed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Quill Delta Parsing Failed - Fallback to HTML : ", e);
|
||||
}
|
||||
|
||||
quill.clipboard.dangerouslyPasteHTML(content);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 시술후기 상세 조회
|
||||
****************************************************************************/
|
||||
function fn_selectProcedureReview() {
|
||||
if (true != fn_emptyCheck(muProcedureReviewId)) {
|
||||
modalEvent.warning("수정", "시술후기 정보가 없습니다.");
|
||||
return;
|
||||
}
|
||||
let formData = new FormData();
|
||||
formData.append("menuClass", menuClass);
|
||||
formData.append("muProcedureReviewId", muProcedureReviewId);
|
||||
|
||||
$.ajax({
|
||||
url: encodeURI('/procedureReview/getProcedureReview.do'),
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if ('0' == data.msgCode) {
|
||||
if (data.rows && data.rows.length > 0) {
|
||||
let photoData = data.rows[0];
|
||||
$("#title").val(photoData.title);
|
||||
fn_loadQuillContent(photoData.content);
|
||||
$("#hashtag").val(photoData.hashtag);
|
||||
} else {
|
||||
modalEvent.warning("수정", "조회된 시술후기 데이터가 없습니다.");
|
||||
}
|
||||
} else {
|
||||
alert("수정 오류: " + data.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
alert("수정 오류: 수정 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 시술후기 수정
|
||||
****************************************************************************/
|
||||
function fn_updateProcedureReview() {
|
||||
if ("Y" != updateUseYn) {
|
||||
modalEvent.warning("", "수정 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
let title = $("#title").val();
|
||||
let content = fn_getQuillContentForSave();
|
||||
let hashtag = $("#hashtag").val();
|
||||
|
||||
// Quill 에디터 빈 값 체크
|
||||
let quillText = quill.getText().trim();
|
||||
|
||||
if (true != fn_emptyCheck(title)) {
|
||||
modalEvent.warning("수정", "제목을 입력하세요.");
|
||||
return;
|
||||
}
|
||||
if (!quillText || quillText.length === 0) {
|
||||
modalEvent.warning("수정", "내용을 입력하세요.");
|
||||
return;
|
||||
}
|
||||
if (true != fn_emptyCheck(hashtag)) {
|
||||
modalEvent.warning("수정", "해시태그를 입력하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
modalEvent.info("수정", "시술후기 정보를 수정하시겠습니까?", function () {
|
||||
let formData = new FormData();
|
||||
formData.append("menuClass", menuClass);
|
||||
formData.append("title", title);
|
||||
|
||||
let encodedContent = btoa(unescape(encodeURIComponent(content)));
|
||||
formData.append("content", encodedContent);
|
||||
formData.append("isEncoded", "Y"); // base64 인코딩 플래그 추가
|
||||
formData.append("hashtag", hashtag);
|
||||
formData.append("muProcedureReviewId", muProcedureReviewId);
|
||||
|
||||
$.ajax({
|
||||
url: encodeURI('/procedureReview/modProcedureReview.do'),
|
||||
data: formData,
|
||||
dataType: "json",
|
||||
processData: false,
|
||||
contentType: false,
|
||||
type: 'POST',
|
||||
async: true,
|
||||
success: function (data) {
|
||||
if ('0' == data.msgCode) {
|
||||
modalEvent.success("수정 성공", data.msgDesc, function () {
|
||||
fn_selectListProcedureReviewIntro();
|
||||
});
|
||||
}
|
||||
else {
|
||||
modalEvent.danger("수정 오류", data.msgDesc);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
modalEvent.danger("수정 오류", "수정 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 리스트 화면으로 이동
|
||||
****************************************************************************/
|
||||
function fn_selectListProcedureReviewIntro() {
|
||||
if ("Y" == selectUseYn) {
|
||||
let pagingParam = "?menuClass=" + menuClass;
|
||||
fn_leftFormAction("/procedureReview/moveProcedureReviewList.do" + pagingParam);
|
||||
} else {
|
||||
modalEvent.warning("", "조회 권한이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 도움말 모달
|
||||
****************************************************************************/
|
||||
function fn_initHelpModal() {
|
||||
$('#btnHelp').on('click', function () {
|
||||
$('#helpModal').addClass('active');
|
||||
});
|
||||
$('#helpModalClose').on('click', function () {
|
||||
$('#helpModal').removeClass('active');
|
||||
});
|
||||
$('#helpModal').on('click', function (e) {
|
||||
if (e.target === this) {
|
||||
$(this).removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 미리보기 모달
|
||||
****************************************************************************/
|
||||
function fn_initPreviewModal() {
|
||||
$('#previewModalClose').on('click', function () {
|
||||
$('#previewModal').removeClass('active');
|
||||
});
|
||||
$('#previewModal').on('click', function (e) {
|
||||
if (e.target === this) {
|
||||
$(this).removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fn_showPreview() {
|
||||
let title = $("#title").val();
|
||||
let content = quill.root.innerHTML;
|
||||
let hashtag = $("#hashtag").val();
|
||||
|
||||
// 미리보기 데이터 설정
|
||||
$('#previewTitle').text(title || '(제목 없음)');
|
||||
$('#previewContent').html(content);
|
||||
|
||||
if (hashtag) {
|
||||
let tags = hashtag.split(/\s+/).map(function (tag) {
|
||||
tag = tag.trim();
|
||||
if (tag && !tag.startsWith('#')) tag = '#' + tag;
|
||||
return tag;
|
||||
}).filter(function (tag) { return tag; });
|
||||
$('#previewHashtag').html(tags.join(' '));
|
||||
$('#previewHashtagArea').show();
|
||||
} else {
|
||||
$('#previewHashtagArea').hide();
|
||||
}
|
||||
|
||||
$('#previewModal').addClass('active');
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 페이지 init
|
||||
****************************************************************************/
|
||||
function fn_pageInit() {
|
||||
fn_initQuillEditor();
|
||||
fn_initHelpModal();
|
||||
fn_initPreviewModal();
|
||||
fn_selectProcedureReview();
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* 페이지 event
|
||||
****************************************************************************/
|
||||
function fn_pageEvent() {
|
||||
$('.btnCancle').on("click", function () {
|
||||
fn_selectListProcedureReviewIntro();
|
||||
});
|
||||
$('.btnSave').on("click", function () {
|
||||
fn_updateProcedureReview();
|
||||
});
|
||||
$('.btnPreview').on("click", function () {
|
||||
fn_showPreview();
|
||||
});
|
||||
}
|
||||
|
||||
$(function () {
|
||||
fn_pageInit();
|
||||
fn_pageEvent();
|
||||
});
|
||||
@@ -0,0 +1,477 @@
|
||||
<!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="~{/web/layout/homeLayout}">
|
||||
<th:block layout:fragment="layout_css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/web/ContentsBbsUpd.css">
|
||||
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||
<style>
|
||||
/* Quill 에디터 스타일 보정 */
|
||||
.ql-toolbar.ql-snow {
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px 5px 0 0;
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
.ql-container.ql-snow {
|
||||
border: 1px solid #E9ECF0;
|
||||
border-top: none;
|
||||
border-radius: 0 0 5px 5px;
|
||||
min-height: 350px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 340px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.ql-editor.ql-blank::before {
|
||||
color: #B5BDC4;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 페이지 스크롤 활성화 오버라이드 */
|
||||
.center_box {
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.project_wrap .content_section .hospital_wrap .center_box .content_box {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* 단일 폼 레이아웃 */
|
||||
.pr-single-form {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.pr-single-form .form-grid-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group input[type="text"] {
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group input[type="text"]:focus {
|
||||
border-color: #3985EA;
|
||||
box-shadow: 0 0 0 2px rgba(57, 133, 234, 0.1);
|
||||
}
|
||||
|
||||
/* 내용 헤더 (라벨 + 도움말 버튼) */
|
||||
.content-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.content-header label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.btn-help {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #3985EA;
|
||||
background: #EFF6FF;
|
||||
border: 1px solid #BFDBFE;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-help:hover {
|
||||
background: #DBEAFE;
|
||||
border-color: #93C5FD;
|
||||
}
|
||||
|
||||
/* 액션 버튼 */
|
||||
.pr-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Quill 에디터 및 미리보기 이미지 크기 제한 */
|
||||
.ql-editor img,
|
||||
#previewContent img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 도움말 모달 */
|
||||
.help-modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.help-modal-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.help-modal {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
width: 640px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.help-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.help-modal-header h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.help-modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: #F3F4F6;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: #6B7280;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.help-modal-close:hover {
|
||||
background: #E5E7EB;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.help-modal-body {
|
||||
padding: 24px;
|
||||
font-size: 13px;
|
||||
color: #4B5563;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.help-modal-body h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin: 20px 0 8px 0;
|
||||
}
|
||||
|
||||
.help-modal-body h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.help-modal-body table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 8px 0 16px 0;
|
||||
}
|
||||
|
||||
.help-modal-body table th {
|
||||
background: #F9FAFB;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border: 1px solid #E5E7EB;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.help-modal-body table td {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #E5E7EB;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.help-modal-body .tip-box {
|
||||
background: #F0F9FF;
|
||||
border: 1px solid #BAE6FD;
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.help-modal-body .tip-box strong {
|
||||
color: #0284C7;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_top_script">
|
||||
<script>
|
||||
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_content">
|
||||
<div class="center_box">
|
||||
<p class="page_title" style="float:none; display:block; width:100%;">시술후기 등록</p>
|
||||
|
||||
<div class="pr-single-form">
|
||||
<!-- 제목 -->
|
||||
<div class="form-grid-row">
|
||||
<div class="form-group">
|
||||
<label>제목</label>
|
||||
<input type="text" id="title" placeholder="제목을 입력해주세요." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 내용 (Quill 에디터) -->
|
||||
<div class="form-group" style="margin-bottom: 16px;">
|
||||
<div class="content-header">
|
||||
<label>내용</label>
|
||||
<button type="button" class="btn-help" id="btnHelp">? 도움말</button>
|
||||
</div>
|
||||
<div id="quillEditor"></div>
|
||||
</div>
|
||||
|
||||
<!-- 해시태그 -->
|
||||
<div class="form-grid-row">
|
||||
<div class="form-group">
|
||||
<label>해시태그</label>
|
||||
<input type="text" id="hashtag" placeholder="#해시태그를 입력해주세요." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 버튼 -->
|
||||
<div class="pr-actions">
|
||||
<button class="preview_btn btnPreview"
|
||||
style="width: 90px; height: 36px; border-radius: 4px; background: #fff; color: #3985EA; border: 1px solid #3985EA; font-weight: 600; cursor: pointer;">미리보기</button>
|
||||
<button class="registration_btn btnSave"
|
||||
style="width: 80px; height: 36px; border-radius: 4px;">등록</button>
|
||||
<button class="cancel_btn btnCancle"
|
||||
style="width: 80px; height: 36px; border-radius: 4px; margin-left: 10px;">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 미리보기 모달 -->
|
||||
<div class="help-modal-overlay" id="previewModal">
|
||||
<div class="help-modal" style="width: 800px; max-height: 90vh;">
|
||||
<div class="help-modal-header">
|
||||
<h3>👁 미리보기</h3>
|
||||
<button class="help-modal-close" id="previewModalClose">✕</button>
|
||||
</div>
|
||||
<div class="help-modal-body" style="padding: 0;">
|
||||
<!-- 제목 영역 -->
|
||||
<div style="padding: 20px 24px; border-bottom: 1px solid #E5E7EB;">
|
||||
<p style="font-size: 11px; color: #9CA3AF; margin-bottom: 4px;">제목</p>
|
||||
<h2 id="previewTitle" style="font-size: 20px; font-weight: 700; color: #1F2937; margin: 0;"></h2>
|
||||
</div>
|
||||
<!-- 내용 영역 -->
|
||||
<div style="padding: 24px;">
|
||||
<div id="previewContent" class="ql-editor"
|
||||
style="min-height: 200px; max-height: 500px; overflow-y: auto; padding: 0;"></div>
|
||||
</div>
|
||||
<!-- 해시태그 영역 -->
|
||||
<div id="previewHashtagArea" style="padding: 12px 24px 20px; border-top: 1px solid #E5E7EB;">
|
||||
<span id="previewHashtag"
|
||||
style="display: inline-block; padding: 4px 12px; background: #EFF6FF; color: #3985EA; border-radius: 20px; font-size: 13px; font-weight: 500;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 도움말 모달 -->
|
||||
<div class="help-modal-overlay" id="helpModal">
|
||||
<div class="help-modal">
|
||||
<div class="help-modal-header">
|
||||
<h3>📝 에디터 사용 가이드</h3>
|
||||
<button class="help-modal-close" id="helpModalClose">✕</button>
|
||||
</div>
|
||||
<div class="help-modal-body">
|
||||
<h4>📌 텍스트 서식</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
<th>단축키</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>B</strong> 굵게</td>
|
||||
<td>선택한 텍스트를 굵게 표시</td>
|
||||
<td>Ctrl+B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><em>I</em> 기울임</td>
|
||||
<td>선택한 텍스트를 기울임꼴로 표시</td>
|
||||
<td>Ctrl+I</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><u>U</u> 밑줄</td>
|
||||
<td>선택한 텍스트에 밑줄 적용</td>
|
||||
<td>Ctrl+U</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><s>S</s> 취소선</td>
|
||||
<td>선택한 텍스트에 취소선 적용</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>🎨 글자 스타일</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>헤더 (H1~H6)</td>
|
||||
<td>제목 크기를 설정합니다. 숫자가 작을수록 큰 제목</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>폰트</td>
|
||||
<td>글꼴을 변경합니다</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>글자 크기</td>
|
||||
<td>Small, Normal, Large, Huge 중 선택</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>글자색 / 배경색</td>
|
||||
<td>텍스트 색상 또는 배경 하이라이트 색상 선택</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>📋 문단 서식</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>순서 목록</td>
|
||||
<td>1. 2. 3. 형태의 번호 목록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>비순서 목록</td>
|
||||
<td>• 형태의 글머리 기호 목록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>체크 목록</td>
|
||||
<td>☐ 형태의 체크박스 목록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>들여쓰기</td>
|
||||
<td>텍스트를 안쪽/바깥쪽으로 이동</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>정렬</td>
|
||||
<td>좌측, 중앙, 우측, 양쪽 정렬</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>인용문</td>
|
||||
<td>인용 블록으로 표시</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>코드 블록</td>
|
||||
<td>코드 형태로 표시</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>🔗 멀티미디어</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>링크</td>
|
||||
<td>선택한 텍스트에 URL 링크 삽입</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>이미지</td>
|
||||
<td>이미지 파일을 선택하여 삽입 (서버에 자동 업로드)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>동영상</td>
|
||||
<td>YouTube 등 동영상 URL을 삽입</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>🧹 기타</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>서식 지우기</td>
|
||||
<td>선택한 텍스트의 모든 서식을 초기화</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>위첨자 / 아래첨자</td>
|
||||
<td>X² 또는 H₂O 같은 수식 표현</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="tip-box">
|
||||
<strong>💡 TIP</strong><br>
|
||||
• 텍스트를 드래그한 후 툴바 버튼을 클릭하면 선택 영역에만 서식이 적용됩니다.<br>
|
||||
• 이미지는 클립보드에서 Ctrl+V로 붙여넣기도 가능합니다.<br>
|
||||
• 실행 취소는 Ctrl+Z, 다시 실행은 Ctrl+Y 입니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_popup">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_script">
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
|
||||
<script src="/js/crm/procedureReview/procedureReviewInsert.js"></script>
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,85 @@
|
||||
<!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="~{/web/layout/homeLayout}">
|
||||
<th:block layout:fragment="layout_css">
|
||||
<link rel="stylesheet" href="/css/web/webPhotoDietSelectList.css">
|
||||
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_top_script">
|
||||
<script src="/js/web/jquery.twbsPagination.js" type="text/javascript"></script>
|
||||
<script>
|
||||
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||
let categoryDivCd = "07";
|
||||
|
||||
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
|
||||
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
|
||||
let deleteUseYn = "[[${deleteUseYn}]]" == "" ? "N" : "[[${deleteUseYn}]]";
|
||||
let downloadUseYn = "[[${downloadUseYn}]]" == "" ? "N" : "[[${downloadUseYn}]]";
|
||||
|
||||
/* 검색 관련 변수 */
|
||||
let procedureReviewSearchKeywordParam0 = "[[${param.procedureReviewSearchKeywordParam0}]]";
|
||||
let procedureReviewSearchKeywordParam1 = "[[${param.procedureReviewSearchKeywordParam1}]]";
|
||||
let procedureReviewSearchKeywordParam2 = "";
|
||||
let procedureReviewSearchKeywordParam3 = "";
|
||||
|
||||
let procedureReviewSearchDateType = "";
|
||||
let procedureReviewSearchStartDate = "";
|
||||
let procedureReviewSearchEndDate = "";
|
||||
|
||||
let procedureReviewSort = "[[${param.procedureReviewSort}]]";
|
||||
let procedureReviewDir = "[[${param.procedureReviewDir}]]";
|
||||
let procedureReviewStart = "[[${param.procedureReviewStart}]]" == "" ? 0 : "[[${param.procedureReviewStart}]]";
|
||||
let procedureReviewLimit = "[[${param.procedureReviewLimit}]]" == "" ? 500 : "[[${param.procedureReviewLimit}]]";
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_content">
|
||||
<!-- 센터쪽 -->
|
||||
<div class="center_box">
|
||||
<p class="page_title">시술후기</p>
|
||||
|
||||
<div class="filter_box">
|
||||
<div class="form_box">
|
||||
|
||||
<!-- 제목 검색 input -->
|
||||
<div class="search_list">
|
||||
<div class="search_box">
|
||||
<img src="/image/web/search_G.svg" alt="search" />
|
||||
<input type="text" id="procedureReviewSearchKeyword1" required placeholder="제목">
|
||||
|
||||
<div class="search_list"></div><!-- 검색내역 나오는곳 -->
|
||||
</div>
|
||||
<button id="btnSearchProcedureReview" class="search_btn" data-toggle="modal"
|
||||
data-target=".work_closed_modal" style="transition: all 0.2s ease-in-out 0s;">조회</button>
|
||||
</div>
|
||||
|
||||
<div class="right_btn_box">
|
||||
<button id="btnInsertProcedureReview" class="treatmentdiet_btn">
|
||||
<img src="/image/web/notice_btn_icon.svg" alt="등록">등록
|
||||
</button>
|
||||
<button id="btnDeleteProcedureReview" class="delete_btn">
|
||||
<img src="/image/web/delete_btn_icon.svg" alt="삭제">삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="procedureReviewGrid" class="table_box ag-theme-balham"></div>
|
||||
|
||||
<!-- 페이지게이션 -->
|
||||
<div class="page_box">
|
||||
<nav aria-label="Page navigation" class="navigation">
|
||||
<ul class="pagination" id="procedureReviewPagination"></ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<form id="procedureReviewSelectListForm" method="POST" target="_blank"></form>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_popup">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_script">
|
||||
<script src="/js/web/ag-grid-community-29.3.5.min.js"></script>
|
||||
<script src="/js/crm/procedureReview/procedureReviewSelectList.js"></script>
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,479 @@
|
||||
<!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="~{/web/layout/homeLayout}">
|
||||
<th:block layout:fragment="layout_css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/web/ContentsBbsUpd.css">
|
||||
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||
<style>
|
||||
/* Quill 에디터 스타일 보정 */
|
||||
.ql-toolbar.ql-snow {
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px 5px 0 0;
|
||||
background: #F9FAFB;
|
||||
}
|
||||
|
||||
.ql-container.ql-snow {
|
||||
border: 1px solid #E9ECF0;
|
||||
border-top: none;
|
||||
border-radius: 0 0 5px 5px;
|
||||
min-height: 350px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 340px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.ql-editor.ql-blank::before {
|
||||
color: #B5BDC4;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 페이지 스크롤 활성화 오버라이드 */
|
||||
.center_box {
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.project_wrap .content_section .hospital_wrap .center_box .content_box {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* 단일 폼 레이아웃 */
|
||||
.pr-single-form {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.pr-single-form .form-grid-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group input[type="text"] {
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #E9ECF0;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.pr-single-form .form-group input[type="text"]:focus {
|
||||
border-color: #3985EA;
|
||||
box-shadow: 0 0 0 2px rgba(57, 133, 234, 0.1);
|
||||
}
|
||||
|
||||
/* 내용 헤더 (라벨 + 도움말 버튼) */
|
||||
.content-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.content-header label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.btn-help {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #3985EA;
|
||||
background: #EFF6FF;
|
||||
border: 1px solid #BFDBFE;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-help:hover {
|
||||
background: #DBEAFE;
|
||||
border-color: #93C5FD;
|
||||
}
|
||||
|
||||
/* 액션 버튼 */
|
||||
.pr-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Quill 에디터 및 미리보기 이미지 크기 제한 */
|
||||
.ql-editor img,
|
||||
#previewContent img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 도움말 모달 */
|
||||
.help-modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.help-modal-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.help-modal {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
width: 640px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.help-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.help-modal-header h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.help-modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: #F3F4F6;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: #6B7280;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.help-modal-close:hover {
|
||||
background: #E5E7EB;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.help-modal-body {
|
||||
padding: 24px;
|
||||
font-size: 13px;
|
||||
color: #4B5563;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.help-modal-body h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
margin: 20px 0 8px 0;
|
||||
}
|
||||
|
||||
.help-modal-body h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.help-modal-body table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 8px 0 16px 0;
|
||||
}
|
||||
|
||||
.help-modal-body table th {
|
||||
background: #F9FAFB;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border: 1px solid #E5E7EB;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.help-modal-body table td {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #E5E7EB;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.help-modal-body .tip-box {
|
||||
background: #F0F9FF;
|
||||
border: 1px solid #BAE6FD;
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.help-modal-body .tip-box strong {
|
||||
color: #0284C7;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_top_script">
|
||||
<script>
|
||||
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
|
||||
let muProcedureReviewId = "[[${param.muProcedureReviewId}]]";
|
||||
const CDN_URL = "[[${@environment.getProperty('url.cdn')}]]";
|
||||
</script>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_content">
|
||||
<div class="center_box">
|
||||
<p class="page_title" style="float:none; display:block; width:100%;">시술후기 수정</p>
|
||||
|
||||
<div class="pr-single-form">
|
||||
<!-- 제목 -->
|
||||
<div class="form-grid-row">
|
||||
<div class="form-group">
|
||||
<label>제목</label>
|
||||
<input type="text" id="title" placeholder="제목을 입력해주세요." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 내용 (Quill 에디터) -->
|
||||
<div class="form-group" style="margin-bottom: 16px;">
|
||||
<div class="content-header">
|
||||
<label>내용</label>
|
||||
<button type="button" class="btn-help" id="btnHelp">? 도움말</button>
|
||||
</div>
|
||||
<div id="quillEditor"></div>
|
||||
</div>
|
||||
|
||||
<!-- 해시태그 -->
|
||||
<div class="form-grid-row">
|
||||
<div class="form-group">
|
||||
<label>해시태그</label>
|
||||
<input type="text" id="hashtag" placeholder="#해시태그를 입력해주세요." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 버튼 -->
|
||||
<div class="pr-actions">
|
||||
<button class="preview_btn btnPreview"
|
||||
style="width: 90px; height: 36px; border-radius: 4px; background: #fff; color: #3985EA; border: 1px solid #3985EA; font-weight: 600; cursor: pointer;">미리보기</button>
|
||||
<button class="registration_btn btnSave"
|
||||
style="width: 80px; height: 36px; border-radius: 4px;">수정</button>
|
||||
<button class="cancel_btn btnCancle"
|
||||
style="width: 80px; height: 36px; border-radius: 4px; margin-left: 10px;">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 미리보기 모달 -->
|
||||
<div class="help-modal-overlay" id="previewModal">
|
||||
<div class="help-modal" style="width: 800px; max-height: 90vh;">
|
||||
<div class="help-modal-header">
|
||||
<h3>👁 미리보기</h3>
|
||||
<button class="help-modal-close" id="previewModalClose">✕</button>
|
||||
</div>
|
||||
<div class="help-modal-body" style="padding: 0;">
|
||||
<!-- 제목 영역 -->
|
||||
<div style="padding: 20px 24px; border-bottom: 1px solid #E5E7EB;">
|
||||
<p style="font-size: 11px; color: #9CA3AF; margin-bottom: 4px;">제목</p>
|
||||
<h2 id="previewTitle" style="font-size: 20px; font-weight: 700; color: #1F2937; margin: 0;"></h2>
|
||||
</div>
|
||||
<!-- 내용 영역 -->
|
||||
<div style="padding: 24px;">
|
||||
<div id="previewContent" class="ql-editor"
|
||||
style="min-height: 200px; max-height: 500px; overflow-y: auto; padding: 0;"></div>
|
||||
</div>
|
||||
<!-- 해시태그 영역 -->
|
||||
<div id="previewHashtagArea" style="padding: 12px 24px 20px; border-top: 1px solid #E5E7EB;">
|
||||
<span id="previewHashtag"
|
||||
style="display: inline-block; padding: 4px 12px; background: #EFF6FF; color: #3985EA; border-radius: 20px; font-size: 13px; font-weight: 500;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 도움말 모달 -->
|
||||
<div class="help-modal-overlay" id="helpModal">
|
||||
<div class="help-modal">
|
||||
<div class="help-modal-header">
|
||||
<h3>📝 에디터 사용 가이드</h3>
|
||||
<button class="help-modal-close" id="helpModalClose">✕</button>
|
||||
</div>
|
||||
<div class="help-modal-body">
|
||||
<h4>📌 텍스트 서식</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
<th>단축키</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>B</strong> 굵게</td>
|
||||
<td>선택한 텍스트를 굵게 표시</td>
|
||||
<td>Ctrl+B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><em>I</em> 기울임</td>
|
||||
<td>선택한 텍스트를 기울임꼴로 표시</td>
|
||||
<td>Ctrl+I</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><u>U</u> 밑줄</td>
|
||||
<td>선택한 텍스트에 밑줄 적용</td>
|
||||
<td>Ctrl+U</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><s>S</s> 취소선</td>
|
||||
<td>선택한 텍스트에 취소선 적용</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>🎨 글자 스타일</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>헤더 (H1~H6)</td>
|
||||
<td>제목 크기를 설정합니다. 숫자가 작을수록 큰 제목</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>폰트</td>
|
||||
<td>글꼴을 변경합니다</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>글자 크기</td>
|
||||
<td>Small, Normal, Large, Huge 중 선택</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>글자색 / 배경색</td>
|
||||
<td>텍스트 색상 또는 배경 하이라이트 색상 선택</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>📋 문단 서식</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>순서 목록</td>
|
||||
<td>1. 2. 3. 형태의 번호 목록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>비순서 목록</td>
|
||||
<td>• 형태의 글머리 기호 목록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>체크 목록</td>
|
||||
<td>☐ 형태의 체크박스 목록</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>들여쓰기</td>
|
||||
<td>텍스트를 안쪽/바깥쪽으로 이동</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>정렬</td>
|
||||
<td>좌측, 중앙, 우측, 양쪽 정렬</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>인용문</td>
|
||||
<td>인용 블록으로 표시</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>코드 블록</td>
|
||||
<td>코드 형태로 표시</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>🔗 멀티미디어</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>링크</td>
|
||||
<td>선택한 텍스트에 URL 링크 삽입</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>이미지</td>
|
||||
<td>이미지 파일을 선택하여 삽입 (서버에 자동 업로드)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>동영상</td>
|
||||
<td>YouTube 등 동영상 URL을 삽입</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>🧹 기타</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>서식 지우기</td>
|
||||
<td>선택한 텍스트의 모든 서식을 초기화</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>위첨자 / 아래첨자</td>
|
||||
<td>X² 또는 H₂O 같은 수식 표현</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="tip-box">
|
||||
<strong>💡 TIP</strong><br>
|
||||
• 텍스트를 드래그한 후 툴바 버튼을 클릭하면 선택 영역에만 서식이 적용됩니다.<br>
|
||||
• 이미지는 클립보드에서 Ctrl+V로 붙여넣기도 가능합니다.<br>
|
||||
• 실행 취소는 Ctrl+Z, 다시 실행은 Ctrl+Y 입니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_popup">
|
||||
</th:block>
|
||||
<th:block layout:fragment="layout_script">
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
|
||||
<script src="/js/crm/procedureReview/procedureReviewUpdate.js"></script>
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user