Compare commits

...

20 Commits

Author SHA1 Message Date
이진기
37bc3f423c 문서 추가 2024-12-19 13:01:04 +09:00
이진기
69d84c4706 ReadMe 추가 2024-12-06 09:15:03 +09:00
d84a3313e3 타입 변경 2024-11-30 11:40:29 +09:00
이진기
f786df1d9b /admin/content/list 페이징 처리와 검색 조건 기능 추가 2024-11-27 16:23:07 +09:00
이진기
40a1c2740d 게시판 페이징 처리 추가 2024-11-26 15:08:22 +09:00
이진기
dae8e6d3a7 /admin/content/list 프론트 기능 추가 2024-11-26 14:46:19 +09:00
이진기
505a3e5101 모델 형식 수정 2024-11-26 10:08:00 +09:00
이진기
a697185451 Contents API 추가 수정 2024-11-25 18:12:18 +09:00
이진기
77c0a7d43f Contents API 추가 2024-11-20 15:06:33 +09:00
이진기
843db73d12 시스템관리 > 권한관리 > 사이트관리 화면과 API 추가 2024-11-19 16:01:42 +09:00
이진기
aed1321d88 Vue 로그인 경로 admin 추가 2024-11-19 15:51:49 +09:00
이진기
aef25bc3dc Site API 추가 (JpaRepository 와 Dao 연결) 2024-11-19 15:45:40 +09:00
이진기
711bba3e4b Dao에서 선언한 함수로 호출하게 변경 2024-11-19 11:26:36 +09:00
이진기
13b469d26f AsaSite API 추가 (자동 생성된 findAll 사용) 2024-11-19 11:09:00 +09:00
2635c22f27 왼쪽 주매뉴 API 적용 2024-11-17 08:48:39 +09:00
8503ef4f3c 로그인 API 수정 2024-11-17 01:40:14 +09:00
이진기
fcfbd1fd96 로그인 백엔드 구현 및 로그인 프론트 기능 구현 2024-11-15 14:46:38 +09:00
이진기
001bba7cb1 JAP 설정및 로그인 테이블 연결 2024-11-15 11:52:59 +09:00
이진기
940a0320b3 로그인, 아이디찾기, 비밀번호찾기, 가입시 권장사항 화면 추가 2024-11-11 18:06:35 +09:00
이진기
7f3bd6ef65 ant desing 플러그인 삭제 2024-11-11 17:26:34 +09:00
92 changed files with 3792 additions and 370 deletions

1
.idea/compiler.xml generated
View File

@@ -2,6 +2,7 @@
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<annotationProcessing> <annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true"> <profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" /> <sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />

4
.idea/dataSources.xml generated
View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true"> <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="profile@leejk0523.com" uuid="48fe1c90-2c8b-4422-ab16-143a998890c0"> <data-source source="LOCAL" name="pub@leejk0523.com" uuid="ba69e241-137d-4c9f-b047-5010d887b198">
<driver-ref>mariadb</driver-ref> <driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize> <synchronize>true</synchronize>
<imported>true</imported> <imported>true</imported>
<remarks>$PROJECT_DIR$/java/src/main/resources/application.properties</remarks> <remarks>$PROJECT_DIR$/java/src/main/resources/application.properties</remarks>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver> <jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://leejk0523.com:3306/profile</jdbc-url> <jdbc-url>jdbc:mariadb://leejk0523.com:3306/pub</jdbc-url>
<jdbc-additional-properties> <jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" /> <property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" /> <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />

1
.idea/encodings.xml generated
View File

@@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="Encoding"> <component name="Encoding">
<file url="file://$PROJECT_DIR$/java/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/java/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/java/target/generated-sources/java" charset="UTF-8" />
</component> </component>
</project> </project>

1
.idea/modules.xml generated
View File

@@ -3,6 +3,7 @@
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/java_vue.iml" filepath="$PROJECT_DIR$/.idea/java_vue.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/java_vue.iml" filepath="$PROJECT_DIR$/.idea/java_vue.iml" />
<module fileurl="file://$PROJECT_DIR$/java/javavue.iml" filepath="$PROJECT_DIR$/java/javavue.iml" />
</modules> </modules>
</component> </component>
</project> </project>

124
.idea/uiDesigner.xml generated Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

40
java/README.md Normal file
View File

@@ -0,0 +1,40 @@
### 기본 환경
- Java 11
- MyBatis 4.x
- JPA 2.2
- QueryDSL 5.0
### 개발 규약
- 사용하지 않는 Parameter는 넣지 않는다.
- Request, Response 호출은 EgovRequestUtils을 통해서만 한다.
- 관리자 Session 정보는 EgovAdminSessionUtils로 호출한다.(MultipartRequest는 제외)
- 사용자 Session 정보는 EgovUserSessionUtils로 호출한다.
- Spring context에서 벗어나게 개발하지 않는다.
### 어노테이션
- API Controller: @RestController
- Page Controller: @Controller
- Service: @Service
- Mapper: @Mapper
- DAO 또는 별도 컴포넌트들: @Component
### API URL 규약
- 대국민포털(prefix): 없음
- 참여기관/관리자포털(prefix): /admin
- 페이지: /**
- api: /api/**
- 메뉴 등록 시 full url로 기재할 것!
- 페이지 메뉴는 도메인으로 분기 처리되므로 동일 URL 사용 가능
- id 조회의 경우 반드시 query 처리할 것!
### DB 암호화
- 환경변수 추가 필요(안하면 에러 남)
- {project.baseDir}/main/resources/ksing/db 에 있는 모든 파일을 C:\SecureDBAgent 에 복사
- 아래의 정보로 환경변수 세팅 진행
- SDB_HOME / C:\SecureDBAgent
- SDB_FIRST_PORT / 9909
### 비지니스 로직 로깅 관련
- [필수] 비지니스 로직에 로깅할 경우 class 상단에 @Slf4j 어노테이션 선언
- [필수] Service에서는 전자정부프레임워크에서 제공하는 egovLogger를 사용

29
java/git.md Normal file
View File

@@ -0,0 +1,29 @@
### 임시 git 설정(SSH 터널링)
```
ssh -i KLAC_SYS_ADM.pem -o ServerAliveInterval=60 -L 8081:localhost:8081 -L 5000:localhost:3000 rocky@192.168.30.7 -p 6722
```
### 브랜치 용도
- master: 운영 서버 반영 브랜치
- dev: 개발 서버 반영 브랜치
- mix: 개발용 브랜치
### 신규 브랜치 작성 방법
- feature/gitea아이디/번호
- 예) feature/natoro/1
- push 할 때마다 뒷 번호는 증가
### git 사용 방법
1. 작업하기 전 반드시 mix 브랜치에서 new branch로 생성
2. 작업이 완료될 경우 반드시 commit
3. 새로운 내용을 받을 때 pull(예: mix 브랜치 pull)
4. 작업한 내용을 서버에 올릴 때 push
5. 개발자는 merge 작업하지 말 것!
6. 작업 단위는 작게 진행(기능별로 분리해서 branch 작업)
7. 커미터, 기능별 태그 관리 및 버전 관리할 것
### 소스 관리 및 반영 관리(수정)
1. pull request 처리 주기
2. 코드 리뷰 주기
3. 반영 주기
4. 예) 수요일 - 오후 4시 pull request 처리

8
java/mapper.md Normal file
View File

@@ -0,0 +1,8 @@
Mapper 사용 시 아래와 같이 작성(중괄호는 치환되는 명칭)
```
package: egovframework.com.lasp.{업무명}.mapper
Mapper파일명: {Admin | User}{업무명}Mapper.xml
annotation: org.egovframe.rte.psl.dataaccess.mapper.Mapper
xml파일: \resources\egovframework\sqlmap\mappers\{Mapper명과 동일}.xml
```

View File

@@ -1,36 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version> <version>3.3.5</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.leejk0523</groupId> <groupId>com.leejk0523</groupId>
<artifactId>javavue</artifactId> <artifactId>javavue</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging> <packaging>war</packaging>
<name>javavue</name> <name>javavue</name>
<description>javavue</description> <description>javavue</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties> <properties>
<java.version>17</java.version> <java.version>17</java.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- Spring Boot Dependencies -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -39,24 +30,48 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.mariadb.jdbc</groupId> <groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId> <artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope> <!-- <version>3.1.2</version>-->
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>jakarta.validation</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId> <artifactId>jakarta.validation-api</artifactId>
<!-- <version>3.1.0</version>-->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <version>1.18.24</version>-->
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>5.0.0</version>
<classifier>jakarta</classifier>
</dependency>
<!-- QueryDSL Annotation Processor -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.0.0</version>
<classifier>jakarta</classifier>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<defaultGoal>package</defaultGoal>
<directory>${basedir}/target</directory>
<finalName>javavue</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@@ -64,5 +79,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project>
</project>

View File

@@ -5,9 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class JavavueApplication { public class JavavueApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(JavavueApplication.class, args); SpringApplication.run(JavavueApplication.class, args);
} }
}
}

View File

@@ -0,0 +1,26 @@
package com.leejk0523.javavue.admin.contents.controller;
import com.leejk0523.javavue.admin.contents.service.AdminContentsService;
import com.leejk0523.javavue.admin.contents.vo.ContentsListResult;
import com.leejk0523.javavue.admin.contents.vo.ContentsPagingQuery;
import com.leejk0523.javavue.model.AsaContent;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class AdminContentsController {
private final AdminContentsService adminContentsService;
@GetMapping("/api/admin/contents/contentsList")
public ResponseEntity<Page<ContentsListResult>> findContentsList(ContentsPagingQuery query) {
final var results = adminContentsService.findContentsList(query);
return ResponseEntity.ok(results);
}
}

View File

@@ -0,0 +1,84 @@
package com.leejk0523.javavue.admin.contents.dao;
import ch.qos.logback.core.util.StringUtil;
import com.leejk0523.javavue.admin.contents.vo.ContentsListResult;
import com.leejk0523.javavue.admin.contents.vo.ContentsPagingQuery;
import com.leejk0523.javavue.common.QueryDSLUtils;
import com.leejk0523.javavue.model.AsaContent;
import com.leejk0523.javavue.model.QAsaContent;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import io.micrometer.common.util.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class AdminContentsDao extends QuerydslRepositorySupport {
public AdminContentsDao() {
super(AsaContent.class);
}
public Page<ContentsListResult> findContentsList(ContentsPagingQuery query) {
QAsaContent asaContent = QAsaContent.asaContent;
final var offset = QueryDSLUtils.getOffset(query);
final var limit = QueryDSLUtils.getLimit(query);
final var pageable = QueryDSLUtils.getPageable(query);
BooleanExpression expression = asaContent.delYn.eq("N");
if (StringUtils.isNotEmpty(query.getKeyword())) {
switch (query.getType()) {
case CONTENT: {
expression = expression.and(asaContent.contents.contains(query.getKeyword()));
break;
}
case TITLE : {
expression = expression.and(asaContent.contentTitle.contains(query.getKeyword()));
break;
}
case TOTAL: {
expression = expression.and(asaContent.contents.contains(query.getKeyword())
.or(asaContent.contentTitle.contains(query.getKeyword())));
}
}
}
if (StringUtils.isNotEmpty(query.getOrgId())) {
expression = expression.and(asaContent.orgId.eq(query.getOrgId()));
}
final var list = from(asaContent)
.select(
Projections.bean(
ContentsListResult.class,
asaContent.contentId,
asaContent.contentTitle,
asaContent.orgId,
asaContent.useYn,
asaContent.frstRgtrId,
asaContent.frstRegDt,
asaContent.lastMdfrId,
asaContent.lastMdfcnDt
)
)
.where(expression)
.limit(limit)
.offset(offset)
.orderBy(asaContent.contentId.desc())
.fetch();
final var total = from(asaContent).where(expression).fetchCount();
return new PageImpl<>(list, pageable, total);
}
}

View File

@@ -0,0 +1,12 @@
package com.leejk0523.javavue.admin.contents.service;
import com.leejk0523.javavue.admin.contents.vo.ContentsListResult;
import com.leejk0523.javavue.admin.contents.vo.ContentsPagingQuery;
import com.leejk0523.javavue.model.AsaContent;
import org.springframework.data.domain.Page;
import java.util.List;
public interface AdminContentsService {
Page<ContentsListResult> findContentsList(ContentsPagingQuery query);
}

View File

@@ -0,0 +1,22 @@
package com.leejk0523.javavue.admin.contents.service;
import com.leejk0523.javavue.admin.contents.dao.AdminContentsDao;
import com.leejk0523.javavue.admin.contents.vo.ContentsListResult;
import com.leejk0523.javavue.admin.contents.vo.ContentsPagingQuery;
import com.leejk0523.javavue.model.AsaContent;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class AdminContentsServiceImpl implements AdminContentsService {
private final AdminContentsDao adminContentsDao;
@Override
public Page<ContentsListResult> findContentsList(ContentsPagingQuery query) {
return adminContentsDao.findContentsList(query);
}
}

View File

@@ -0,0 +1,17 @@
package com.leejk0523.javavue.admin.contents.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ContentsListResult {
private Integer contentId;
private String contentTitle;
private String orgId;
private String useYn;
private String frstRgtrId;
private LocalDateTime frstRegDt;
private String lastMdfrId;
private LocalDateTime lastMdfcnDt;
}

View File

@@ -0,0 +1,26 @@
package com.leejk0523.javavue.admin.contents.vo;
import com.leejk0523.javavue.common.PagingQuery;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
@Data
public class ContentsPagingQuery implements PagingQuery {
@NotNull
private int page;
@NotNull
private int size;
private String orgId;
private String siteId;
private String keyword;
private Type type;
public enum Type {
TITLE,
CONTENT,
TOTAL
}
}

View File

@@ -0,0 +1,35 @@
package com.leejk0523.javavue.admin.login.controller;
import com.leejk0523.javavue.admin.login.service.UserLoginService;
import com.leejk0523.javavue.admin.login.vo.In;
import com.leejk0523.javavue.admin.login.vo.Out;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Objects;
@RestController
@RequiredArgsConstructor
public class UserLoginController {
private final UserLoginService userLoginService;
@PostMapping("/api/admin/login")
private ResponseEntity<Out> login(@RequestBody In in) {
final var authorization = userLoginService.login(in);
if (Objects.isNull(authorization)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
return ResponseEntity.ok(authorization);
}
@GetMapping("/api/test")
public String test() {
return "Hello World!";
}
}

View File

@@ -0,0 +1,114 @@
package com.leejk0523.javavue.admin.login.dao;
import com.leejk0523.javavue.admin.login.vo.AdminMember;
import com.leejk0523.javavue.admin.login.vo.In;
import com.leejk0523.javavue.admin.login.vo.Member;
import com.leejk0523.javavue.admin.login.vo.Out;
import com.leejk0523.javavue.model.*;
import com.querydsl.core.types.Projections;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class UserLoginDao extends QuerydslRepositorySupport {
public UserLoginDao() {
super(AsaAdminMember.class);
}
public Member findByMemberId(In in) {
QAsaAdminMember asaAdminMember = QAsaAdminMember.asaAdminMember;
return from(asaAdminMember)
.select(Projections.bean(
AdminMember.class,
asaAdminMember.adminId.as("memberId"),
asaAdminMember.adminName.as("memberName"),
asaAdminMember.adminPassword.as("memberPassword"),
asaAdminMember.adminActive.as("memberEnabled"),
asaAdminMember.adminLoginFailCount.as("memberFailureCount"),
asaAdminMember.adminLock.as("memberLocked"),
asaAdminMember.orgId.as("orgzId")
))
.where(asaAdminMember.adminId.eq(in.getMemberId()))
.fetchOne();
}
public List<Out.Menu> findAllMenuByAdminId(String adminId) {
final var menu = QAsaMenu.asaMenu;
final var permission = QAsaPermission.asaPermission;
final var adminSiteRoleRel = QAsaAdminSiteRoleRel.asaAdminSiteRoleRel;
return from(menu)
.innerJoin(adminSiteRoleRel).on(
menu.siteId.eq(adminSiteRoleRel.siteId)
)
.innerJoin(permission).on(
adminSiteRoleRel.siteId.eq(permission.siteId)
.and(adminSiteRoleRel.roleCode.eq(permission.roleCode))
.and(menu.menuId.eq(permission.menuId))
)
.distinct()
.select(Projections.bean(
Out.Menu.class,
menu.menuId,
menu.upMenuId,
menu.menuName,
menu.menuType,
menu.menuDepth,
menu.menuOrder,
menu.menuUrl
))
.where(
adminSiteRoleRel.adminId.eq(adminId)
.and(adminSiteRoleRel.siteId.eq("admin"))
.and(menu.menuType.ne(AsaMenu.Type.API))
.and(menu.menuStatus.eq(AsaMenu.Status.ENABLED))
)
.orderBy(menu.menuDepth.asc(), menu.menuOrder.asc())
.fetch();
}
public List<Out.PermitApi> findAllPermitApiByAdminId(String adminId) {
final var menu = QAsaMenu.asaMenu;
final var permission = QAsaPermission.asaPermission;
final var adminSiteRoleRel = QAsaAdminSiteRoleRel.asaAdminSiteRoleRel;
return from(menu)
.innerJoin(adminSiteRoleRel).on(
menu.siteId.eq(adminSiteRoleRel.siteId)
)
.innerJoin(permission).on(
adminSiteRoleRel.siteId.eq(permission.siteId)
.and(adminSiteRoleRel.roleCode.eq(permission.roleCode))
.and(menu.menuId.eq(permission.menuId))
)
.distinct()
.select(Projections.bean(
Out.PermitApi.class,
menu.menuUrl
))
.where(
adminSiteRoleRel.adminId.eq(adminId)
.and(adminSiteRoleRel.siteId.eq("admin"))
.and(menu.menuType.eq(AsaMenu.Type.API))
)
.fetch();
}
public List<AsaRole> findRoleCodeByAdminId(String adminId) {
final var adminSiteRole = QAsaAdminSiteRoleRel.asaAdminSiteRoleRel;
final var role = QAsaRole.asaRole;
return from(adminSiteRole)
.innerJoin(role).on(adminSiteRole.roleCode.eq(role.roleCode)
.and(adminSiteRole.siteId.eq(role.siteId))
)
.select(role)
.where(adminSiteRole.adminId.eq(adminId))
.fetch();
}
}

View File

@@ -0,0 +1,8 @@
package com.leejk0523.javavue.admin.login.service;
import com.leejk0523.javavue.admin.login.vo.In;
import com.leejk0523.javavue.admin.login.vo.Out;
public interface UserLoginService {
Out login(In in);
}

View File

@@ -0,0 +1,58 @@
package com.leejk0523.javavue.admin.login.service;
import com.leejk0523.javavue.admin.login.dao.UserLoginDao;
import com.leejk0523.javavue.admin.login.vo.In;
import com.leejk0523.javavue.admin.login.vo.Out;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class UserLoginServiceImpl implements UserLoginService {
private final UserLoginDao userLoginDAO;
@Override
public Out login(In in) {
var member = userLoginDAO.findByMemberId(in);
var menuList = getMenuList(member.getMemberId());
var permitApiList = userLoginDAO.findAllPermitApiByAdminId(member.getMemberId());
return Out.builder()
.memberNo(member.getMemberNo())
.memberName(member.getMemberName())
.menuList(menuList)
.permitApiList(permitApiList)
.authenticated(true)
.build();
}
private List<Out.Menu> getMenuList(String adminId) {
final var menuList = userLoginDAO.findAllMenuByAdminId(adminId);
final var root = menuList.stream()
.filter(menu -> menu.getMenuDepth().equals(1))
.collect(Collectors.toList());
for (var parent : root) {
nestedMenuList(menuList, parent);
}
return root;
}
private void nestedMenuList(List<Out.Menu> list, Out.Menu parent) {
final var children = list.stream()
.filter(child -> Objects.nonNull(child.getUpMenuId())
&& child.getUpMenuId().equals(parent.getMenuId()))
.collect(Collectors.toList());
parent.setChildren(children.isEmpty() ? null : children);
for (var child : children) {
nestedMenuList(list, child);
}
}
}

View File

@@ -0,0 +1,25 @@
package com.leejk0523.javavue.admin.login.vo;
import com.leejk0523.javavue.model.AsaAdminMember;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AdminMember implements Member {
private AsaAdminMember member;
private Long memberNo;
private String memberId;
private String memberName;
private String memberPassword;
private String orgzId;
private Type type;
private Boolean memberEnabled;
private Boolean memberLocked;
private Integer memberFailureCount;
private String plainPassword;
}

View File

@@ -0,0 +1,17 @@
package com.leejk0523.javavue.admin.login.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class In implements Serializable {
private String memberId;
private String password;
}

View File

@@ -0,0 +1,21 @@
package com.leejk0523.javavue.admin.login.vo;
public interface Member {
String getMemberName();
Long getMemberNo();
String getMemberId();
String getMemberPassword();
Type getType();
Boolean getMemberEnabled();
Boolean getMemberLocked();
Integer getMemberFailureCount();
void setPlainPassword(String plainPassword);
String getPlainPassword();
String getOrgzId();
enum Type {
USER,
ADMIN
}
}

View File

@@ -0,0 +1,68 @@
package com.leejk0523.javavue.admin.login.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.leejk0523.javavue.model.AsaMenu;
import com.leejk0523.javavue.model.AsaRole;
import lombok.*;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Out implements Serializable {
private static final long serialVersionUID = 1L;
@JsonIgnore
private Long memberNo;
@JsonIgnore
private String memberId;
@JsonIgnore
private String orgzId;
@JsonIgnore
private List<AsaRole> roles;
@JsonIgnore
private List<String> orgList;
@JsonIgnore
private String accessIpAddress;
private String memberName;
private List<Menu> menuList;
private List<PermitApi> permitApiList;
private boolean authenticated;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class Menu implements Serializable{
private static final long serialVersionUID = -6699279565175110630L;
private String menuId;
private String upMenuId;
private Integer menuDepth;
private String menuName;
private AsaMenu.Type menuType;
private String menuUrl;
private List<Menu> children;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class PermitApi implements Serializable{
private static final long serialVersionUID = 385854228342672220L;
private String menuUrl;
}
}

View File

@@ -0,0 +1,23 @@
package com.leejk0523.javavue.admin.site.controller;
import com.leejk0523.javavue.model.AsaSite;
import com.leejk0523.javavue.admin.site.service.AdminSiteService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class AdminSiteController {
private final AdminSiteService adminSiteService;
@GetMapping("/api/admin/sys/site/siteList")
public ResponseEntity<List<AsaSite>> siteAllList() {
final var results = adminSiteService.SiteAllList();
return ResponseEntity.ok(results);
}
}

View File

@@ -0,0 +1,22 @@
package com.leejk0523.javavue.admin.site.dao;
import com.leejk0523.javavue.model.AsaSite;
import com.leejk0523.javavue.model.QAsaSite;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class AdminSiteDao extends QuerydslRepositorySupport {
public AdminSiteDao() {
super(AsaSite.class);
}
public List<AsaSite> SiteAllList() {
QAsaSite asaSite = QAsaSite.asaSite;
return from(asaSite)
.fetch();
}
}

View File

@@ -0,0 +1,11 @@
package com.leejk0523.javavue.admin.site.dao;
import com.leejk0523.javavue.model.AsaSite;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AdminSiteRepository extends JpaRepository<AsaSite, String> {
}

View File

@@ -0,0 +1,9 @@
package com.leejk0523.javavue.admin.site.service;
import com.leejk0523.javavue.model.AsaSite;
import java.util.List;
public interface AdminSiteService {
List<AsaSite> SiteAllList();
List<AsaSite> findAll();
}

View File

@@ -0,0 +1,26 @@
package com.leejk0523.javavue.admin.site.service;
import com.leejk0523.javavue.admin.site.dao.AdminSiteRepository;
import com.leejk0523.javavue.model.AsaSite;
import com.leejk0523.javavue.admin.site.dao.AdminSiteDao;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class AdminSiteServiceImpl implements AdminSiteService {
private final AdminSiteDao adminSiteDao;
private final AdminSiteRepository adminSiteRepository;
@Override
public List<AsaSite> SiteAllList() {
return adminSiteDao.SiteAllList();
}
@Override
public List<AsaSite> findAll() {
return adminSiteRepository.findAll();
}
}

View File

@@ -0,0 +1,48 @@
package com.leejk0523.javavue.code.dao;
import com.leejk0523.javavue.common.GridCode;
import com.leejk0523.javavue.model.ComCd;
import com.leejk0523.javavue.model.QAsaSite;
import com.leejk0523.javavue.model.QIstInst;
import com.querydsl.core.types.Projections;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class CodeDAO extends QuerydslRepositorySupport {
public CodeDAO() {
super(ComCd.class);
}
public List<GridCode> findSiteCodeList() {
final var site = QAsaSite.asaSite;
return from(site)
.select(
Projections.bean(
GridCode.class,
site.siteId.as("value"),
site.siteName.as("text"),
site.siteName.as("label")
)
)
.fetch();
}
public List<GridCode> findInstCodeList() {
final var inst = QIstInst.istInst;
return from(inst)
.select(
Projections.bean(
GridCode.class,
inst.instNo.as("value"),
inst.instNm.as("label"),
inst.instNm.as("text")
)
)
.fetch();
}
}

View File

@@ -0,0 +1,10 @@
package com.leejk0523.javavue.code.service;
import com.leejk0523.javavue.common.GridCode;
import java.util.List;
public interface CodeService {
List<GridCode> findSiteCodeList();
List<GridCode> findInstCodeList();
}

View File

@@ -0,0 +1,27 @@
package com.leejk0523.javavue.code.service.impl;
import com.leejk0523.javavue.code.dao.CodeDAO;
import com.leejk0523.javavue.code.service.CodeService;
import com.leejk0523.javavue.common.GridCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("lasp.codeServiceImpl")
@RequiredArgsConstructor
public class CodeServiceImpl implements CodeService {
private final CodeDAO codeDAO;
@Override
public List<GridCode> findSiteCodeList() {
return codeDAO.findSiteCodeList();
}
@Override
public List<GridCode> findInstCodeList() {
return codeDAO.findInstCodeList();
}
}

View File

@@ -0,0 +1,29 @@
package com.leejk0523.javavue.code.web;
import com.leejk0523.javavue.code.service.CodeService;
import com.leejk0523.javavue.common.GridCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class CodeController {
private final CodeService codeService;
@GetMapping("/api/admin/code/siteList")
public ResponseEntity<List<GridCode>> siteCodeList() {
final var results = codeService.findSiteCodeList();
return ResponseEntity.ok(results);
}
@GetMapping("/api/admin/code/instList")
public ResponseEntity<List<GridCode>> instCodeList() {
final var results = codeService.findInstCodeList();
return ResponseEntity.ok(results);
}
}

View File

@@ -0,0 +1,10 @@
package com.leejk0523.javavue.common;
import lombok.Data;
@Data
public class GridCode {
private String label;
private String text;
private String value;
}

View File

@@ -0,0 +1,6 @@
package com.leejk0523.javavue.common;
public interface PagingQuery {
int getPage();
int getSize();
}

View File

@@ -0,0 +1,18 @@
package com.leejk0523.javavue.common;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
public class QueryDSLUtils {
public static Pageable getPageable(PagingQuery query) {
return PageRequest.of(query.getPage() - 1, query.getSize());
}
public static long getOffset(PagingQuery query) {
return (long) (query.getPage() - 1) * query.getSize();
}
public static long getLimit(PagingQuery query) {
return query.getSize();
}
}

View File

@@ -1,26 +0,0 @@
package com.leejk0523.javavue.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig {
// @Bean
// public WebMvcConfigurer corsConfigurer() {
// return new WebMvcConfigurer() {
// @Override
// public void addCorsMappings(@Nullable CorsRegistry registry) {
// if (registry != null) {
// registry.addMapping("/api/**")
// .allowedOrigins("http://localhost:8080")
// .allowedMethods("GET", "POST", "PUT", "DELETE")
// .allowedHeaders("*")
// .allowCredentials(true);
// }
// }
// };
// }
}

View File

@@ -1,64 +0,0 @@
package com.leejk0523.javavue.controller;
import com.leejk0523.javavue.entity.UserProfile;
import com.leejk0523.javavue.repository.UserProfileRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api")
public class UserProfileController {
@Autowired
private UserProfileRepository userProfileRepository;
@GetMapping("")
public String test() {
return "Hello World!";
}
@GetMapping("/all")
public List<UserProfile> getAllUsers() {
return userProfileRepository.findAll();
}
// 특정 ID로 사용자 조회
@GetMapping("/{id}")
public UserProfile getUserById(@PathVariable String id) {
return userProfileRepository.findById(id)
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
}
// 이름으로 사용자 조회
@GetMapping("/name/{name}")
public List<UserProfile> getUsersByName(@PathVariable String name) {
return userProfileRepository.findByName(name);
}
@PostMapping
public UserProfile createUser(@RequestBody UserProfile user) {
return userProfileRepository.save(user);
}
@PutMapping("/{id}")
public UserProfile updateUser(@PathVariable String id, @RequestBody UserProfile userDetails) {
UserProfile userProfile = userProfileRepository.findById(id).orElseThrow();
userProfile.setName(userDetails.getName());
userProfile.setPhone(userDetails.getPhone());
userProfile.setAddress(userDetails.getAddress());
return userProfileRepository.save(userProfile);
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable String id) {
userProfileRepository.deleteById(id);
}
}

View File

@@ -1,66 +0,0 @@
package com.leejk0523.javavue.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "UserProfile") // 테이블 이름을 명시적으로 설정
public class UserProfile {
@Id
@Column(name = "id", length = 100)
private String id;
@Column(name = "name", length = 100)
private String name;
@Column(name = "phone", length = 100)
private String phone;
@Column(name = "address", length = 100)
private String address;
@Column(name = "filepath", length = 100)
private String filepath;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getFilepath() {
return filepath;
}
public void setFilepath(String filepath) {
this.filepath = filepath;
}
}

View File

@@ -0,0 +1,170 @@
package com.leejk0523.javavue.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.micrometer.common.util.StringUtils;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*;
import org.hibernate.annotations.ColumnTransformer;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "asa_admin_member")
public class AsaAdminMember {
@Id
@Size(max = 50)
@Column(name = "ADMIN_ID", nullable = false, length = 50)
private String adminId;
@JsonIgnore
@Size(max = 200)
@NotNull
@Column(name = "ADMIN_PASSWORD", nullable = false, length = 200)
private String adminPassword;
@Size(max = 200)
@Column(name = "ADMIN_NAME", length = 200)
private String adminName;
@Size(max = 10)
@Column(name = "ADMIN_SEX", length = 10)
private String adminSex;
@Size(max = 200)
@Column(name = "ADMIN_EMAIL", length = 200)
private String adminEmail;
@Size(max = 100)
@Column(name = "ADMIN_TEL1", length = 100)
private String adminTel1;
@Size(max = 100)
@Column(name = "ADMIN_TEL2", length = 100)
private String adminTel2;
@Size(max = 100)
@Column(name = "ADMIN_TEL3", length = 100)
private String adminTel3;
@Size(max = 100)
@Column(name = "ADMIN_MOBILE1", length = 100)
private String adminMobile1;
@Size(max = 100)
@Column(name = "ADMIN_MOBILE2", length = 100)
private String adminMobile2;
@Size(max = 100)
@Column(name = "ADMIN_MOBILE3", length = 100)
private String adminMobile3;
@Size(max = 50)
@Column(name = "ADMIN_FAX", length = 50)
private String adminFax;
@Size(max = 100)
@Column(name = "ADMIN_ZIPCODE", length = 100)
private String adminZipcode;
@Size(max = 200)
@Column(name = "ADMIN_ADDRESS", length = 200)
private String adminAddress;
@Size(max = 200)
@Column(name = "ADMIN_ADDRESS_DETAIL", length = 200)
private String adminAddressDetail;
@Column(name = "ADMIN_REGDATE")
private LocalDateTime adminRegdate;
@Column(name = "ADMIN_LOGIN_LAST_DATE")
private LocalDateTime adminLoginLastDate;
@Column(name = "ADMIN_PW_LAST_UPDATE")
private LocalDateTime adminPwLastUpdate;
@Size(max = 100)
@Deprecated
@Column(name = "ADMIN_ORGANIZATION", length = 100)
private String adminOrganization;
@Size(max = 100)
@Deprecated
@Column(name = "ADMIN_DEPARTMENT", length = 100)
private String adminDepartment;
@Size(max = 100)
@Deprecated
@Column(name = "ADMIN_TEAM", length = 100)
private String adminTeam;
@Size(max = 100)
@Deprecated
@Column(name = "ADMIN_POSITION", length = 100)
private String adminPosition;
@Size(max = 200)
@Column(name = "ADMIN_ROLE", length = 200)
private String adminRole;
@Size(max = 50)
@Deprecated
@Column(name = "ADMIN_MANAGEMENT_LEVEL", length = 50)
private String adminManagementLevel;
@Column(name = "ADMIN_LOGIN_FAIL_COUNT")
private Integer adminLoginFailCount;
@Column(name = "ADMIN_LOGIN_FAIL_DATE")
private LocalDateTime adminLoginFailDate;
@NotNull
@Column(name = "ADMIN_ACTIVE", nullable = false)
private Boolean adminActive;
@Column(name = "ADMIN_LOCK")
private Boolean adminLock;
@Column(name = "ADMIN_LONG_TERM")
private Boolean adminLongTerm;
@Column(name = "SITE_ID", length = 20, nullable = false)
private String siteId;
@Column(name = "ORG_ID", length = 36)
private String orgId;
@Size(max = 100)
@Column(name = "ADMIN_TEL", length = 100)
private String adminTel;
@Size(max = 100)
@Column(name = "ADMIN_MOBILE", length = 100)
private String adminMobile;
public String getAdminTel() {
if (StringUtils.isNotBlank(this.getAdminTel1())
&& StringUtils.isNotBlank(this.getAdminTel2())
&& StringUtils.isNotBlank(this.getAdminTel3())) {
return this.getAdminTel1() + "-" + this.getAdminTel2() + "-" + this.getAdminTel3();
} else {
return null;
}
}
public String getAdminMobile() {
if (StringUtils.isNotBlank(this.getAdminMobile1())
&& StringUtils.isNotBlank(this.getAdminMobile2())
&& StringUtils.isNotBlank(this.getAdminMobile3())) {
return this.getAdminMobile1() + "-" + this.getAdminMobile2() + "-" + this.getAdminMobile3();
} else {
return null;
}
}
}

View File

@@ -0,0 +1,46 @@
package com.leejk0523.javavue.model;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "asa_admin_site_role_rel")
@IdClass(AsaAdminSiteRoleRel.Key.class)
public class AsaAdminSiteRoleRel {
@Id
@Size(max = 50)
@NotNull
@Column(name = "ADMIN_ID", nullable = false, length = 50)
private String adminId;
@Id
@Size(max = 20)
@NotNull
@Column(name = "SITE_ID", nullable = false, length = 20)
private String siteId;
@Id
@Size(max = 500)
@Column(name = "ROLE_CODE", length = 100)
private String roleCode;
@Data
public static class Key implements Serializable {
private String adminId;
private String siteId;
private String roleCode;
}
}

View File

@@ -0,0 +1,54 @@
package com.leejk0523.javavue.model;
import lombok.Getter;
import lombok.Setter;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
@Getter
@Setter
@Entity
@Table(name = "ASA_CONTENT")
public class AsaContent {
@Id
@SequenceGenerator(name = "SQ_ASA_CONTENT", sequenceName = "SQ_ASA_CONTENT", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_ASA_CONTENT")
@Column(name = "CONTENT_ID", nullable = false)
private Integer contentId;
@Column(name = "ORG_ID", length = 36)
private String orgId;
@Size(max = 500)
@Column(name = "CONTENT_TITLE", length = 500)
private String contentTitle;
@Lob
@Column(name = "CONTENTS")
private String contents;
@Size(max = 4000)
@Column(name = "CONTENT_PLAIN", length = 4000)
private String contentPlain;
@Column(name = "USE_YN", length = 1, nullable = false)
private String useYn;
@Column(name = "DEL_YN", length = 1, nullable = false)
private String delYn;
@Column(name = "FRST_RGTR_ID", length = 50, nullable = false)
private String frstRgtrId;
@Column(name = "FRST_REG_DT", nullable = false)
private LocalDateTime frstRegDt;
@Column(name = "LAST_MDFR_ID", length = 50)
private String lastMdfrId;
@Column(name = "LAST_MDFCN_DT")
private LocalDateTime lastMdfcnDt;
}

View File

@@ -0,0 +1,126 @@
package com.leejk0523.javavue.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Size;
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "asa_menu")
public class AsaMenu {
@Id
@Size(max = 100)
@GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
@GeneratedValue(generator = "uuid")
@Column(name = "menu_id", length = 100, nullable = false)
private String menuId;
@Column(name = "site_id", length = 30, nullable = false)
private String siteId;
@Size(max = 100)
@Column(name = "up_menu_id", length = 100)
private String upMenuId;
@Column(name = "menu_depth", nullable = false)
private Integer menuDepth;
@Column(name = "menu_order", nullable = false)
private Integer menuOrder;
@Size(max = 200)
@Column(name = "menu_name", length = 200, nullable = false)
private String menuName;
@Enumerated(EnumType.STRING)
@Column(name = "menu_type", length = 20, nullable = false)
private Type menuType;
@Enumerated(EnumType.STRING)
@Column(name = "menu_feature", length = 20, nullable = false)
private Feature menuFeature;
@Column(name = "menu_layout", length = 50)
private String menuLayout;
@Column(name = "menu_url", length = 200)
private String menuUrl;
@Column(name = "menu_method", length = 200)
private String menuMethod;
@Column(name = "menu_description", length = 1000)
private String menuDescription;
@Enumerated(EnumType.STRING)
@Column(name = "menu_link_target", length = 20, nullable = false)
private Target menuLinkTarget;
@Column(name = "menu_use_satisfaction", nullable = false)
private Boolean menuUseSatisfaction;
@Column(name = "menu_use_mng_info", nullable = false)
private Boolean menuUseMngInfo;
@Column(name = "menu_mng_id", length = 50)
private String menuMngId;
@Enumerated(EnumType.STRING)
@Column(name = "menu_status", length = 20, nullable = false)
private Status menuStatus;
@Column(name = "menu_reg_date", nullable = false)
private LocalDateTime menuRegDate;
@Column(name = "menu_reg_id", length = 50, nullable = false)
private String menuRegId;
@Column(name = "menu_upd_date")
private LocalDateTime menuUpdDate;
@Column(name = "menu_upd_id", length = 50)
private String menuUpdId;
@Transient
@JsonProperty("_children")
private List<AsaMenu> children;
@Getter
public enum Type {
MENU,
PAGE,
API,
TAB
}
@Getter
public enum Feature {
PAGE,
LIST,
DETAIL,
CREATE,
UPDATE,
DELETE
}
public enum Target {
CURRENT,
BLANK
}
public enum Status {
ENABLED,
HIDDEN,
DISABLED,
}
}

View File

@@ -0,0 +1,45 @@
package com.leejk0523.javavue.model;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "ASA_PERMISSION")
@IdClass(AsaPermission.Key.class)
public class AsaPermission {
@Id
@Size(max = 100)
@NotNull
@Column(name = "ROLE_CODE", nullable = false, length = 100)
private String roleCode;
@Id
@Size(max = 100)
@Column(name = "MENU_ID", length = 100, nullable = false)
private String menuId;
@Id
@Size(max = 20)
@NotNull
@Column(name = "SITE_ID", nullable = false, length = 20)
private String siteId;
@Data
public static class Key implements Serializable {
private String roleCode;
private String menuId;
private String siteId;
}
}

View File

@@ -0,0 +1,61 @@
package com.leejk0523.javavue.model;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "asa_role")
@IdClass(AsaRole.Key.class)
public class AsaRole implements Serializable {
private static final long serialVersionUID = 690922257948242364L;
@Id
@Size(max = 100)
@Column(name = "ROLE_CODE", nullable = false, length = 100)
private String roleCode;
@Id
@Size(max = 20)
@Column(name = "SITE_ID", nullable = false, length = 20)
private String siteId;
@Size(max = 100)
@Column(name = "ROLE_NAME", length = 100)
private String roleName;
@Size(max = 1000)
@Column(name = "ROLE_DESCRIPTION", length = 1000)
private String roleDescription;
@Column(name = "ROLE_REGDATE")
private LocalDateTime roleRegdate;
@Column(name = "ROLE_DEFAULT")
private Integer roleDefault;
@Column(name = "ROLE_ADMIN")
private Integer roleAdmin;
@Column(name = "ROLE_JOIN")
private Integer roleJoin;
@Data
public static class Key implements Serializable {
private static final long serialVersionUID = 1745833326981056763L;
private String roleCode;
private String siteId;
}
}

View File

@@ -0,0 +1,72 @@
package com.leejk0523.javavue.model;
import lombok.*;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import jakarta.validation.constraints.*;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "asa_site")
public class AsaSite {
@Id
@Size(max = 20)
@Column(name = "SITE_ID", length = 20)
private String siteId;
@Size(max = 200)
@Column(name = "SITE_NAME", length = 200)
private String siteName;
@Size(max = 1000)
@Column(name = "SITE_DESCRIPTION", length = 1000)
private String siteDescription;
@Size(max = 500)
@Column(name = "SITE_DOMAIN", length = 500)
private String siteDomain;
@Enumerated(EnumType.STRING)
@Column(name = "SITE_TYPE", length = 50)
private Type siteType;
@Size(max = 20)
@Column(name = "site_prefix", length = 20)
private String sitePrefix;
@Size(max = 11)
@Column(name = "SITE_MAIN", length = 11)
private long siteMain;
@Size(max = 20)
@Column(name = "SITE_LOCALE", length = 20)
private String siteLocale;
@Size(max = 11)
@Column(name = "SITE_LOGO", length = 11)
private Integer siteLogo;
@Column(name = "SITE_REGDATE")
private LocalDateTime siteRegdate;
@Size(max = 50)
@Column(name = "ORG_ID", length = 50)
private String orgId;
@Size(max = 200)
@Column(name = "BSC_URL", length = 200)
private String bscUrl;
@Size(max = 200)
@Column(name = "LGN_URL", length = 200)
private String lgnUrl;
public enum Type {
ADMIN,
USER
}
}

View File

@@ -0,0 +1,110 @@
package com.leejk0523.javavue.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "com_cd")
@IdClass(ComCd.Key.class)
public class ComCd {
@Id
@NotNull
@Size(max = 20)
@Column(name = "CD_GROUP_ID", nullable = false, length = 20)
private String cdGroupId;
@Id
@Size(max = 6)
@NotNull
@Column(name = "COM_CD", nullable = false, length = 6)
private String comCd;
@Size(max = 100)
@Column(name = "COM_CD_NM", length = 100)
private String comCdNm;
@Size(max = 2000)
@Column(name = "COM_CD_EXPLN", length = 2000)
private String comCdExpln;
@Column(name = "SORT_SEQ", nullable = false)
private Integer sortSeq;
@Column(name = "USE_YN", length = 1, nullable = false)
private String useYn;
@Column(name = "DEL_YN", length = 1, nullable = false)
private String delYn;
@Size(max = 20)
@Column(name = "ARTCL_NM1", length = 20)
private String artclNm1;
@Size(max = 20)
@Column(name = "ARTCL_NM2", length = 20)
private String artclNm2;
@Size(max = 20)
@Column(name = "ARTCL_NM3", length = 20)
private String artclNm3;
@Size(max = 20)
@Column(name = "ARTCL_NM4", length = 20)
private String artclNm4;
@Size(max = 20)
@Column(name = "ARTCL_NM5", length = 20)
private String artclNm5;
@Size(max = 20)
@Column(name = "ARTCL_NM6", length = 20)
private String artclNm6;
@Size(max = 20)
@Column(name = "ARTCL_NM7", length = 20)
private String artclNm7;
@Size(max = 20)
@Column(name = "ARTCL_NM8", length = 20)
private String artclNm8;
@Size(max = 20)
@Column(name = "ARTCL_NM9", length = 20)
private String artclNm9;
@Size(max = 20)
@Column(name = "ARTCL_NM10", length = 20)
private String artclNm10;
@Column(name = "FRST_RGTR_ID", length = 50, nullable = false)
private String frstRgtrId;
@Column(name = "FRST_REG_DT", nullable = false)
private LocalDateTime frstRegDt;
@Column(name = "LAST_MDFR_ID", length = 50)
private String lastMdfrId;
@Column(name = "LAST_MDFCN_DT")
private LocalDateTime lastMdfcnDt;
@Data
public static class Key implements Serializable {
private static final long serialVersionUID = -3281903370957728656L;
private String cdGroupId;
private String comCd;
}
}

View File

@@ -0,0 +1,52 @@
package com.leejk0523.javavue.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "IST_INST")
public class IstInst {
@Id
@Column(name = "INST_NO", length = 10, nullable = false)
private String instNo;
@Column(name = "INST_CLSF_CD", length = 6, nullable = false)
private String instClsfCd;
@Column(name = "INST_NM", length = 200, nullable = false)
private String instNm;
@Column(name = "INST_SRVC_EXPLN", length = 50)
private String instSrvcExpln;
@Column(name = "INST_CN")
private String instCn;
@Column(name = "ATCH_FILE_ID", length = 20)
private String atchFileId;
@Column(name = "USE_YN", length = 1, nullable = false)
private String useYn;
@Column(name = "FRST_RGTR_ID", length = 50, nullable = false)
private String frstRgtrId;
@Column(name = "FRST_REG_DT", nullable = false)
private LocalDateTime frstRegDt;
@Column(name = "LAST_MDFR_ID", length = 50)
private String lastMdfrId;
@Column(name = "LAST_MDFCN_DT")
private LocalDateTime lastMdfcnDt;
}

View File

@@ -1,23 +0,0 @@
package com.leejk0523.javavue.repository;
import com.leejk0523.javavue.entity.UserProfile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserProfileRepository extends JpaRepository<UserProfile, String> {
// 이름으로 검색
List<UserProfile> findByName(String name);
// 전화번호로 검색
List<UserProfile> findByPhone(String phone);
// 주소에 특정 키워드가 포함된 사용자 검색
List<UserProfile> findByAddressContaining(String keyword);
// 파일 경로가 특정 값과 일치하는 사용자 검색
UserProfile findByFilepath(String filepath);
}

View File

@@ -1,28 +1,22 @@
spring.application.name=javavue spring.application.name=javavue
spring.datasource.url=jdbc:mariadb://leejk0523.com:3306/profile spring.datasource.url=jdbc:mariadb://leejk0523.com:3306/pub
spring.datasource.username=leejk0523 spring.datasource.username=leejk0523
spring.datasource.password=Ghtkssk0325 spring.datasource.password=Ghtkssk0325
# none: ???? ???? ??? (???? DB?? ?????) spring.jpa.hibernate.ddl-auto=update
# create-drop: SessionFactory? ??? ? drop? ??? ????, SessionFactory? ??? ? drop? ???? (in-memory DB? ?? ?????)
# create: SessionFactory? ??? ? ?????? drop? ???? ??? DDL? ????
# update: ??? ???? ????
# validate: ??? ???? ??? ???? ???? ??????? ????
spring.jpa.hibernate.ddl-auto=none
# DB? ??? ?? ??? ????
spring.jpa.properties.hibernate.show-sql=true spring.jpa.properties.hibernate.show-sql=true
# SQL 출력
#spring.jpa.show-sql=true
# ???? ??? ??? ??? ??? # SQL 포맷팅
spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.type=trace
# ?? ?? ???? ?? ???? # 바인딩 파라미터 값 로깅
logging.level.org.hibernate.type.descriptor.sql=DEBUG logging.level.org.hibernate.type.descriptor.sql=TRACE
# ?? ?? ?? # SQL에 바인딩된 파라미터 값도 출력
logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.SQL=DEBUG
#UserProfile? ???? ????? ???? ??? ??
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

View File

@@ -1,75 +1,71 @@
# Nuxt Minimal Starter ### 기본 환경
- vue 3.x(5.0.8)
- node (v20.17.0)
- vue-router(최신)
- pinia(최신) - store
- Nuxt(최신) - 프레임워크
- antd 4.x - UI/UX
- yarn (1.22.22)
- 추후 환경은 변경될 수 있음
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. ## 최초 세팅
- terminal(혹은 command창): yarn install
- 실행 시 yarn dev
## Setup ## 개발 시 중요 사항
- 모든 파일은 업무 기준으로 작성됨(예: 사용자 > user)
- 공통사항으로 적용되는 경우 utils 사용할 것!
- 모든 네이밍은 명확한 단어로 사용할 것!
- 반드시 저장할 경우 Eslint, Prettier 적용
Make sure to install dependencies: ## 공통 단어 주의사항
- 비즈니스 로직은 공통이 아님(불허)
- 공통은 언제든지 사용할 수 있는 라이브러리성을 말하는 것임
```bash ## Commit 금지 파일 및 디렉토리
# npm - /node_modules/
npm install - 기타 IDE 환경 파일(.classpath 등등)
# pnpm ### 페이지 작성
pnpm install - pages/도메인/index.vue
- 페이지는 controller와 같은 역할을 함
- useHead를 이용하여 html head 영역을 설정함
# yarn ### 페이지 내 컴포넌트
yarn install - /src/components/도메인/컴포넌트명.vue
# bun ### store 작성 위치
bun install - 디렉토리: /src/stores/도메인/index.ts
### api 작성 위치
- 디렉토리: /src/apis/도메인/index.ts
### Type 지정
- type 및 interface로 타입을 지정하는 경우가 많음
- 법률구조공단은 type으로 모든 객체의 타입을 지정하는 것을 원칙으로 함
- 최대한 undefined를 사용하지 않는 선으로 개발할 것
- 예로 userId: string의 경우 빈값을 표현할 때 userId: '' 형식으로 사용할 것!
- 타입 import 시 아래와 같이 type을 지정해야 함
```
import type {UserItemType} from 'types'
``` ```
## Development Server
Start the development server on `http://localhost:3000`: ### Toast UI Calendar
```
// ES MODULE
import Calendar from '@toast-ui/calendar';
```bash // CSS 적용
# npm import '@toast-ui/calendar/dist/toastui-calendar.min.css';
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
``` ```
## Production ### Toast UI Grid
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
``` ```
// ES MODULE
import 'tui-grid/dist/tui-grid.css';
import 'tui-date-picker/dist/tui-date-picker.min.css';
Locally preview production build: // CSS 적용
import Grid from 'tui-grid';
```bash ```
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

View File

@@ -1,8 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
// import 'ant-design-vue/dist/reset.css';
// import '~/assets/font/PretendardGOV/font.css';
// import '~/assets/css/index.css';
// import 'dayjs/locale/ko';
import 'uno.css'; import 'uno.css';
</script> </script>

View File

@@ -1,37 +0,0 @@
<!-- DateSearch.vue -->
<template>
<div>
<label for="start-date">시작 날짜:</label>
<input type="date" v-model="startDate" id="start-date" />
<label for="end-date">종료 날짜:</label>
<input type="date" v-model="endDate" id="end-date" />
<button @click="searchData">검색</button>
</div>
</template>
<script setup>
import { ref, onMounted, defineEmits } from 'vue';
const startDate = ref('');
const endDate = ref('');
const emit = defineEmits(['search']);
const searchData = () => {
emit('search', { startDate: startDate.value, endDate: endDate.value });
};
const today = new Date();
const tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
startDate.value = formatDate(today);
endDate.value = formatDate(tomorrow);
function formatDate(date) {
return date.toISOString().split('T')[0];
}
</script>
<style scoped>
/* 필요한 스타일 */
</style>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import type { GridCodeType } from '~/types';
const value = defineModel<string>({ default: '' });
const props = defineProps<{
options: GridCodeType[];
className?: string;
selectType?: 'SELECT' | 'ALL';
isLoading?: boolean;
}>();
const selectOptions = computed(() => {
if (props.selectType === 'REQUIRED') {
return [{ label: '선택', value: '' }, ...props.options];
}
if (props.selectType === 'ALL') {
return [{ label: '전체', value: '' }, ...props.options];
}
return props.options;
});
const emit = defineEmits(['change']);
const change = () => {
emit('change');
};
</script>
<template>
<a-select
:class="className"
:options="selectOptions"
:loading="isLoading"
v-model:value="value"
@change="change"
/>
</template>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
const props = defineProps<{
className?: string;
selectType?: 'SELECT' | 'ALL';
}>();
const commonCodeStore = useCommonCodeStore();
const value = defineModel<string>('');
const { data, isLoading } = useQuery({
queryKey: ['INST_CODE_LIST'],
queryFn: async () => {
return await commonCodeStore.searchInstCodeList();
},
staleTime: 60 * 1000,
refetchOnWindowFocus: false,
refetchOnMount: false
});
</script>
<template>
<common-default-select-code
v-if="!isLoading"
:class-name="className"
:select-type="selectType"
:options="data"
v-model="value"
/>
</template>

View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import { useAuthStore } from '~/stores/login';
import { some } from 'lodash-es';
const props = defineProps({
api: {
type: String,
required: true
}
});
const authStore = useAuthStore();
const { permitApiList } = storeToRefs(authStore);
const permit = computed(() => {
return some(permitApiList.value, (value) => {
return props.api === value.menuUrl;
});
});
</script>
<template>
<template v-if="permit">
<slot />
</template>
</template>
<style scoped></style>

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import { Editor } from '@toast-ui/editor';
import '@toast-ui/editor/dist/toastui-editor.css';
const editorRef = ref();
const instance = ref();
const emit = defineEmits(['change']);
const props = defineProps<{ initialValue: string }>();
onMounted(() => {
instance.value = new Editor({
el: editorRef.value,
initialValue: props.initialValue ?? '',
initialEditType: 'wysiwyg',
hideModeSwitch: true,
usageStatistics: false,
events: {
keyup: () => {
if (instance.value) {
emit('change', instance.value.getHTML());
}
}
}
});
});
</script>
<template>
<div ref="editorRef"></div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,136 @@
<script setup lang="ts">
import 'tui-grid/dist/tui-grid.css';
import 'tui-date-picker/dist/tui-date-picker.min.css';
import Grid, { type RowKey } from 'tui-grid';
import { cloneDeep } from 'lodash-es';
import type { DataGridType } from '~/types/data/grid';
import type { GridEventName, OptRow } from 'tui-grid/types/options';
import { TUI_GRID_THEME } from '~/constants/theme/grid';
Grid.setLanguage('ko', {
display: {
noData: '조회된 데이터가 존재하지 않습니다.'
}
});
Grid.applyTheme('default', TUI_GRID_THEME);
const gridRef = ref();
const instance = ref<Grid>();
const props = defineProps<DataGridType>();
onMounted(() => {
const gridOptions = {
el: gridRef.value,
columns: props.columns ?? [],
data: cloneDeep(props.data) ?? [],
bodyHeight: props.bodyHeight,
columnOptions: props.columnOptions,
keyColumnName: props.keyColumnName,
width: props.width,
heightResizable: props.heightResizable,
minBodyHeight: props.minBodyHeight,
rowHeight: props.rowHeight ?? 40,
minRowHeight: props.minRowHeight,
scrollX: props.scrollX,
scrollY: props.scrollY,
editingEvent: props.editingEvent,
tabMode: props.tabMode,
rowHeaders: props.rowHeaders,
summary: props.summary,
useClientSort: props.useClientSort,
selectionUnit: props.selectionUnit,
showDummyRows: props.showDummyRows,
copyOptions: props.copyOptions,
pageOptions: props.pageOptions,
treeColumnOptions: props.treeColumnOptions,
header: props.header,
usageStatistics: false,
disabled: props.disabled,
draggable: props.draggable,
contextMenu: props.contextMenu
};
if (props.onGridMounted) {
gridOptions['onGridMounted'] = props.onGridMounted;
}
if (props.onGridUpdated) {
gridOptions['onGridUpdated'] = props.onGridUpdated;
}
if (props.onGridBeforeDestroy) {
gridOptions['onGridBeforeDestroy'] = props.onGridBeforeDestroy;
}
instance.value = new Grid(gridOptions);
});
const appendRow = (value: OptRow) => {
instance.value?.appendRow(value);
};
const appendRows = (value: OptRow[]) => {
instance.value?.appendRows(value);
};
const prependRow = (value: OptRow) => {
instance.value?.prependRow(value);
};
const getCheckedRows = () => {
return instance.value?.getCheckedRows();
};
const getData = () => {
return instance.value?.getData();
};
const validate = () => {
return instance.value?.validate();
};
const resetData = (data: OptRow[]) => {
instance.value?.resetData(data);
};
const removeRow = (data: RowKey) => {
instance.value?.removeRow(data);
};
const removeRows = (data: RowKey[]) => {
instance.value?.removeRows(data);
};
const on = (eventName: GridEventName, eventFunction: (event) => void) => {
instance.value?.on(eventName, eventFunction);
};
const off = (eventName: GridEventName) => {
instance.value?.off(eventName);
};
defineExpose({
appendRow,
appendRows,
prependRow,
getCheckedRows,
getData,
validate,
resetData,
on,
off,
removeRow,
removeRows
});
watch(
() => props.data,
(newValue) => {
resetData(newValue);
}
);
</script>
<template>
<div ref="gridRef" />
</template>

View File

@@ -0,0 +1,163 @@
<script setup lang="ts">
import 'tui-pagination/dist/tui-pagination.css';
import type { PaginationType } from '~/types/data/pagination';
import {
DoubleLeftOutlined,
DoubleRightOutlined,
LeftOutlined,
RightOutlined
} from '@ant-design/icons-vue';
const props = defineProps<PaginationType>();
const emit = defineEmits(['change']);
const totalPages = computed(() => Math.ceil(props.totalElements / props.size));
const rangePages = computed(() =>
Array.from({ length: totalPages.value }, (_, i) => i + 1)
);
const ranges = computed(() => {
const ranges: number[][] = [];
do {
ranges.push(rangePages.value.splice(0, props.showPaginationCount));
} while (rangePages.value.length !== 0);
if (ranges[0].length === 0) {
ranges[0][0] = 1;
}
return ranges;
});
const currentRange = computed(() => {
for (const range of ranges.value) {
if (range.some((i) => i === props.page)) {
return range;
}
}
return [];
});
const currentPeriod = computed(() => {
for (let i = 0; i < ranges.value.length; ++i) {
if (ranges.value[i].some((i) => i === props.page)) {
return i;
}
}
return -1;
});
const prevPageNumber = computed(() => {
const range = ranges.value[currentPeriod.value - 1];
if (range) {
return range[range.length - 1];
}
return props.page;
});
const nextPageNumber = computed(() => {
const range = ranges.value[currentPeriod.value + 1];
if (range) {
return range[0];
}
return props.page;
});
const firstPage = () => {
submitPage(1);
};
const prevPage = () => {
submitPage(prevPageNumber.value);
};
const nextPage = () => {
submitPage(nextPageNumber.value);
};
const lastPage = () => {
submitPage(totalPages.value ? totalPages.value : 1);
};
const change = (value) => {
if (props.size !== value) {
emit('change', props.page, value);
}
};
const movePage = (value) => {
submitPage(value);
};
const submitPage = (page: number) => {
if (props.page !== page) {
emit('change', page, props.size);
}
};
const options = [
{ label: '15', value: 15 },
{ label: '30', value: 30 },
{ label: '50', value: 50 },
{ label: '100', value: 100 }
];
</script>
<template>
<a-row justify="space-between" class="mt-5">
<a-col>
<a-space>
<a-select :options="options" :value="size" @change="change" />
<span>/페이지 ( {{ totalElements }})</span>
</a-space>
</a-col>
<a-col
><a-space>
<a-button
type="link"
:icon="h(DoubleLeftOutlined)"
size="small"
class="pagination-icon-size text-black"
@click="firstPage"
/>
<a-button
type="link"
:icon="h(LeftOutlined)"
class="pagination-icon-size text-black"
size="small"
@click="prevPage"
/>
<a-space>
<a-button
size="small"
:type="pageNo === page ? 'default' : `link`"
class="text-black"
v-for="pageNo in currentRange"
:key="`pagination-${pageNo}`"
@click="() => movePage(pageNo)"
>{{ pageNo }}</a-button
>
</a-space>
<a-button
type="link"
:icon="h(RightOutlined)"
size="small"
class="pagination-icon-size text-black"
@click="nextPage"
/>
<a-button
type="link"
:icon="h(DoubleRightOutlined)"
class="pagination-icon-size text-black"
size="small"
@click="lastPage"
/>
</a-space>
</a-col>
<a-col></a-col>
</a-row>
</template>

View File

@@ -1,9 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAuthStore } from '~/stores/login';
const authStore = useAuthStore();
const { loginResponse } = storeToRefs(authStore);
const selectedKeys = ref<string[]>([]); const selectedKeys = ref<string[]>([]);
</script> </script>
<template> <template>
<a-menu v-model:selected-keys="selectedKeys" mode="inline" theme="dark"> <a-menu v-model:selected-keys="selectedKeys" mode="inline" theme="dark">
<!-- <layout-left-menu-item :menu-list="authorization.menuList" />--> <layout-left-menu-item :menu-list="loginResponse.menuList" />
</a-menu> </a-menu>
</template> </template>

View File

@@ -1,26 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
import type { AuthorizationMenuType } from '~/types/login';
defineProps<{
menuList: AuthorizationMenuType[]
}>();
</script> </script>
<template> <template>
<!-- <template v-for="menu in menuList" :key="menu.menuId">--> <template v-for="menu in menuList" :key="menu.menuId">
<!-- <a-menu-item :key="menu.menuId" v-if="!menu.children">--> <a-menu-item :key="menu.menuId" v-if="!menu.children">
<!-- <nuxt-link :to="menu.menuUrl">{{ menu.menuName }}</nuxt-link>--> <nuxt-link :to="menu.menuUrl">{{ menu.menuName }}</nuxt-link>
<!-- </a-menu-item>--> </a-menu-item>
<!-- <a-sub-menu :key="menu.menuId" v-if="menu.children">--> <a-sub-menu :key="menu.menuId" v-if="menu.children">
<!-- <template #title>{{ menu.menuName }}</template>--> <template #title>{{ menu.menuName }}</template>
<!-- <template v-for="child in menu.children">--> <template v-for="child in menu.children">
<!-- <template v-if="!child.children">--> <template v-if="!child.children">
<!-- <a-menu-item :key="child.menuId">--> <a-menu-item :key="child.menuId">
<!-- <nuxt-link :to="child.menuUrl">{{ child.menuName }}</nuxt-link>--> <nuxt-link :to="child.menuUrl">{{ child.menuName }}</nuxt-link>
<!-- </a-menu-item>--> </a-menu-item>
<!-- </template>--> </template>
<!-- <template v-if="child.children">--> <template v-if="child.children">
<!-- <a-sub-menu :key="child.menuId">--> <a-sub-menu :key="child.menuId">
<!-- <template #title>{{ child.menuName }}</template>--> <template #title>{{ child.menuName }}</template>
<!-- <layout-left-menu-item :menu-list="child.children" />--> <layout-left-menu-item :menu-list="child.children" />
<!-- </a-sub-menu>--> </a-sub-menu>
<!-- </template>--> </template>
<!-- </template>--> </template>
<!-- </a-sub-menu>--> </a-sub-menu>
<!-- </template>--> </template>
</template> </template>

View File

@@ -10,13 +10,17 @@ import { LogoutOutlined } from '@ant-design/icons-vue';
</a-button> </a-button>
</a-col> </a-col>
<a-col> <router-link to="/admin/login" style="color: #1890ff">
<a-button @click="'#'"> 로그아웃
<template #icon> </router-link>
<LogoutOutlined />
</template> <!-- <a-col>-->
로그아웃 <!-- <a-button @click="'#'">-->
</a-button> <!-- <template #icon>-->
</a-col> <!-- <LogoutOutlined />-->
<!-- </template>-->
<!-- 로그아웃-->
<!-- </a-button>-->
<!-- </a-col>-->
</a-row> </a-row>
</template> </template>

View File

@@ -0,0 +1,41 @@
import axios, { type AxiosError, type AxiosResponse } from 'axios';
// import { useAuthStore } from '~/stores/login';
// import { useDefaultStore, useLoadingStore } from '~/stores';
const baseURL = import.meta.env.VITE_API_URL as string;
export const useAxios = () => {
// const loadingStore = useLoadingStore();
// const defaultStore = useDefaultStore();
// const { siteInfo } = storeToRefs(defaultStore);
// const authStore = useAuthStore();
const router = useRouter();
const instance = axios.create({
baseURL,
withCredentials: true
});
instance.interceptors.request.use(
(config) => {
return Promise.resolve(config);
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response: AxiosResponse<any, any>) => {
return Promise.resolve(response);
},
(error: AxiosError) => {
if (error.status === 403) {
return router.push('/');
}
return Promise.reject(error);
}
);
return instance;
};

View File

@@ -0,0 +1,20 @@
export const BOOLEANS = [
{ text: '사용', value: 'true' },
{ text: '미사용', value: 'false' }
];
export const YES_OR_NO_CODE_LIST = [
{ text: '예', value: 'true' },
{ text: '아니오', value: 'false' }
];
export const LOCK_CODE_LIST = [
{ text: '정상', value: 'false' },
{ text: '잠김', value: 'true' }
];
export const ADMIN_STATUS_CODE_LIST = [
{ text: '미승인', value: 'NONE' },
{ text: '승인', value: 'APRV' },
{ text: '반려', value: 'RJCT' }
];

View File

@@ -0,0 +1,16 @@
import type { LoginRequestType, LoginResponseType } from '~/types/login';
export const DEFAULT_AUTHENTICATION_VALUE: LoginRequestType = {
memberId: '',
password: '',
remember: false
};
export const DEFAULT_AUTHORIZATION_VALUE: LoginResponseType = {
memberName: '',
deptNm: '',
instNm: '',
menuList: [],
permitApiList: [],
authenticated: false
};

View File

@@ -0,0 +1,36 @@
import type { CellRendererProps } from 'tui-grid/types/renderer';
export class ConditionButtonRenderer {
el: HTMLElement;
constructor(props: CellRendererProps) {
const { rowKey, grid } = props;
const { options } = props.columnInfo.renderer;
const data = grid.getRow(rowKey);
console.log(!data.baAnswerYn);
if (!data.baAnswerYn) {
const el = document.createElement('button');
el.className = 'ant-btn ant-btn-primary';
el.onclick = () => options?.onClick(data);
el.innerHTML = `<span>${options?.buttonName}</span>`;
this.el = el;
} else {
const el = document.createElement('span');
el.innerHTML = options?.spanName;
this.el = el;
}
}
beforeDestroy(): void {}
focused(): void {}
getElement(): Element {
return this.el;
}
mounted(parent: HTMLElement): void {}
render(props: CellRendererProps): void {}
}

View File

@@ -0,0 +1,39 @@
import type { CellRendererProps } from 'tui-grid/types/renderer';
export class ConditionIconButtonRenderer {
el: HTMLElement;
constructor(props: CellRendererProps) {
const { options } = props.columnInfo.renderer;
const data = props.grid.getRow(props.rowKey);
if (options?.condition(data)) {
const el = document.createElement('a');
// @ts-ignore
const { icon } = options;
el.innerHTML = `
<svg focusable="false" data-icon="${icon.name}" width="1em" height="2em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896">
<path d="${icon.icon.children[0].attrs.d}" />
</svg>
`;
el.className = 'ant-btn ant-btn-primary';
el.onclick = () => options?.onClick(data);
this.el = el;
} else {
this.el = document.createElement('span');
}
}
beforeDestroy(): void {}
focused(): void {}
getElement(): Element {
return this.el;
}
mounted(parent: HTMLElement): void {}
render(props: CellRendererProps): void {}
}

View File

@@ -0,0 +1,28 @@
import type { CellRendererProps } from 'tui-grid/types/renderer';
export class FunctionalButtonRenderer {
el: HTMLElement;
constructor(props: CellRendererProps) {
const el = document.createElement('button');
const options = props.columnInfo.renderer.options;
const data = props.grid.getRow(props.rowKey);
el.className = 'ant-btn ant-btn-primary';
el.onclick = () => options?.onClick(data);
el.innerHTML = `<span>${options?.buttonName}</span>`;
this.el = el;
}
beforeDestroy(): void {}
focused(): void {}
getElement(): Element {
return this.el;
}
mounted(parent: HTMLElement): void {}
render(props: CellRendererProps): void {}
}

View File

@@ -0,0 +1,27 @@
import type { CellEditorProps } from 'tui-grid/types/editor';
export class MaxLengthTextEditor {
el: HTMLInputElement;
constructor(props: CellEditorProps) {
const el = document.createElement('input');
el.type = 'text';
el.value = props.value as string;
el.maxLength = props.columnInfo.editor?.options?.maxlength;
el.placeholder = props.columnInfo.editor?.options?.placeholder;
this.el = el;
}
getElement() {
return this.el;
}
getValue() {
return this.el.value;
}
mounted() {
this.el.select();
}
}

View File

@@ -0,0 +1,82 @@
import type { CellRendererProps } from 'tui-grid/types/renderer';
import type { MenuType } from '~/types/sys/menu';
export class MenuSatisChargerRenderer {
el: HTMLElement;
constructor(props: CellRendererProps) {
const el = document.createElement('div');
el.style['gap'] = '8px';
el.className = 'flex justify-center';
const { rowKey, grid } = props;
const options = props.columnInfo.renderer.options as any;
const data = grid.getRow(rowKey) as unknown as MenuType;
if (!data.menuType.endsWith('API')) {
if (data?.menuMngId) {
const { onView, onDelete } = options;
const viewLink = document.createElement('a');
const deleteLink = document.createElement('a');
viewLink.innerHTML = '보기';
viewLink.onclick = () => onView(data);
deleteLink.innerHTML = '삭제';
deleteLink.onclick = () => onDelete(data);
el.append(viewLink, deleteLink);
} else {
const { onEdit } = options;
const link = document.createElement('a');
link.innerHTML = '등록';
link.onclick = () => onEdit(data);
el.append(link);
}
}
this.el = el;
}
beforeDestroy(): void {}
focused(): void {}
getElement(): Element {
return this.el;
}
mounted(parent: HTMLElement): void {}
render(props: CellRendererProps): void {
this.el.innerHTML = '';
const { rowKey, grid } = props;
const options = props.columnInfo.renderer.options as any;
const data = grid.getRow(rowKey) as unknown as MenuType;
if (!data.menuType.endsWith('API')) {
if (data?.menuMngId) {
const { onView, onDelete } = options;
const viewLink = document.createElement('a');
const deleteLink = document.createElement('a');
viewLink.innerHTML = '보기';
viewLink.onclick = () => onView(data);
deleteLink.innerHTML = '삭제';
deleteLink.onclick = () => onDelete(data);
this.el.append(viewLink, deleteLink);
} else {
const { onEdit } = options;
const link = document.createElement('a');
link.innerHTML = '등록';
link.onclick = () => onEdit(data);
this.el.append(link);
}
}
}
}

View File

@@ -0,0 +1,32 @@
import type { CellRendererProps } from 'tui-grid/types/renderer';
export class RadioHeaderRenderer {
el: HTMLElement;
constructor(props: CellRendererProps) {
const { rowKey, grid } = props;
const { options } = props.columnInfo.renderer;
const data = grid.getRow(rowKey);
const el = document.createElement('input');
el.name = 'gridRadio';
el.type = 'radio';
el.className = '';
el.addEventListener('change', () => options?.onChange(data));
this.el = el;
}
beforeDestroy(): void {}
focused(): void {}
getElement(): Element {
return this.el;
}
mounted(parent: HTMLElement): void {}
render(props: CellRendererProps): void {}
}

View File

@@ -0,0 +1,65 @@
import type { OptPreset } from 'tui-grid/types/options';
export const TUI_GRID_THEME: OptPreset = {
selection: {
background: '#4daaf9',
border: '#004082'
},
scrollbar: {
background: '#f5f5f5',
thumb: '#d9d9d9',
active: '#c1c1c1'
},
outline: {
border: '#e1e2e5'
},
area: {
header: {
border: '#e1e2e5',
background: '#f8f8f9'
}
},
row: {
even: {
background: '#EFFAFF'
}
},
cell: {
normal: {
background: 'white',
border: '#eee',
showVerticalBorder: true
},
header: {
background: '#f8f8f9',
showHorizontalBorder: true,
showVerticalBorder: true
},
rowHeader: {
border: '#e1e2e5',
background: '#f8f8f9',
showHorizontalBorder: false,
showVerticalBorder: false
},
editable: {
// 수정 가능 셀 색상은 아래에
background: 'white'
},
selectedHeader: {
background: '#e0e0e0'
},
focused: {
border: '#418ed4'
},
disabled: {
text: '#333',
background: 'white'
},
invalid: {
background: '#D60440'
},
required: {
background: 'white'
}
}
};

12
nuxt/layouts/empty.vue Normal file
View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import locale from 'ant-design-vue/es/locale/ko_KR';
import { DEFAULT_THEME } from '~/constants/theme/ui';
</script>
<template>
<a-config-provider :locale="locale" :theme="DEFAULT_THEME">
<a-row class="w-full h-full">
<slot />
</a-row>
</a-config-provider>
</template>

View File

@@ -21,24 +21,19 @@ export default defineNuxtConfig({
autoImport: true autoImport: true
}, },
devtools: { enabled: true }, devtools: { enabled: true },
plugins: [
'~/plugins/ant-design-vue.ts'
],
css: [
'ant-design-vue/dist/reset.css'
],
modules: [ modules: [
'@pinia/nuxt', '@pinia/nuxt',
'@unocss/nuxt', '@unocss/nuxt',
'@ant-design-vue/nuxt',
'@hebilicious/vue-query-nuxt' '@hebilicious/vue-query-nuxt'
], ],
vite: { vite: {
optimizeDeps: { optimizeDeps: {
include: ['tui-grid', '@ant-design', 'ant-design-vue'] include: ['tui-grid', 'ant-design-vue']
} }
}, },
alias: {}, alias: {},
experimental: { experimental: {
payloadExtraction: false payloadExtraction: true
} }
}); });

View File

@@ -0,0 +1,126 @@
<script setup lang="ts">
import { useContentStore } from '~/stores/contents';
const route = useRoute();
const router = useRouter();
const contentId = route.query.contentId;
const editorRef = ref();
const contentStore = useContentStore();
const { contents, initialValue } = storeToRefs(contentStore);
onBeforeMount(() => {
if (contentId) {
contentStore.searchContents(Number(contentId));
} else {
contentStore.resetContents();
}
});
const save = () => {
contents.value.contents = editorRef.value.getValue();
contentStore
.updateContents()
.then(() => {
message.success('콘텐츠 정보가 저장이 되었습니다.');
moveList();
})
.catch(() => {
message.error('콘텐츠 저장에 실패하였습니다.');
});
};
const moveList = () => {
router.push('/admin/content/list');
};
const nonValid = computed(() => {
return !contents.value.contentTitle;
});
</script>
<template>
<client-only>
<a-space direction="vertical" class="w-full">
<a-card>
<a-row>
<a-col :span="24">
<a-form-item
label="제목"
label-align="left"
:colon="false"
:label-col="{ span: 2 }"
>
<a-input
title="제목"
placeholder="콘텐츠 제목"
v-model:value="contents.contentTitle"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
label="기관"
label-align="left"
:colon="false"
:label-col="{ span: 2 }"
>
<common-inst-code-select v-model:value="contents.orgId" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
label="내용"
label-align="left"
:colon="false"
:label-col="{ span: 2 }"
:wrapper-col="{ span: 22 }"
>
<lazy-data-editor ref="editorRef" :initial-value="initialValue" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item
label="사용여부"
label-align="left"
:colon="false"
:label-col="{ span: 2 }"
:wrapper-col="{ span: 22 }"
>
<a-switch v-model:checked="contents.useYn" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-flex justify="space-between">
<a-space>
<common-permit-button api="/api/admin/contents/updateContents">
<a-button
type="primary"
@click="save"
:disabled="nonValid"
v-if="!contentId"
>저장</a-button
>
<a-button
type="primary"
@click="save"
v-if="contentId != null"
>수정</a-button
>
</common-permit-button>
<a-button type="default" @click="moveList">목록</a-button>
</a-space>
</a-flex>
</a-col>
</a-row>
</a-card>
</a-space>
</client-only>
</template>

View File

@@ -0,0 +1,202 @@
<script setup lang="ts">
import type { OptColumn, OptRowHeader } from 'tui-grid/types/options';
import { useContentStore } from '~/stores/contents';
import type { ContentType } from '~/types/contents';
import { useCommonCodeStore } from '~/stores';
const router = useRouter();
const instCodeList = await useCommonCodeStore().searchInstCodeList();
const contentStore = useContentStore();
const { contentsList, contentsQuery } = storeToRefs(contentStore);
const gridRef = ref();
const gridRowHeaders: OptRowHeader[] = ['checkbox', 'rowNum'];
const columns: OptColumn[] = [
{
name: 'contentId',
hidden: true
},
{
name: 'contentTitle',
header: '콘텐츠제목'
},
{
name: 'orgId',
header: '관리기관',
width: 100,
disabled: true,
formatter: 'listItemText',
resizable: true,
editor: {
type: 'select',
options: {
listItems: instCodeList
}
}
},
{
name: 'useYn',
header: '사용여부',
width: 80,
align: 'center',
},
{
name: 'frstRgtrId',
header: '작성자',
width: 100
},
{
name: 'frstRegDt',
header: '작성일',
width: 130
},
{
name: 'lastMdfrId',
header: '수정자',
width: 100
},
{
name: 'lastMdfcnDt',
header: '수정일',
width: 130
}
];
const contentType = [
{ label: '전체', value: 'TOTAL' },
{ label: '제목', value: 'TITLE' },
{ label: '내용', value: 'CONTENT' }
];
onBeforeMount(() => {
contentStore.searchContentList();
});
onBeforeUnmount(() => {
// contentStore.resetContentListQuery();
});
const search = () => {
contentStore.searchContentList();
};
const list = computed(() => {
return contentsList.value.content;
});
watch(list, (newValue) => {
if (gridRef.value) {
gridRef.value.off('dblclick');
if (newValue.length > 0) {
setTimeout(() => {
gridRef.value.on('dblclick', ({ instance, rowKey }) => {
const row = instance.getRow(rowKey);
editPage(row.contentId);
});
}, 100);
}
}
});
const deleteContentList = async () => {
const checkedRows = gridRef.value.getCheckedRows() as Array<ContentType>;
if (!checkedRows.length) {
message.warn('삭제할 콘텐츠가 없습니다.');
return;
}
Modal.confirm({
type: 'warning',
okText: '예',
cancelText: '아니오',
title: '컨텐츠 삭제',
content: '선택한 컨텐츠를 삭제하시겠습니까?',
onOk: async () => {
const createdRows = checkedRows.filter((row) => row.contentId);
await contentStore.deleteContents(createdRows);
await contentStore.searchContentList();
}
});
};
const movePage = (page: number) => {
contentsQuery.value.page = page;
search();
};
const editPage = (contentId: any) => {
const query = typeof contentId === 'number' ? `?contentId=${contentId}` : '';
router.push(`/admin/content${query}`);
};
</script>
<template>
<client-only>
<a-space direction="vertical" class="w-full">
<a-card>
<a-row :gutter="[16, 8]">
<a-col>
<a-space>
<a-typography-text>관리기관</a-typography-text>
<lazy-common-inst-code-select
key="inst-code-select"
v-model="contentsQuery.orgId"
class-name="w-40"
select-type="ALL"
/>
</a-space>
</a-col>
<a-col>
<a-space>
<a-typography-text>검색어</a-typography-text>
<a-select
title="컨텐츠 구분"
class="w-20"
v-model:value="contentsQuery.type"
:options="contentType"
/>
<a-input title="검색어" placeholder="Search" class="w-60"
v-model:value="contentsQuery.keyword" allow-clear/>
</a-space>
</a-col>
<a-col>
<a-button type="primary" @click="search">검색</a-button>
</a-col>
</a-row>
</a-card>
<a-card>
<a-space direction="vertical" class="w-full">
<a-flex justify="space-between">
<!-- <common-permit-button api="/api/admin/contents/deleteContents">-->
<!-- <a-button type="primary" danger @click="deleteContentList"-->
<!-- >삭제-->
<!-- </a-button>-->
<!-- </common-permit-button>-->
<!-- <common-permit-button api="/api/admin/contents/updateContents">-->
<!-- <a-button type="primary" @click="editPage">추가</a-button>-->
<!-- </common-permit-button>-->
</a-flex>
<data-grid
:key="`board-content-grid-${Math.random()}`"
:row-headers="gridRowHeaders"
:data="contentsList.content"
:columns="columns"
ref="gridRef"
/>
<data-pagination
:key="`pagination-${Math.random()}`"
:total-elements="contentsList.totalElements"
:show-pagination-count="10"
:size="contentsQuery.size"
:page="contentsQuery.page"
@change="movePage"
/>
</a-space>
</a-card>
</a-space>
</client-only>
</template>

View File

@@ -0,0 +1,104 @@
<script setup lang="ts">
import { useAuthStore } from '~/stores/login';
definePageMeta({
layout: 'empty'
});
const router = useRouter();
const store = useAuthStore();
const { loginRequest } = storeToRefs(store);
const login = async () => {
try {
const { data } = await store.LoginAPI();
store.loginResponse = data;
console.log(JSON.stringify(store.loginResponse));
await router.push('/');
} catch (e) {
message.error('아이디 또는 비밀번호를 확인해주세요.');
}
};
const validateLogin = computed(() => {
return loginRequest.value.memberId && loginRequest.value.password;
});
</script>
<template>
<a-row justify="center" class="w-full h-full items-center">
<a-col :span="7.5">
<a-card bordered style="background-color: #f5f5f5; padding: 24px">
<h1
style="
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
"
>
로그인
</h1>
<div style="height: 18px"></div>
<a-form-item>
<a-checkbox v-model:checked="loginRequest.remember">
키보드보안 프로그램적용
</a-checkbox>
<div style="height: 10px"></div>
<div>
<p style="font-size: 12px; color: #888">
안전한 서비스 이용을 위해 키보드보안 프로그램 적용을 권장합니다.
</p>
</div>
</a-form-item>
<a-form :colon="false" label-align="left">
<a-form-item label="아이디" :label-col="{ span: 5 }">
<a-input
v-model:value="loginRequest.memberId"
placeholder="아이디를 입력하세요"
maxLength="20"
/>
</a-form-item>
<a-form-item label="비밀번호" :label-col="{ span: 5 }">
<a-input-password
v-model:value="loginRequest.password"
placeholder="비밀번호를 입력하세요"
maxLength="20"
@keyup.enter="login()"
/>
</a-form-item>
<a-form-item>
<a-button
type="primary"
block
@click="login()"
:disabled="!validateLogin"
style="font-weight: bold"
>
로그인
</a-button>
</a-form-item>
<div style="display: flex; justify-content: center; margin-top: 16px">
<router-link to="#" style="color: #1890ff"
>아이디 찾기</router-link
>
<span style="margin: 0 8px; color: #888">|</span>
<router-link to="#" style="color: #1890ff"
>비밀번호 찾기</router-link
>
<span style="margin: 0 8px; color: #888">|</span>
<router-link to="#" style="color: #1890ff"
>회원가입</router-link
>
</div>
</a-form>
</a-card>
</a-col>
</a-row>
</template>

View File

@@ -0,0 +1,192 @@
<script setup lang="ts">
import type { OptColumn, OptRowHeader } from 'tui-grid/types/options';
import { useSiteStore } from '~/stores/sys/site';
import type { SiteType } from '~/types/sys/site';
import { MaxLengthTextEditor } from '~/constants/theme/grid/MaxLengthTextEditor';
const siteStore = useSiteStore();
const { siteList } = storeToRefs(siteStore);
const gridRef = ref();
const gridRowHeaders: OptRowHeader[] = ['checkbox', 'rowNum'];
const columns: OptColumn[] = [
{
name: 'siteId',
header: '사이트 ID',
width: 80,
editor: {
type: 'text'
},
validation: {
required: true
}
},
{
name: 'siteName',
header: '사이트명',
width: 120,
editor: {
type: 'text'
},
validation: {
required: true
}
},
{
name: 'siteDescription',
header: '사이트 설명',
width: 200,
editor: {
type: 'text'
}
},
{
name: 'siteDomain',
header: '사이트 도메인',
width: 150,
editor: {
type: 'text'
},
validation: {
required: true
}
},
{
name: 'siteType',
header: '사이트 형식',
width: 80,
formatter: 'listItemText',
editor: {
type: 'select',
options: {
listItems: [
{ text: '사용자', value: 'USER' },
{ text: '관리자', value: 'ADMIN' }
]
}
},
validation: {
required: true
}
},
{
name: 'sitePrefix',
header: '사이트 구분',
width: 120,
editor: {
type: 'select',
options: {
listItems: [
{ text: '대국민포털', value: 'portal' },
{ text: '참여기관포털', value: 'admin' }
]
}
}
},
{
name: 'lgnUrl',
header: '로그인 URL',
width: 120,
editor: {
type: MaxLengthTextEditor,
options: {
maxlength: 200,
placeholder: '로그인 URL을 입력해주세요.'
}
}
},
{
name: 'bscUrl',
header: '기본 URL',
width: 120,
editor: {
type: MaxLengthTextEditor,
options: {
maxlength: 200,
placeholder: '로그인 후 이동될 URL을 입력해주세요.'
}
}
},
{ name: 'siteRegdate', header: '등록일시', width: 140 }
];
onBeforeMount(() => {
siteStore.searchSiteList();
});
const save = () => {
if (gridRef.value.validate().length) {
alert('len:::' + gridRef.value.validate().length);
message.warn('입력된 사이트 정보를 확인해주세요.');
return;
}
Modal.confirm({
type: 'info',
title: '사이트 정보 저장',
content: '선택한 사이트 정보를 저장하시겠습니까?',
okText: '예',
cancelText: '아니오',
onOk: async () => {
try {
const data = gridRef.value.getData();
siteStore.updateSiteList(data);
} catch (e) {
message.error('사이트 정보를 저장하는 도중 에러가 발생되었습니다.');
}
}
});
};
const deleteSiteList = () => {
const checkedRows = gridRef.value.getCheckedRows() as Array<SiteType>;
if (!checkedRows.length) {
message.warn('삭제할 사이트가 없습니다.');
return;
}
const params = new URLSearchParams();
checkedRows.forEach((value) => {
params.append('siteId', value.siteId);
});
};
const addRow = () => {
gridRef.value.appendRow({});
};
</script>
<template>
<client-only>
<a-space direction="vertical" style="width: 100%">
<a-card>
<a-space direction="vertical" style="width: 100%">
<a-flex justify="space-between">
<common-permit-button api="/api/admin/sys/site/deleteSiteList">
<a-button type="primary" danger @click="deleteSiteList"
>삭제</a-button
>
</common-permit-button>
<common-permit-button api="/api/admin/sys/site/updateSiteList">
<a-space>
<a-button type="primary" @click="addRow">추가</a-button>
<a-button type="primary" @click="save">저장</a-button>
</a-space>
</common-permit-button>
</a-flex>
<data-grid
:key="`site-grid-${Math.random()}`"
:row-headers="gridRowHeaders"
:data="siteList"
:columns="columns"
ref="gridRef"
/>
</a-space>
</a-card>
</a-space>
</client-only>
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
import { defineNuxtPlugin } from '#app';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Antd);
});

View File

@@ -0,0 +1,140 @@
import type {
ContentListQueryType,
ContentListType,
ContentType
} from '~/types/contents';
import { cloneDeep } from 'lodash-es';
import type { Page } from '~/types/common';
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useAxios } from '~/composables/useAxios';
import { message } from 'ant-design-vue';
const DEFAULT_CONTENT_QUERY: ContentListQueryType = {
keyword: '',
orgId: '',
page: 1,
siteId: '',
size: 10,
type: 'TOTAL'
};
const DEFAULT_CONTENTS_LIST: Page<ContentListType> = {
content: [],
totalElements: 0,
totalPages: 0
};
const DEFAULT_CONTENTS: ContentType = {
contentTitle: '',
contentPlain: '',
contents: '',
frstRegDt: '',
frstRgtrId: '',
lastMdfcnDt: '',
lastMdfrId: '',
orgId: '',
siteId: '',
useYn: true
};
export const useContentStore = defineStore('useContentStore', () => {
const contentsQuery = ref<ContentListQueryType>(
cloneDeep(DEFAULT_CONTENT_QUERY)
);
const contentsList = ref<Page<ContentListType>>(
cloneDeep(DEFAULT_CONTENTS_LIST)
);
const contents = ref<ContentType>(cloneDeep(DEFAULT_CONTENTS));
const initialValue = ref<string>('');
const resetContentListQuery = () => {
contentsQuery.value = cloneDeep(DEFAULT_CONTENT_QUERY);
};
const resetContentList = () => {
contentsList.value = cloneDeep(DEFAULT_CONTENTS_LIST);
};
const resetContents = () => {
contents.value = cloneDeep(DEFAULT_CONTENTS);
initialValue.value = '';
};
const initContentsQuery = () => {
contentsQuery.value = cloneDeep(DEFAULT_CONTENT_QUERY);
};
const searchContentList = async () => {
try {
const { data } = await useAxios().get(
'/api/admin/contents/contentsList',
{
params: {
...contentsQuery.value
}
}
);
contentsList.value = data;
} catch (e) {
message.error('사이트 리스트를 불러오는데 실패하였습니다.');
}
};
const searchContents = async (contentId: number) => {
try {
const { data } = await useAxios().get('/api/admin/contents/detail', {
params: {
contentId
}
});
contents.value = data;
initialValue.value = data.contents;
} catch (e) {
message.error('컨텐츠 정보를 불러오는데 실패하였습니다.');
}
};
const updateContents = () => {
console.log(contents.value);
return useAxios().post(
'/api/admin/contents/updateContents',
contents.value
);
};
const deleteContents = async (data: ContentType[]) => {
try {
const params = new URLSearchParams();
data.forEach((item) => {
params.append('contentId', String(item.contentId));
});
await useAxios().post('/api/admin/contents/deleteContents', null, {
params
});
message.success('컨텐츠 정보가 삭제 되었습니다.');
} catch (e) {
message.error('컨텐츠 정보 삭제에 실패하였습니다.');
}
};
return {
contentsQuery,
contentsList,
contents,
initialValue,
resetContentListQuery,
searchContentList,
searchContents,
resetContentList,
resetContents,
initContentsQuery,
deleteContents,
updateContents
};
});

80
nuxt/stores/index.ts Normal file
View File

@@ -0,0 +1,80 @@
import type { SiteType } from '~/types/sys/site';
import type { GridCodeType } from '~/types';
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import { useAxios } from '~/composables/useAxios';
export const useLoadingStore = defineStore('useLoadingStore', () => {
const loadCount = ref<number>(0);
const incrementLoadCount = () => {
loadCount.value++;
};
const decrementLoadCount = () => {
loadCount.value--;
};
const resetLoadCount = () => {
loadCount.value = 0;
};
const isLoading = computed(() => {
return loadCount.value > 0;
});
return {
isLoading,
resetLoadCount,
incrementLoadCount,
decrementLoadCount
};
});
export const useDefaultStore = defineStore('useDefaultStore', () => {
const siteInfo = ref<SiteType>({
siteId: '',
siteName: '',
siteDescription: '',
siteDomain: '',
siteType: '',
sitePrefix: '',
siteLocale: '',
siteLogo: '',
bscUrl: '',
lgnUrl: '',
delYn: false,
useYn: true,
frstRgtrId: '',
frstRegDt: '',
lastMdfrId: '',
lastMdfcnDt: ''
});
const fetchSiteInfo = async () => {
const { data } = await useAxios().get<SiteType>('/api/admin/siteInfo');
siteInfo.value = data;
};
return { siteInfo, fetchSiteInfo };
});
export const useCommonCodeStore = defineStore('useCommonCodeStore', () => {
const searchSiteCodeList = async (): Promise<GridCodeType[]> => {
const { data } = await useAxios().get('/api/admin/code/siteList');
return data;
};
const searchInstCodeList = async (): Promise<GridCodeType[]> => {
const { data } = await useAxios().get<GridCodeType[]>(
'/api/admin/code/instList'
);
return data;
};
return {
searchInstCodeList,
searchSiteCodeList
};
});

View File

@@ -0,0 +1,32 @@
import {useAxios} from "~/composables/useAxios";
import type {LoginRequestType, LoginResponseType} from "~/types/login";
import { cloneDeep } from 'lodash-es';
import {
DEFAULT_AUTHENTICATION_VALUE,
DEFAULT_AUTHORIZATION_VALUE
} from '~/constants/login';
export const useAuthStore = defineStore('authStore', () => {
const loginRequest = ref<LoginRequestType>(
cloneDeep(DEFAULT_AUTHENTICATION_VALUE)
);
const loginResponse = ref<LoginResponseType>(
cloneDeep(DEFAULT_AUTHORIZATION_VALUE)
);
const LoginAPI = async () => {
return await useAxios().post(`/api/admin/login`, loginRequest.value);
};
const permitApiList = computed(() => {
return loginResponse.value.permitApiList;
});
return {
loginRequest,
loginResponse,
LoginAPI,
permitApiList
};
});

View File

@@ -0,0 +1,34 @@
import type { SiteType } from '~/types/sys/site';
export const useSiteStore = defineStore('useSiteStore', () => {
const siteList = ref<SiteType[]>([]);
const resetSiteList = () => {
siteList.value = [];
};
const searchSiteList = async () => {
try {
const { data } = await useAxios().get('/api/admin/sys/site/siteList');
siteList.value = data;
} catch (e) {
message.error('사이트 리스트를 불러오는데 실패하였습니다.');
}
};
const updateSiteList = async (data: SiteType[]) => {
try {
await useAxios().post('/api/admin/sys/site/updateSiteList', data);
message.success('사이트 정보가 저장이 되었습니다.');
} catch (e) {
message.error('사이트 저장에 실패하였습니다.');
}
};
return {
siteList,
resetSiteList,
searchSiteList,
updateSiteList
};
});

View File

@@ -0,0 +1,10 @@
export type Page<T> = {
content: T[];
totalElements: number;
totalPages: number;
};
export type PagingQuery = {
page: number;
size: number;
};

View File

@@ -0,0 +1,32 @@
export type ContentListQueryType = {
page: number;
size: number;
siteId: string;
orgId: string;
keyword: string;
type: 'TOTAL' | 'TITLE' | 'CONTENT';
};
export type ContentListType = {
contentId: number;
contentTitle: string;
useYn: boolean;
frstRgtrId: string;
frstRegDt: string;
lastMdfrId: string;
lastMdfcnDt: string;
};
export type ContentType = {
contentId?: number;
siteId: string;
orgId: string;
contentTitle: string;
contents: string;
contentPlain: string;
useYn: boolean;
frstRgtrId: string;
frstRegDt: string;
lastMdfrId: string;
lastMdfcnDt: string;
};

50
nuxt/types/data/grid.ts Normal file
View File

@@ -0,0 +1,50 @@
import type {
GridEventListener,
OptColumn,
OptHeader,
OptRow,
OptRowHeader,
OptSummaryData,
OptTree
} from 'tui-grid/types/options';
import type { EditingEvent, TabMode } from 'tui-grid/types/store/focus';
import type { SelectionUnit } from 'tui-grid/types/store/selection';
import type {
ClipboardCopyOptions,
ColumnOptions
} from 'tui-grid/types/store/column';
import type { PageOptions } from 'tui-grid/types/store/data';
import type { CreateMenuGroups } from 'tui-grid/types/store/contextMenu';
export type DataGridType = {
data: OptRow[];
columns: OptColumn[];
bodyHeight?: number | 'fitToParent' | 'auto';
columnOptions?: ColumnOptions;
keyColumnName?: String;
width?: number | 'auto';
heightResizable?: Boolean;
minBodyHeight?: Number;
rowHeight?: number | 'auto';
minRowHeight?: Number;
scrollX?: Boolean;
scrollY?: Boolean;
editingEvent?: EditingEvent;
tabMode?: TabMode;
rowHeaders?: OptRowHeader[];
summary?: OptSummaryData;
useClientSort?: Boolean;
selectionUnit?: SelectionUnit;
showDummyRows?: Boolean;
copyOptions?: ClipboardCopyOptions;
pageOptions?: PageOptions;
treeColumnOptions?: OptTree;
header?: OptHeader;
usageStatistics?: Boolean;
disabled?: Boolean;
onGridMounted?: GridEventListener;
onGridUpdated?: GridEventListener;
onGridBeforeDestroy?: GridEventListener;
draggable?: Boolean;
contextMenu?: CreateMenuGroups;
};

View File

@@ -0,0 +1,7 @@
export type PaginationType = {
page: number;
size: number;
totalElements: number;
showPaginationCount: number;
};

20
nuxt/types/index.ts Normal file
View File

@@ -0,0 +1,20 @@
export type PagingQueryType = {
page: number;
size: number;
};
export type GridCodeType = {
label?: string;
text?: string;
value: string;
};
export type FileInfoType = {
fileId: number;
fileOriginalName: string;
filePath: string;
fileMimeType: string;
fileSize: number;
fileAltText: string;
fileDownloadCount: number;
};

33
nuxt/types/login/index.ts Normal file
View File

@@ -0,0 +1,33 @@
import type { MenuType } from '../sys/menu';
export type LoginRequestType = {
memberId: string;
password: string;
remember: boolean;
};
export type LoginResponseType = {
memberName: string;
instNm: string;
deptNm: string;
menuList: AuthorizationMenuType[];
permitApiList: PermitApiType[];
authenticated: boolean;
};
export type AuthorizationMenuType = {
menuId: string;
upMenuId: string;
menuDepth: number;
menuName: string;
menuType: MenuType;
menuUrl: string;
bcId: string;
contentId: string;
children: AuthorizationMenuType[];
};
export type PermitApiType = {
menuUrl: string;
};

View File

@@ -0,0 +1,25 @@
export type MenuType = {
menuId: string;
siteId: string;
upMenuId: string;
menuDepth: number;
menuOrder: number;
menuName: string;
menuType: 'MENU' | 'PAGE' | 'API' | 'TAB' | 'COMMON_MENU' | 'COMMON_API';
menuFeature: 'PAGE' | 'LIST' | 'DETAIL' | 'CREATE' | 'UPDATE' | 'DELETE';
menuLayout: string;
menuUrl: string;
menuMethod: string;
menuDescription: string;
menuLinkTarget: 'CURRENT' | 'BLANK';
menuUseSatisfaction: boolean;
menuUseMngInfo: boolean;
menuMngId: string;
menuStatus: 'ENABLED' | 'HIDDEN' | 'DISABLED';
delYn: boolean;
useYn: boolean;
frstRgtrId: string;
frstRegDt: string;
lastMdfrId: string;
lastMdfcnDt: string;
};

View File

@@ -0,0 +1,13 @@
export type SiteType = {
siteId: string;
siteName: string;
siteDescription: string;
siteDomain: string;
siteType: '' | 'ADMIN' | 'USER';
sitePrefix: string;
siteLocale: string;
siteLogo: string;
bscUrl: string;
lgnUrl: string;
siteRegdate: string;
};