오늘이라도
[Spring] 16. 방명록 만들기 ② : 첨부파일 미리보기, 게시글 표시 갯수, 게시판 표시 형태 본문
취업성공패키지 SW 개발자 교육/Spring
[Spring] 16. 방명록 만들기 ② : 첨부파일 미리보기, 게시글 표시 갯수, 게시판 표시 형태
upcake_ 2020. 7. 22. 11:30반응형
https://github.com/upcake/Class_Examples
교육 중에 작성한 예제들은 깃허브에 올려두고 있습니다.
gif 파일은 클릭해서 보는 것이 정확합니다.
- 방명록 만들기 ② : 첨부파일 미리보기, 게시글 표시 갯수, 게시판 표시 형태 -
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>board list jsp</title>
<style type="text/css">
table { table-layout:fixed; }
table td { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
</style>
</head>
<body>
<h3>방명록</h3>
<form id="list" method="post" action="">
<input type="hidden" name="curPage" value="1" />
<div id="list-top">
<div>
<!-- 검색 -->
<ul>
<li>
<select name="search" class="w-px80">
<option value="all" ${page.search eq 'all' ? 'selected' : '' }>전체</option>
<option value="title" ${page.search eq 'title' ? 'selected' : '' }>제목</option>
<option value="content" ${page.search eq 'content' ? 'selected' : '' }>내용</option>
<option value="writer" ${page.search eq 'writer' ? 'selected' : '' }>작성자</option>
</select>
</li>
<li>
<input type="text" name="keyword" class="w-px300"/>
</li>
<li>
<a class="btn-fill" onclick="$('form').submit()">검색</a>
</li>
</ul>
<!-- 글쓰기 버튼 -->
<ul>
<!-- 로그인되어 있으면 글쓰기 가능 -->
<core:if test="${!empty login_info }">
<li>
<a class="btn-fill" href="new.bo">글쓰기</a>
</li>
</core:if>
</ul>
<ul>
<li>
<select name="pageList" class="w-px80" onchange="$('[name=curPage]').val(1); $('form').submit()">
<option value="10" ${page.pageList eq 10 ? 'selected' : '' }>10개씩</option>
<option value="20" ${page.pageList eq 20 ? 'selected' : '' }>20개씩</option>
<option value="30" ${page.pageList eq 30 ? 'selected' : '' }>30개씩</option>
</select>
</li>
<li>
<select name="viewType" class="w-px100" onchange="$('form').submit()">
<option value="list" ${page.viewType eq 'list' ? 'selected' : '' }>리스트 형태</option>
<option value="grid" ${page.viewType eq 'grid' ? 'selected' : '' }>바둑판 형태</option>
</select>
</li>
</ul>
</div>
</div>
</form>
<div id="data-list">
<core:if test="${page.viewType eq 'list' }">
<table>
<tr>
<th class="w-px60">번호</th>
<th>제목</th>
<th class="w-px100">작성자</th>
<th class="w-px120">작성일자</th>
<th class="w-px60">첨부 파일</th>
</tr>
<core:forEach items="${page.list }" var="vo">
<tr>
<td>${vo.no }</td>
<td class="left">${vo.title }</td>
<td>${vo.name }</td>
<td>${vo.writedate }</td>
<td>
<core:if test="${!empty vo.filename }">
<img src="img/attach.png" class="file-img"/>
</core:if>
</td>
</tr>
</core:forEach>
</table>
</core:if>
<core:if test="${page.viewType eq 'grid' }">
<ul class="grid">
<core:forEach items="${page.list }" var="vo">
<li>
<div>${vo.title }</div>
<div>${vo.name }</div>
<div>${vo.writedate }</div>
</li>
</core:forEach>
</ul>
</core:if>
</div>
<div class="btnSet">
<jsp:include page="/WEB-INF/views/include/page.jsp"/>
</div>
<script type="text/javascript">
$(function(){
$('#data-list ul').css('height', $('.grid li').length < 5 ? 1 : $('.grid li').length/5 * $('.grid li').outerHeight(true) - 20);
})
</script>
</body>
</html>
▲list.jsp
package com.hanul.iot;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import board.BoardPage;
import board.BoardServiceImpl;
import board.BoardVO;
import common.CommonService;
import member.MemberVO;
@Controller
public class BoardController {
@Autowired private BoardServiceImpl service;
@Autowired private BoardPage page;
@Autowired private CommonService common;
//방명록 목록 화면 요청================================================================
@RequestMapping("/list.bo")
public String list(HttpSession session, Model model, @RequestParam(defaultValue = "1") int curPage,
String search, String keyword, @RequestParam(defaultValue = "10") int pageList,
@RequestParam(defaultValue = "list") String viewType) {
//DB에서 방명록 정보를 조회해와 목록 화면에 출력
session.setAttribute("category", "bo");
page.setCurPage(curPage);
page.setSearch(search);
page.setKeyword(keyword);
page.setPageList(pageList);
page.setViewType(viewType);
model.addAttribute("page", service.board_list(page));
return "board/list";
} //list()
//방명록 신규 화면 요청================================================================
@RequestMapping("/new.bo")
public String board() {
//방명록 글쓰기 화면으로 연결
return "board/new";
} // board()
//신규 방명록 저장 처리 요청================================================================
@RequestMapping("/insert.bo")
public String insert(BoardVO vo, MultipartFile file, HttpSession session) {
//화면에서 입력한 정보를 DB에 저장한 후 목록 화면으로 연결
if( !file.isEmpty() ) {
vo.setFilename( file.getOriginalFilename() );
vo.setFilepath(common.upload("board", file, session));
}
vo.setWriter( ((MemberVO) session.getAttribute("login_info")).getId() );
service.board_insert(vo);
return "redirect:list.bo";
} //insert()
} //class
▲BoardController.java
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>board new jsp</title>
</head>
<body>
<h3>방명록 글쓰기</h3>
<form action="insert.bo" method="post" enctype="multipart/form-data">
<table>
<tr>
<th class="w-px160">제목</th>
<td><input type="text" name="title" class="need" title="제목"/></td>
</tr>
<tr>
<th>작성자</th>
<td>${login_info.name }</td>
</tr>
<tr>
<th>내용</th>
<td><textarea name="content" class="need" title="내용"></textarea></td>
</tr>
<tr>
<th>첨부 파일</th>
<td class="left">
<label>
<input type="file" name="file" id="attach-file" />
<img src="img/select.png" class="file-img"/>
</label>
<span id="file-name"></span>
<span id="preview"></span>
<span id="delete-file" style="color:red; margin-left:20px;">
<i class="fas fa-times font-img" ></i>
</span>
</td>
</tr>
</table>
</form>
<div class="btnSet">
<a class="btn-fill" onclick="if( necessary() ){ $('form').submit() }">저장</a>
<a class="btn-empty" href="list.bo">취소</a>
</div>
<script type="text/javascript">
function isImage(filename){
//ab.txt, abc.png, abc.JPG, abcd.hwp, ...
var ext = filename.substring(filename.lastIndexOf('.') + 1 ).toLowerCase(); // 마지막 점(.) 다음 위치에서부터 끝까지 뽑고 소문자로 변환
var imgs = ['png', 'jpg', 'jpeg', 'gif', 'bmp'];
if ( imgs.indexOf(ext) > -1 ) { return true; }
else { return false; }
}
$('#attach-file').on('change', function() {
var attach = this.files[0];
if( attach ) {
if( isImage(attach.name) ) {
var img = "<img id='preview-img' class='file-img' src='' style='border-radius:50%'/>";
$('#preview').html(img);
var reader = new FileReader();
reader.onload = function(e) {
$('#preview-img').attr('src', e.target.result);
}
reader.readAsDataURL(attach);
} else {
$('#preview').html('')
}
}
});
$('#delete-file').on('click', function() {
$('#preview').html('')
});
</script>
<script type="text/javascript" src="js/file_attach.js"></script>
<script type="text/javascript" src="js/need_check.js?v=<%=new java.util.Date().getTime() %>"></script>
</body>
</html>
▲new.jsp
/**
* 입력 항목에 입력되어 있는지 여부를 반환하는 함수
*/
function necessary(){
var need = true;
$('.need').each(function(){
if( $(this).val()=='' ){
alert( $(this).attr('title') + ' 입력하세요!' );
$(this).focus();
need = false;
return need;
}
});
return need;
}
//엔터를 누를 경우
$('[name=title]').on('keypress', function(e) {
if(e.keyCode == 13) {
if(necessary()) {$('form').submit(); }
else {return false;}
}
});
▲need_check.js
package board;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class BoardDAO implements BoardService {
@Autowired private SqlSession sql;
@Override
public int board_insert(BoardVO vo) {
return sql.insert("board.mapper.insert", vo);
}
@Override
public BoardPage board_list(BoardPage page) {
page.setTotalList((Integer) sql.selectOne("board.mapper.total", page));
page.setList(sql.selectList("board.mapper.list", page));
return page;
}
@Override
public BoardVO board_detail(int id) {
// TODO Auto-generated method stub
return null;
}
@Override
public void board_read(int id) {
// TODO Auto-generated method stub
}
@Override
public int board_update(BoardVO vo) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int board_delete(int id) {
// TODO Auto-generated method stub
return 0;
}
}
▲BoardDAO.java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board.mapper">
<select id="total" resultType="integer">
SELECT COUNT(*) FROM board <include refid="search"/>
</select>
<select id="list" resultType="board.BoardVO">
SELECT n.*, (SELECT name FROM member WHERE member.id=writer) name
FROM (SELECT b.*, ROWNUM no
FROM (SELECT * FROM board <include refid="search"/> ORDER by id) b
ORDER BY no DESC) n
WHERE no BETWEEN #{beginList } AND #{endList }
</select>
<sql id="search">
<if test="search == 'title' or search == 'content'" >
WHERE ${search } LIKE '%' || #{keyword } || '%'
</if>
<if test="search == 'writer'" >
WHERE <include refid="writer" />
</if>
<if test="search == 'all'">
WHERE TITLE LIKE '%' || #{keyword } || '%'
OR content LIKE '%' || #{keyword } || '%'
OR <include refid="writer" />
</if>
</sql>
<sql id="writer">
writer IN (SELECT id FROM member WHERE name LIKE '%' || #{keyword } || '%')
</sql>
<insert id="insert">
INSERT INTO board(title, content, writer, filename, filepath)
VALUES (#{title }, #{content}, #{writer}, #{filename, jdbcType=VARCHAR}, #{filepath, jdbcType=VARCHAR} )
</insert>
</mapper>
▲board-mapper.xml
package board;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BoardServiceImpl implements BoardService {
@Autowired private BoardDAO dao;
@Override
public int board_insert(BoardVO vo) {
return dao.board_insert(vo);
}
@Override
public BoardPage board_list(BoardPage page) {
return dao.board_list(page);
}
@Override
public BoardVO board_detail(int id) {
// TODO Auto-generated method stub
return null;
}
@Override
public void board_read(int id) {
// TODO Auto-generated method stub
}
@Override
public int board_update(BoardVO vo) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int board_delete(int id) {
// TODO Auto-generated method stub
return 0;
}
}
▲BoardServiceImpl.java
--테이블 생성
CREATE TABLE customer (
id NUMBER CONSTRAINT customer_id_pk PRIMARY KEY,
name VARCHAR2(50) NOT NULL,
gender VARCHAR2(3) NOT NULL,
email VARCHAR2(50),
phone VARCHAR2(13)
);
--시퀀스 생성
CREATE SEQUENCE seq_customer START WITH 1 INCREMENT BY 1;
--레코드 삽입
INSERT INTO customer(id, name, gender)
VALUES (seq_customer.NEXTVAL, '홍길동', '남');
INSERT INTO customer(name, gender)
VALUES ('심청', '여');
--트리거(trigger) 설정
CREATE OR REPLACE TRIGGER trg_customer
BEFORE INSERT ON customer --커스터머 테이블에 인서트가 되기전에
FOR EACH ROW --모든 행에 대하여
BEGIN --시작한다
SELECT seq_customer.NEXTVAL INTO :new.id FROM dual; --시퀀스의 데이터를 담고있는 테이블은 없으므로 더미 테이블(dual)에서 조회한다.
END;
/ --끝 슬래쉬까지 써줘야한다
drop trigger trg_customer;
CREATE OR REPLACE TRIGGER trg_customer
BEFORE INSERT ON customer
FOR EACH ROW
BEGIN
SELECT seq_customer.NEXTVAL INTO :new.id FROM dual;
END;
/
--조회
SELECT * FROM customer;
--커밋
COMMIT;
--20/07/02==================================================================
--회원 관리 테이블
CREATE TABLE member(
irum VARCHAR2(20) NOT NULL,
id VARCHAR2(20) CONSTRAINT member_id_pk PRIMARY KEY,
pw VARCHAR2(20) NOT NULL,
age NUMBER,
gender VARCHAR2(3) NOT NULL,
birth DATE,
post VARCHAR2(7),
addr VARCHAR2(50),
email VARCHAR2(50) NOT NULL, --유니크가 들어간다 생각하고 NOT NULL만 지정
tel VARCHAR2(20),
admin VARCHAR2(1) DEFAULT 'N'
);
--기존에 있던 member 테이블 수정
ALTER TABLE member
ADD(
gender VARCHAR2(3) DEFAULT '남' NOT NULL,
birth DATE,
post VARCHAR2(7),
email VARCHAR2(50),
admin VARCHAR2(1) default 'N'
);
UPDATE MEMBER SET email = id || '@naver.com';
ALTER TABLE member
MODIFY (irum NOT NULL, pw NOT NULL, email NOT NULL);
ALTER TABLE member RENAME COLUMN irum TO name;
ALTER TABLE member ADD CONSTRAINT member_id_pk PRIMARY KEY(id);
--관리자 회원 정보 저장
INSERT INTO member(name, id, pw, age, gender, email, admin)
VALUES ('관리자', 'admin', '1234', 25, '남', 'admin@admin.com', 'Y');
SELECT * FROM member;
--20/07/10======================================================================
CREATE TABLE notice(
id NUMBER CONSTRAINT notice_id_pk PRIMARY KEY,
title VARCHAR2(300) NOT NULL,
content VARCHAR2(4000) NOT NULL,
writer VARCHAR2(20) NOT NULL,
writedate DATE DEFAULT SYSDATE,
readcnt NUMBER DEFAULT 0,
filename VARCHAR2(300),
filepath VARCHAR2(300)
);
CREATE SEQUENCE seq_notice
START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER trg_notice
BEFORE INSERT ON notice
FOR EACH ROW
BEGIN
SELECT seq_notice.NEXTVAL INTO:NEW.id FROM dual;
END;
/
SELECT * FROM notice;
INSERT INTO notice(title, content, writer)
VALUES ('공지 글 테스트', '테스트 공지 글 입니다.', 'admin');
COMMIT;
--20/07/15================================================
--테이블에 공지글 항목 추가
INSERT INTO notice(title, content ,writer, writedate, filepath, filename)
SELECT title, content, writer, writedate, filepath, filename FROM notice;
COMMIT;
--20/07/16================================================
--notice 테이블에 칼럼 추가
CREATE TABLE notice(
id NUMBER CONSTRAINT notice_id_pk PRIMARY KEY,
title VARCHAR2(300) NOT NULL,
content VARCHAR2(4000) NOT NULL,
writer VARCHAR2(20) NOT NULL,
writedate DATE DEFAULT SYSDATE,
readcnt NUMBER DEFAULT 0,
filename VARCHAR2(300),
filepath VARCHAR2(300)
root NUMBER,
step NUMBER default 0,
indent NUMBER default 0
);
ALTER TABLE notice
ADD(root NUMBER, step NUMBER DEFAULT 0, indent NUMBER DEFAULT 0);
UPDATE notice SET root = id;
--root : 원글의 root
--step : 원글 step + 1 / 원글의 step보다 더 큰 step을 가진 글이 있다면 그 글들의 step을 먼저 +1 한다.
--indent : 원글 indent + 1
ALTER TRIGGER trg_notice DISABLE;
SELECT * FROM notice;
COMMIT;
--20/07/20======================================================================================================================
INSERT INTO member(id, pw, gender, email, name, admin)
VALUES ('admin2', '1234', '남', 'admin2@admin@.com', '운영자', 'Y');
UPDATE notice SET writer='admin2'
WHERE mod(id, 3) = 0;
COMMIT;
--qna 테이블 생성
CREATE TABLE qna(
id NUMBER CONSTRAINT qna_id_pk PRIMARY KEY,
title VARCHAR2(300) NOT NULL,
content VARCHAR2(4000) NOT NULL,
writer VARCHAR2(20) NOT NULL,
writedate DATE DEFAULT SYSDATE,
readcnt NUMBER DEFAULT 0,
filename VARCHAR2(300),
filepath VARCHAR2(300),
root NUMBER,
step NUMBER default 0,
indent NUMBER default 0
);
CREATE SEQUENCE seq_qna
START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER trg_qna
BEFORE INSERT ON qna
FOR EACH ROW
BEGIN
SELECT seq_qna.NEXTVAL INTO:NEW.id FROM dual;
END;
/
INSERT INTO qna (id, title, content, writer)
VALUES (1, '첫 글 테스트', 'ㅁㄴㅇㄻㄴㅇㄹ', '관리자');
--20/07/21======================================================================================================================
INSERT INTO qna(title, content, writer, writedate, filepath, filename)
SELECT title, content, writer, writedate, filepath, filename FROM qna;
UPDATE qna SET root = id;
ALTER TRIGGER trg_qna DISABLE
UPDATE qna SET writer='admin2'
WHERE mod(id, 3) = 0;
COMMIT;
--방명록 관리
CREATE TABLE board(
id NUMBER CONSTRAINT board_id_pk PRIMARY KEY,
title VARCHAR2(300) NOT NULL,
content VARCHAR2(4000) NOT NULL,
writer VARCHAR2(100) NOT NULL,
writedate DATE DEFAULT SYSDATE,
readcnt NUMBER DEFAULT 0,
filename VARCHAR2(300),
filepath VARCHAR2(300)
);
CREATE SEQUENCE seq_board START WITH 1 INCREMENT BY 1;
CREATE OR REPLACE TRIGGER trg_board
BEFORE INSERT ON board
FOR EACH ROW
BEGIN
SELECT seq_board.NEXTVAL INTO :new.id FROM dual;
END;
/
COMMIT;
--20/07/22========================================================
INSERT INTO board (title, content, writer, filename, filepath)
SELECT title, content, writer, filename, filepath
FROM board;
▲table.sql
package board;
import java.sql.Date;
public class BoardVO {
private int id, readcnt, no;
private String title, content, writer, name, filename, filepath;
private Date writedate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getReadcnt() {
return readcnt;
}
public void setReadcnt(int readcnt) {
this.readcnt = readcnt;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getFilepath() {
return filepath;
}
public void setFilepath(String filepath) {
this.filepath = filepath;
}
public Date getWritedate() {
return writedate;
}
public void setWritedate(Date writedate) {
this.writedate = writedate;
}
}
▲BoardVO.java
package common;
public class PageVO {
private int pageList = 10; //페이지당 목록수
private int blockPage = 10; //블럭당 페이지수
private int totalList; //총목록수
private int totalPage; //총페이지수
//157 페이지 = 총목록수/페이지당 목록수 + 나머지가 있으면+1
private int totalBlock; //총블럭수
//16 블럭 = 총페이지수/블럭당 페이지수 + 나머지가 있으면+1
private int curPage; //현재페이지번호
private int beginList, endList; //현재페이지의 시작/끝 목록번호
private int beginPage, endPage; //현재블럭의 시작/끝 페이지번호
private String search, keyword , viewType="list"; //검색기준, 검색어, 보기 형태
public String getViewType() {
return viewType;
}
public void setViewType(String viewType) {
this.viewType = viewType;
}
public String getSearch() {
return search;
}
public void setSearch(String search) {
this.search = search;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public int getPageList() {
return pageList;
}
public void setPageList(int pageList) {
this.pageList = pageList;
}
public int getBlockPage() {
return blockPage;
}
public void setBlockPage(int blockPage) {
this.blockPage = blockPage;
}
public int getTotalList() {
return totalList;
}
public void setTotalList(int totalList) {
this.totalList = totalList;
//총페이지수=총목록수/페이지당보여질목록수
//576/10 --> 57 ..6 -> 58페이지
totalPage = totalList / pageList;
if( totalList % pageList >0 ) ++totalPage;
//총블럭수=총페이지수/블럭당보여질페이지수
//58/10 --> 5..8 -> 6블럭
totalBlock = totalPage / blockPage;
if( totalPage % blockPage > 0 ) ++totalBlock;
//시작/끝 목록번호
//끝목록번호: 576, 566, 556,
endList = totalList - (curPage-1) * pageList;
//시작목록번호: 567, 557, 547,
//= 끝목록번호 - (페이지당보여질목록수-1)
beginList = endList - (pageList-1);
//현재 블럭번호
curBlock = curPage / blockPage;
if( curPage % blockPage > 0 ) ++curBlock;
//시작/끝 페이지번호
//끝페이지번호: 10, 20, 30, ...
endPage = curBlock * blockPage;
//시작페이지번호 : 1, 11, 21, ...
beginPage = endPage - (blockPage-1);
//2048건 ▶ 1페이지 : 2048 ~ 2039, 1 ~ 10
// 205페이지 : 8 ~ 1, 51 ~ 58
//끝 페이지 번호가 총 페이지 번호보다 크면 총 페이지 번호가 끝 페이지 번호이다.
if(endPage > totalPage) {endPage = totalPage; }
}
private int curBlock; //현재블럭번호
public int getCurBlock() {
return curBlock;
}
public void setCurBlock(int curBlock) {
this.curBlock = curBlock;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getTotalBlock() {
return totalBlock;
}
public void setTotalBlock(int totalBlock) {
this.totalBlock = totalBlock;
}
public int getCurPage() {
return curPage;
}
public void setCurPage(int curPage) {
this.curPage = curPage;
}
public int getBeginList() {
return beginList;
}
public void setBeginList(int beginList) {
this.beginList = beginList;
}
public int getEndList() {
return endList;
}
public void setEndList(int endList) {
this.endList = endList;
}
public int getBeginPage() {
return beginPage;
}
public void setBeginPage(int beginPage) {
this.beginPage = beginPage;
}
public int getEndPage() {
return endPage;
}
public void setEndPage(int endPage) {
this.endPage = endPage;
}
}
▲PageVO.java
@charset "UTF-8";
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap');
body {
margin: 0 auto;
text-align: center;
font-size: 16px;
font-family: 'Noto Sans KR', sans-serif;
}
a:link, a:visited {
text-decoration: none;
color: #000;
}
#content {
padding: 20px 0;
min-width: 1024px; /* 창의 최소 크기 지정 */
}
img {
vertical-align: middle; /* 세로축 가운데 정렬 */
}
table {
width: 80%;
margin: 0 auto;
border: 1px solid;
border-collapse: collapse; /* 테두리 겹침 설정 collapse: 겹치지 않게 처리 */
}
table th, table td {
border: 1px solid;
padding: 5px 10px;
}
table td a:hover { font-weight: bold; }
.btnSet { margin-top: 20px; }
a.btn-fill, a.btn-empty {
text-align: center;
padding: 3px 10px;
border:1px solid #3367d6;
border-radius: 3px;
box-shadow: 2px 2px 3px #022d72;
/* 오른쪽, 아래쪽, 번진 정도 */
}
a.btn-fill {
background-color: #3367d6;
color: #fff;
}
a.btn-empty {
background-color: #fff;
color: #3367d6
}
a.btn-fill-s, a.btn-empty-s {
text-align: center;
padding: 1px 10px;
border:1px solid #c4dafc
border-radius: 3px;
box-shadow: 2px 2px 3px #022d72;
color: #0000cd;
font-size: 13px;
}
a.btn-fill-s {
background-color: #bacdfa;
}
a.btn-empty-s {
background-color: #fff;
}
.btnSet a:not(:first-child) {
margin-left: 3px;
}
a:hover { cursor:pointer; }
input {
height: 22px;
padding: 3px 5px;
font-size: 15px;
}
input[type=radio] {
width: 18px;
margin: 0 5px 3px;
vertical-align: middle;
}
table tr td label:not(:last-child) {
margin-right: 20px;
}
.w-pct60 { width: 60% }
.w-pct70 { width: 70% }
.w-pct80 { width: 80% }
.w-px40 { width: 40px }
.w-px60 { width: 60px }
.w-px80 { width: 80px }
.w-px100 { width: 100px }
.w-px120 { width: 120px }
.w-px140 { width: 140px }
.w-px160 { width: 160px }
.w-px180 { width: 180px }
.w-px200 { width: 200px }
.w-px300 { width: 300px }
.left { text-align: left }
.right { text-align: right }
.font-img { cursor: pointer; }
ul { list-style: none; padding: 0; }
#list-top { width: 80%; padding: 20px 10%;}
#list-top ul { margin:0; display:flex; }
#list-top ul:last-child { float: right; }
#list-top ul:first-child { float: left; }
#list-top ul li:not(:first-child) { margin-left:2px }
#list-top div { width: 100%; height: 32px;}
#list-top ul li * { vertical-align:middle; }
input[name=title] { width:calc(100% - 14px) }
textarea[name=content] { width:calc(100% - 6px); height: 150px; resize: none;}
/* 파일 첨부 */
.file-img { width: 18px; height:18px; cursor:poinrter; }
#attach-file, #delete-file { display:none; }
select { height: 32px }
ul.grid { width: 80%; margin: 0 10%; }
.grid li { float: left; width: 19%; height: 150px; border: 1px solid gray; box-sizing: border-box; }
.grid li:not(:nth-child(5n)) { margin: 0 1.25% 20px 0 }
.grid li div{ text-align: left; padding: 10px; }
.grid li div:nth-child(1) {
height:60px;
overflow:hidden;
display:-webkit-box;
-webkit-line-clamp: 3;
}
.grid li div:nth-child(2) { padding: 0 10px; }
▲common.css
반응형
'취업성공패키지 SW 개발자 교육 > Spring' 카테고리의 다른 글
[Spring] 18. 방명록 만들기 ④ : 사진 미리보기, 덧글 기능 (0) | 2020.07.24 |
---|---|
[Spring] 17. 방명록 만들기 ③ : 글 상세 조회, 수정 (0) | 2020.07.23 |
[Spring] 15. 미니 프로젝트 : Q&A 게시판 만들기 / 방명록 만들기 (1) | 2020.07.21 |
[Spring] 14. 웹사이트 만들기 ⑬ : 답글 처리, 검색 기능 (0) | 2020.07.17 |
[Spring] 13. 웹사이트 만들기 ⑫ : 페이징, 답글 작성 (0) | 2020.07.16 |