no image
[프로젝트] 파이썬 미니 프로젝트 Day.8
드디어 저도 무언가 한 건 하는 날이 오는군요. 먼저 이번 프로젝트에서 해결해야할 부분은 다음과 같습니다. 로그인 시 오류페이지 이미지 규격이 제멋대로 텍스트 제한이 없어 너무 자유 분방함 검색 후 출력되는 테이블의 넓이 문제 텍스트로 검색 후 헤더에 카테고리 클릭 기능이 미구현됨 이외에도 문제가 많았으나 오늘 크게 수정 된 부분은 이렇게 해당합니다. 1번 문제는 조장이 맡아 처리해주었고, 나머지부분을 손보았습니다. 이전 사진은 다음과 같습니다. 사진과 글자부분은 한 css에 링크 되어 있어 해당 부분에서 모두 손보았습니다. 변경된 코드는 다음과 같습니다. 1. 이미지 크기 제한 2. 글자 수 제한 1) css파일 2) 글자가 출력되는 html 부분(메인 페이지, 검색 후 출력되는 페이지 둘다 동일하게 ..
2023.05.10
no image
[프로젝트] 파이썬 미니 프로젝트 Day.7
오늘은 DB의 구성을 왜 잘 해야하는가에 대해 깨닫는 하루였습니다. 각각 맡은 부분을 하면서 각자 컴퓨터에 SQL 테이블을 작성했는데 컬럼이 달라서 하나하나 수정하고 맞춰가며 작업을 진행하다보니 시간도 많이 잡아먹고, 하나하나 찾는게 정말 쉽지 않았습니다. 페이지 종류가 많아지면서 코드는 점점 복잡해지는데 꼬이고 오류 뜨고 어디서부터 해결해야 할지 감조차 잡히지 않습니다. 이걸 하나하나 해결해나가는 동기들을 보면 참 대단하다 싶습니다. 도움이 되고 싶어 뭐라도 아득바득 해보려 하는데, 뱁새가 황새를 따라가려는 것처럼 다리만 찟어지고 아픕니다. 너무 많은 분야를 한번에 공부하려니 머리만 아프고 답답하네요. 다음 프로젝트에서는 이런일이 없었으면 좋겠습니다. 더욱 분발해야죠. 그 때는 이렇게 일기 쓰는 프로젝..
2023.05.09
no image
[프로젝트] 파이썬 미니 프로젝트 Day.6
SQL 공부를 하고, 데이터 베이스와 친해질 겸 DB 내용에 중복 등 문제 제거 작업을 진행했습니다. 또한 이미 데이터 베이스는 마련이 되었고, flask까지 연결을 마쳤으니 이제 남은건 기능들을 추가하는 것입니다. 사실 이번에는 저 포함 조원들이 파이썬, SQL, Flask에 대해 아는 것이 거의 없다 시피해서 우리 멋진 조장님께서 큰 틀에대해 설명해줬고, 이 프로젝트는 SQL과 flask를 사용해 원활한 서비스를 하는 것이므로 디자인은 크게 신경쓰지 않는 방향으로 진행하기로 했습니다. 이번 추가 안건은 검색 페이지에 아래와 같이 목록을 미리 정할 수 있도록 하면 어떨지입니다. 얼른 따라가야할텐데 명령어 숙지부터 이 내용을 따라가기가 벅차다는 느낌이 들기도 하네요... 검색 페이지 html 검색 전체 ..
2023.05.08
[프로젝트] 파이썬 미니 프로젝트 Day.5
오늘은 MySQL을 다운로드 받고 이전에 제작한 html과 DB를 연결해 로그인 기능을 구현하려 했습니다. 해당 내용을 구현하기 위해 해당 페이지를 참고했으나 제작하는 과정에서 오류가 많이 나와 오늘은 미완성인 채로 아쉽게 마무리 되었습니다. 이전에 중구난방으로 html과 css를 제작했었으나 이번에는 각각 폴더를 지정해 몰아 넣어 정리했습니다. 하지만, 저는 오늘 건강관계상 같이 토론은 못하고 내용을 전달받고 이 코드가 무슨뜻인지 주석을 달아주는 것 밖에 못했습니다. DB랑 연결해서 실험을 해봐야하는데, 오늘은 이렇게 가볍게 마치고 이번 주말에 더 공부해볼 생각입니다. 다음은 코드와 무슨 뜻인지 정리되어있으니 참고할 수 있을 것으로 생각됩니다. 참고로 html의 주석은 , 파이썬의 주석은 #, sql의..
2023.05.05
no image
[프로젝트] 파이썬 미니 프로젝트 Day.4
프로젝트 4일차에 프론트쪽은 거의 완성되가는 느낌입니다. 이외의 작업은 DB를 연결하고 기타 기능들도 구현을 해야했으나, 강사님께서 진행하는 속도보다 훨씬 빠른 속도로 진행이 되어버렸기 때문에 DB 부분은 잠시 쉬어가고 아래 페이지들을 조금씩 다듬었는데, 소소한 문제들이 계속 나와서 어떻게 해야될지 머리를 싸매 고민하고 있습니다. 잘하는 친구들이 있어 공부를 하는 방향성에 대해서 조금씩 듣고 있는데 개발을 처음부터 끝까지 한다는 것 자체가 쉽지 않다는 것을 깨달아 가고 있습니다. 왜 이걸 통틀어 하는 직업군은 없고 DB 전문가, 프론트엔드 전문가, 백엔드 전문가들이 나뉘어 한 프로젝트에 수십, 많으면 수백명이 달려드는지 알 수 있었습니다. 웹페이지를 하나 만드는데 html로 페이지의 뼈대를 만들고, cs..
2023.05.04
no image
[프로젝트] 파이썬 미니 프로젝트 Day.3
초안을 간단한 툴들로 만들었습니다. html과 css로 타이핑해서 만드는 것도 좋지만, 요즘은 툴들이 너무 잘나와서 언어 몰라도 프로그래밍 할 수 있다는게 조금씩 무슨 말인지 알 것 같습니다. ChatGPT같은 생성형 AI가 짜주는 코드들도 잘 활용하면 멋진 작품이 탄생할 것 같다는 생각이 듭니다. 이미지를 넣어보려고 하는데 너무 뒤죽박죽으로 적용되고, 어디서부터 찾아나가야할지 조금더 알아봐야겠습니다. 뭔가 틀들이 잡혀나가는 모습을 보니 점점 재미있어지네요. 오늘은 이렇게 간단히 짚고 넘어가겠습니다.
2023.05.03
반응형

드디어 저도 무언가 한 건 하는 날이 오는군요. 먼저 이번 프로젝트에서 해결해야할 부분은 다음과 같습니다.

  1. 로그인 시 오류페이지
  2. 이미지 규격이 제멋대로
  3. 텍스트 제한이 없어 너무 자유 분방함
  4. 검색 후 출력되는 테이블의 넓이 문제
  5. 텍스트로 검색 후 헤더에 카테고리 클릭 기능이 미구현됨

이외에도 문제가 많았으나 오늘 크게 수정 된 부분은 이렇게 해당합니다.

 

 

1번 문제는 조장이 맡아 처리해주었고, 나머지부분을 손보았습니다. 이전 사진은 다음과 같습니다.

이미지 넓이 불일정
글자 제한이 없어 너무 많은 내용 출력
검색 후 해당 헤더 카테고리가 비활성화 됨

사진과 글자부분은 한 css에 링크 되어 있어 해당 부분에서 모두 손보았습니다. 변경된 코드는 다음과 같습니다.

 

1. 이미지 크기 제한

2. 글자 수 제한

1) css파일

2) 글자가 출력되는 html 부분(메인 페이지, 검색 후 출력되는 페이지 둘다 동일하게 수정)

해당 부분을 추가해 글자수를 50으로 제한하였습니다.

3. 서치 아이콘 검색 창 옆으로 이동

컨테이너 클래스 추가
컨테이너에 상세 설정

4. 검색 후 기능 비활설화 수정

검색 후 기능이 비활성화 되는 이유는 해당 부분이 하이퍼링크가 제대로 구현되지 않았었기 때문입니다. 따라서 하이퍼링크를 추가하고 DB에 준비되어있는 장르에 맞게 수정했습니다.

 

결과

글자들이 이전보다 가독성 있게 변경되었고, 기능 구현 또한 잘 되는 것을 확인했습니다. 메인화면은 조금 아쉽지만 그래도 뭔가를 해냈다는 생각에 뿌듯합니다.

반응형
반응형

오늘은 DB의 구성을 왜 잘 해야하는가에 대해 깨닫는 하루였습니다. 각각 맡은 부분을 하면서 각자 컴퓨터에 SQL 테이블을 작성했는데 컬럼이 달라서 하나하나 수정하고 맞춰가며 작업을 진행하다보니 시간도 많이 잡아먹고, 하나하나 찾는게 정말 쉽지 않았습니다. 페이지 종류가 많아지면서 코드는 점점 복잡해지는데 꼬이고 오류 뜨고 어디서부터 해결해야 할지 감조차 잡히지 않습니다.

 

이걸 하나하나 해결해나가는 동기들을 보면 참 대단하다 싶습니다. 도움이 되고 싶어 뭐라도 아득바득 해보려 하는데, 뱁새가 황새를 따라가려는 것처럼 다리만 찟어지고 아픕니다. 너무 많은 분야를 한번에 공부하려니 머리만 아프고 답답하네요. 다음 프로젝트에서는 이런일이 없었으면 좋겠습니다. 더욱 분발해야죠. 그 때는 이렇게 일기 쓰는 프로젝트 진행 상황이 아닌 어떤 문제와 해결해 나가는 과정을 다뤄보도록 하겠습니다. 이번 미니 프로젝트에서는 너그러히 바주십사 부탁드립니다. ㅎㅎ

책 상세 정보 페이지

<!DOCTYPE html>
<html>
<head>
    <title>My Ebook Page</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css')}}">
</head>
<body>
    <header>
        <div class="logo">
            <a href="{{ url_for('home') }}">
              <img src="{{ url_for('static', filename='logo.png') }}" alt="My Ebook Logo">
            </a>
        </div>
        <div class="search">
            <form action="{{ url_for('search') }}" method="GET">
              <input type="text" name="keyword" placeholder="Search..." value="{{ request.args.get('keyword', '') }}">
              <button type="submit">Search</button>
            </form>
        </div>
        <div class="user">
            {% if 'loggedin' in session %}
                <a href="/logout">로그아웃</a> |
                <a href="/mypage">마이 페이지</a>
            {% else %}
                <a href="/login?next=/mypage">로그인</a> |
                <a href="/mypage">마이 페이지</a>
            {% endif %}
        </div>
    </header>
    <nav>
        <ul>
            <li><a href="{{ url_for('book_by_genre', genre='소설') }}">소설</a></li>
            <li><a href="{{ url_for('book_by_genre', genre='판타지') }}">판타지</a></li>
            <li><a href="{{ url_for('book_by_genre', genre='로맨스') }}">로맨스</a></li>
            <li><a href="{{ url_for('book_by_genre', genre='액션') }}">액션</a></li>
            <li><a href="{{ url_for('book_by_genre', genre='공포') }}">공포</a></li>
            <li><a href="{{ url_for('book_by_genre', genre='사회') }}">사회</a></li>
        </ul>
    </nav>
    <h1>{{ result[1] }}</h1>
    <p>작가: {{ result[2] }}</p>
    <p>출판사: {{ result[3] }}</p>
    <p>출판일: {{ result[4] }}</p>
    <p>ISBN: {{ result[5] }}</p>
    <p>장르: {{ result[6] }}</p>
    <img src="{{ result[7] }}">
    <p>가격: {{ result[8] }}</p>
    <footer>
        <p>Copyright &copy;
          <script>
            document.write(new Date().getFullYear())
          </script> Etevers e-book. All Rights Reserved.</p>
    </footer>
</body>
</html>

app.py 종합(기존 구성에 도서 검색을 추가)

rom flask import Flask, render_template, request, redirect, url_for, session
from flask_mysqldb import MySQL
from flask_paginate import Pagination
import MySQLdb.cursors
import re
import random
app = Flask(__name__, static_folder='static')
app.secret_key = 'your secret key'
app.config['MYSQL_HOST'] = 'localhost'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = '0000'
app.config['MYSQL_DB'] = 'ebook'
mysql = MySQL(app)  # MySQL 모듈을 사용하여 Flask 애플리케이션과 MySQL 데이터베이스를 연결합니다.
@app.route("/main")
def main():
    if 'loggedin' in session:
        return render_template("main.html")
@app.route('/')
def home():
    cursor = mysql.connection.cursor()
    # Retrieve the 10 latest books from your database
    query = "SELECT * FROM book ORDER BY publishdate DESC LIMIT 10"
    cursor.execute(query)
    latest_books = cursor.fetchall()
    cursor.close()
    # Randomly select 3 books to display
    new_books = random.sample(latest_books, 3)
    cursor = mysql.connection.cursor()
    query = "SELECT * FROM book"
    cursor.execute(query)
    results = cursor.fetchall()
    cursor.close()
    recommended_books = random.sample(results, 3)
    return render_template('main.html',
                            new_books=new_books,
                            recommended_book_1=recommended_books[0],
                            recommended_book_2=recommended_books[1],
                            recommended_book_3=recommended_books[2])
@app.route('/search')
def search():
    keyword = request.args.get('keyword', '')
    if keyword:
        cursor = mysql.connection.cursor()
        query = f"SELECT * FROM book WHERE name LIKE '%{keyword}%'"
        cursor.execute(query)
        results = cursor.fetchall()
        cursor.close()
    else:
        results = []
    return render_template('booklist.html', results=results)
@app.route('/books/genre/<string:genre>')
def book_by_genre(genre):
    page = int(request.args.get('page', 1))
    per_page = 10
    offset = (page - 1) * per_page
   
    cursor = mysql.connection.cursor()
    query = "SELECT * FROM book WHERE TRIM(genre) = %s LIMIT %s OFFSET %s"
    cursor.execute(query, (genre, per_page, offset,))
    results = cursor.fetchall()
    cursor.close()
    total = len(results)
    pagination = Pagination(page=page, per_page=per_page, total=total, css_framework='bootstrap4')
    return render_template('books.html', results=results, pagination=pagination)
@app.route('/books/<int:number>')
def book_detail(number):
    cursor = mysql.connection.cursor()
    query = "SELECT * FROM book WHERE number = %s"
    cursor.execute(query, (number,))
    result = cursor.fetchone()
    cursor.close()
    return render_template('book.html', result=result)
@app.route('/recommended_book')
def recommended_book():
    cursor = mysql.connection.cursor()
    cursor.execute("SELECT * FROM book ORDER BY RAND() LIMIT 1")
    recommended_book = cursor.fetchone()
    cursor.close()
    return render_template('recommended_book.html', recommended_book=recommended_book)
@app.route('/login', methods=['GET', 'POST'])
def login():
    msg = ''
    if request.method == 'POST' and 'account' in request.form and 'password' in request.form:
        account = request.form['account']
        password = request.form['password']
        cursor = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
        cursor.execute(
            'SELECT * FROM user WHERE account = % s \
            AND password = % s', (account, password, ))
        user = cursor.fetchone()
        if user:
            session['loggedin'] = True
            session['account'] = user['account']
            session['password'] = user['password']
            msg = 'Logged in successfully !'
            return render_template('main.html', msg=msg)
        else:
            msg = 'Incorrect username / password !'
    return render_template('login.html', msg=msg)
@app.route('/logout')
def logout():
    session.pop('loggedin', None)
    session.pop('account', None)
    session.pop('password', None)
    return render_template('main.html')
@app.route('/signup', methods=['GET', 'POST'])
def signup():
    msg = ''
    if request.method == 'POST' \
    and 'account' in request.form \
    and 'password' in request.form \
    and 'username' in request.form \
    and 'email' in request.form \
    and 'gender' in request.form \
    and 'birth' in request.form \
    and 'phonenumber' in request.form:
        account = request.form['account']
        password = request.form['password']
        username = request.form['username']
        email = request.form['email']
        gender = request.form['gender']
        birth = request.form['birth']
        phonenumber = request.form['phonenumber']
        cursor = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
        cursor.execute(
            'SELECT * FROM user WHERE account = % s', (account, ))
        user = cursor.fetchone()
        if user:
            msg = '이미 존재하는 계정입니다 !'
        elif not re.match(r'[^@]+@[^@]+\.[^@]+', email):
            msg = '이메일 형식에 맞지 않습니다 !'
        elif not re.match(r'^[a-zA-Z0-9가-힣]+$', username):
            msg = '이름은 영어와 숫자, 한글로만 구성되어야 한다 !'
        else:
            cursor.execute('INSERT INTO user VALUES \
                (% s, % s, % s, % s, % s, % s, % s)',
               (account, password, username, email, gender, birth, phonenumber))
            mysql.connection.commit()
            msg = '가입을 환영합니다 !'
            return redirect('/login')
    elif request.method == 'POST':
        msg = 'Please fill out the form !'
    return render_template('signup.html', msg=msg)
@app.route('/password-retrieval', methods=['GET', 'POST'])
def password_retrieval():
    msg = ''
    if request.method == 'POST' and 'account' in request.form:
        account = request.form['account']
        cursor = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
        cursor.execute(
            'SELECT * FROM user WHERE account = % s', (account, ))
        user = cursor.fetchone()
        if user:
            # 임시 비밀번호 생성 및 업데이트
            import random
            import string
            temp_password = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
            cursor.execute(
                'UPDATE user SET password = %s WHERE account = %s',
                (temp_password, account))
            mysql.connection.commit()
           
            # 결과를 바로 출력
            msg = f"임시 비밀번호는 {temp_password}입니다."
        else:
            msg = '존재하지 않는 계정입니다.'
    elif request.method == 'POST':
        msg = '계정을 입력해주세요.'
    return render_template('password-retrieval.html', msg=msg)
@app.route("/update", methods=['GET', 'POST'])
def update():
    msg = ''
    if 'loggedin' in session:
        if request.method == 'POST':
            if '' in [request.form[field] for field in ['password', 'username', 'email', 'gender', 'birth', 'phonenumber']]:
                msg = 'Please fill out the form !'
            else:
                account = session['account']
                password = request.form['password']
                username = request.form['username']
                email = request.form['email']
                gender = request.form['gender']
                birth = request.form['birth']
                phonenumber = request.form['phonenumber']
                cursor = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
                cursor.execute(
                    'UPDATE user SET password=%s, username=%s, email=%s, gender=%s, birth=%s, phonenumber=%s WHERE account=%s',
                    (password, username, email, gender, birth, phonenumber, account))
                mysql.connection.commit()
                msg = '내 정보 수정이 완료되었습니다'
        return render_template("mypage.html")
    return redirect(url_for('update.html'))
@app.route("/update_result")
def update_result():
    msg = request.args.get('msg')
    return render_template("update_result.html", msg=msg)
@app.route('/mypage')
def mypage():
    if 'loggedin' not in session:
        return redirect(url_for('login', next='/mypage'))
    cursor = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
    cursor.execute('SELECT * FROM user WHERE account = % s', (session['account'], ))
    user = cursor.fetchone()
   
    return render_template('mypage.html', user=user)
    # return render_template('mypage.html')

if __name__ == '__main__':
    app.run(debug=True)
반응형
반응형

SQL 공부를 하고, 데이터 베이스와 친해질 겸 DB 내용에 중복 등 문제 제거 작업을 진행했습니다. 또한 이미 데이터 베이스는 마련이 되었고, flask까지 연결을 마쳤으니 이제 남은건 기능들을 추가하는 것입니다. 사실 이번에는 저 포함 조원들이 파이썬, SQL, Flask에 대해 아는 것이 거의 없다 시피해서 우리 멋진 조장님께서 큰 틀에대해 설명해줬고, 이 프로젝트는 SQL과 flask를 사용해 원활한 서비스를 하는 것이므로 디자인은 크게 신경쓰지 않는 방향으로 진행하기로 했습니다.

 

이번 추가 안건은 검색 페이지에 아래와 같이 목록을 미리 정할 수 있도록 하면 어떨지입니다. 얼른 따라가야할텐데 명령어 숙지부터 이 내용을 따라가기가 벅차다는 느낌이 들기도 하네요...

검색 페이지 html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>검색 페이지</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        form {
            display: flex;
            align-items: center;
        }
        h1 {
            margin-right: 1rem;
        }
        select {
            margin-right: 1rem;
        }
        input {
            margin-right: 1rem;
        }
    </style>
</head>
<body>
    <!-- 검색 폼 생성 -->
    <form action="books" method="get">
        <!-- 검색 제목 -->
        <h1>검색</h1>
        <!-- 장르 선택 select 태그 -->
        <select name="genre" id="genre">
            <option value="">전체</option>
            <option value="로맨스">로맨스</option>
            <option value="공포">공포</option>
            <option value="액션">액션</option>
            <option value="사회">사회</option>
            <option value="소설">소설</option>
            <option value="판타지">판타지</option>
        </select>
        <!-- 정렬 선택 select 태그 -->
        <select name="sorting" id="sorting">
            <option value="default">기본</option>
            <option value="rating">별점순</option>
            <option value="desc">내림차순</option>
            <option value="asc">오름차순</option>
        </select>
        <!-- 검색어 입력 input 태그 -->
        <input type="text" id="search-text" name="search-text" placeholder="도서명, 작가명 등 검색어 입력">
        <!-- 검색 버튼 -->
        <button type="submit">
            <img src="magnifying-glass-icon.png" alt="검색 아이콘" style="width: 20px; height: 20px;">
        </button>
    </form>
</body>
</html>

 

반응형
반응형

오늘은 MySQL을 다운로드 받고 이전에 제작한 html과 DB를 연결해 로그인 기능을 구현하려 했습니다. 해당 내용을 구현하기 위해 해당 페이지를 참고했으나 제작하는 과정에서 오류가 많이 나와 오늘은 미완성인 채로 아쉽게 마무리 되었습니다. 이전에 중구난방으로 html과 css를 제작했었으나 이번에는 각각 폴더를 지정해 몰아 넣어 정리했습니다. 하지만, 저는 오늘 건강관계상 같이 토론은 못하고 내용을 전달받고 이 코드가 무슨뜻인지 주석을 달아주는 것 밖에 못했습니다. DB랑 연결해서 실험을 해봐야하는데, 오늘은 이렇게 가볍게 마치고 이번 주말에 더 공부해볼 생각입니다.

 

다음은 코드와 무슨 뜻인지 정리되어있으니 참고할 수 있을 것으로 생각됩니다. 참고로 html의 주석은 <!--  -->, 파이썬의 주석은 #, sql의 주석은 -- 입니다. 이 때 html의 주석의 내용은 <!--    --> 이 빨간 부분에 작성하게 됩니다.

 

1. main.html : 메인 페이지

<!DOCTYPE html> <!-- HTML5 문서 유형 선언 -->
<html> <!-- HTML 문서 시작 -->
<head> <!-- 문서의 메타데이터를 포함하는 요소 -->
    <title>My Ebook Page</title> <!-- 문서 제목 -->
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css')}}"> <!-- 외부 CSS 파일 연결 -->
</head>
<body> <!-- 문서의 본문을 포함하는 요소 -->
    <header> <!-- 문서의 머리말을 나타내는 요소 -->
        <div class="logo"> <!-- 로고 이미지를 담는 div 요소 -->
            <img src="{{ url_for('static', filename='logo.png') }}" alt="My Ebook Logo"> <!-- 로고 이미지 -->
        </div>
        <div class="search"> <!-- 검색창을 담는 div 요소 -->
            <input type="text" placeholder="Search..."> <!-- 검색창 input 요소 -->
        </div>
        <div class="user"> <!-- 로그인, 마이 페이지 링크를 담는 div 요소 -->
            <a href="/login">로그인</a> | <!-- 로그인 링크 -->
            <a href="/mypage">마이 페이지</a> <!-- 마이 페이지 링크 -->
        </div>
    </header>
    <nav> <!-- 문서의 내비게이션을 담는 요소 -->
        <ul> <!-- 내비게이션 목록을 담는 ul 요소 -->
            <li><a href="#">일반</a></li> <!-- 일반 카테고리 링크 -->
            <li><a href="#">로맨스</a></li> <!-- 로맨스 카테고리 링크 -->
            <li><a href="#">판타지</a></li> <!-- 판타지 카테고리 링크 -->
            <li><a href="#">미스테리</a></li> <!-- 미스테리 카테고리 링크 -->
            <li><a href="#">추리</a></li> <!-- 추리 카테고리 링크 -->
        </ul>
    </nav>
    <section>
        <h2>추천 도서</h2>
        <div class="book-list">
            <!-- Recommended book 1 -->
            <div class="book">
                <img src="{{ url_for('static', filename='book1.jpg') }}" alt="Book 1">
                <h3>세이노의 가르침</h3>
                <p>000년부터 발표된 그의 주옥같은 글들. 독자들이 자발적으로 만든 제본서는 물론, 전자책과 앱까지 나왔던 《세이노의 가르침》이 드디어 전국 서점에서 독자들을 마주한다. </p>
                <button>Read Now</button>
            </div>
            <!-- Recommended book 2 -->
            <div class="book">
                <img src="book2.jpg" alt="Book 2">
                <h3>NCS 통합 기본서</h3>
                <p>기획재정부 시행 ‘공공기관 채용박람회’ NCS 초청강사 집필! 공사 공단 및 금융권 필기 전형 대비</p>
                <button>Read Now</button>
            </div>
            <!-- Recommended book 3 -->
            <div class="book">
                <img src="book3.jpg" alt="Book 3">
                <h3>대한민국 청약지도</h3>
                <p>추첨제 부활! 사상 최대 특별공급 확대! 중도금 대출 완화! 당신이 알던 ‘청약’의 모든 것이 달라졌다</p>
                <button>Read Now</button>
            </div>
        </div>
        <h2>신상 도서</h2>
        <div class="book-list">
            <!-- New book 1 -->
            <div class="book">
                <img src=https://books.google.com/books/content?id=C2hMEAAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api alt="Book 4">
                <h3>내일을 바꾸는 인생 정보</h3>
                <p>평온한 밤을 위한 인생의 클래식, 잠 못 이루는 오늘, 당신이 묻고 고전이 답하다! </p>
                <button>Read Now</button>
            </div>
            <!-- New book 2 -->
            <div class="book">
                <img src="book5.jpg" alt="Book 5">
                <h3>면접바이블</h3>
                <p>독보적 취업 유튜버 ‘면접왕 이형’의 베스트셀러 ‘면접 바이블’이 개정판으로 돌아왔다!</p>
                <button>Read Now</button>
            </div>
            <!-- New book 3 -->
            <div class="book">
                <img src="book6.jpg" alt="Book 6">
                <h3>장하준의 경제 레시피</h3>
                <p>“자유 시장의 자유에 맡겨 두면 경제가 저절로 발전할까?” “사람들이 가난한 건 게으르기 때문일까?”</p>
                <button>Read Now</button>
            </div>
        </div>
    </section>
    <footer>
        <p>Copyright &copy;
          <script>
            document.write(new Date().getFullYear())
          </script> Etevers e-book. All Rights Reserved.</p>
    </footer>
</body>
</html>

 

2. mypage.html : 마이페이지

<!DOCTYPE html> <!-- HTML5 문서 유형 선언 -->
<html> <!-- HTML 문서 시작 -->
<head> <!-- head 태그 시작 -->
  <title>My Ebook Page</title> <!-- 문서 제목 -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 반응형 웹을 위한 뷰포트 설정 -->
  <title>My Account</title> <!-- 탭 제목 -->
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet"> <!-- 구글 폰트 링크 -->
  <link rel="stylesheet" href="{{ url_for('static', filename='mypage.css')}}"> <!-- CSS 파일 링크 -->
</head> <!-- head 태그 종료 -->
<body>
  <header> <!-- 헤더 영역 -->
    <div class="logo"> <!-- 로고 이미지 -->
      <img src="{{ url_for('static', filename='logo.png') }}" alt="My Ebook Logo">
    </div>
    <div class="search"> <!-- 검색창 -->
      <input type="text" placeholder="Search...">
    </div>
    <div class="user"> <!-- 사용자 정보 -->
      <a href="#">로그아웃</a> | <a href="#">마이 페이지</a>
    </div>
  </header>
  <nav> <!-- 네비게이션 바 -->
    <ul>
      <li><a href="#">일반</a></li>
      <li><a href="#">로맨스</a></li>
      <li><a href="#">판타지</a></li>
      <li><a href="#">미스테리</a></li>
      <li><a href="#">추리</a></li>
    </ul>
  </nav>
  <main> <!-- 메인 컨텐츠 영역 -->
    <h2>나의 정보</h2> <!-- 제목 -->
    <dl> <!-- 설명 리스트 -->
      <dt>이름</dt> <!-- 설명 제목 -->
      <dd>이주성</dd> <!-- 설명 내용 -->
      <dt>이메일</dt>
      <dd>wntjd@example.com</dd>
      <dt>전화번호</dt>
      <dd>010-1234-5678</dd>
      <dt>주소</dt>
      <dd>서울시 강남구 역삼동 123-456</dd>
    </dl>
    <h2>최근 주문 내역</h2> <!-- 제목 -->
    <table> <!-- 테이블 -->
      <thead> <!-- 테이블 헤더 -->
        <tr>
          <th>주문 번호</th>
          <th>제목</th>
          <th>가격</th>
          <th>구매일자</th>
        </tr>
      </thead>
      <tbody> <!-- 테이블 바디 -->
        <tr>
          <td>12345</td>
          <td>해리포터와 마법사의 돌</td>
          <td>12,000원</td>
          <td>2022-04-18</td>
        </tr>
        <tr>
          <td>12344</td>
          <td>물고기의 언어</td>
          <td>9,000원</td>
          <td>2022-04-17</td>
        </tr>
      </tbody>
    </table>
  </main>
  <footer> <!-- 푸터 영역 -->
    <p> <!-- 문장 -->
      Copyright &copy;
      <script>
        document.write(new Date().getFullYear())  <!-- 현재 연도 자동으로 출력 -->
      </script> Etevers e-book. All Rights Reserved. <!-- 출판사 정보 -->
    </p>
  </footer>
</body>

 

3. login.html : 로그인 페이지

<!DOCTYPE html> <!-- HTML5 문서 유형 선언 -->
<html lang="en"> <!-- 문서 언어 설정 -->
<head> <!-- head 태그 시작 -->
    <meta charset="UTF-8"> <!-- 문자 인코딩 설정 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 반응형 웹 설정 -->
    <title>Document</title> <!-- 문서 제목 -->
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
    <!-- 구글 폰트 링크 -->
    <script src="https://kit.fontawesome.com/53a8c415f1.js" crossorigin="anonymous"></script>
    <!-- Font Awesome 아이콘 링크 -->
    <link rel="stylesheet" href="{{ url_for('static', filename='login.css')}}"> <!-- CSS 파일 링크 -->
</head> <!-- head 태그 종료 -->
<body>
    <div class="wrap">
        <div class="login">
            <h2>Log-in</h2>
            <div class="loginaction">
                <!-- 로그인 폼 -->
                <form action="{{ url_for('login')}}" method="post" autocomplete="off">
                    <!-- 메시지 출력 -->
                    <div class="msg">{{ msg }}</div>
                    <!-- 아이디 입력 -->
                    <div class="login_id">
                        <h4>ID</h4>
                        <input type="text" name="" id="" placeholder="ID">
                    </div>
                    <!-- 비밀번호 입력 -->
                    <div class="login_pw">
                        <h4>Password</h4>
                        <input type="password" name="" id="" placeholder="Password">
                    </div>
                    <!-- 기타 옵션 -->
                    <div class="login_etc">
                        <!-- 아이디 저장 체크박스 -->
                        <div class="checkbox">
                            <input type="checkbox" name="" id=""> 아이디 저장
                        </div>
                        <!-- 비밀번호 찾기 링크 -->
                        <div class="forgot_pw">
                            <a href="">비밀번호 찾기</a>
                        </div>
                    </div>
                    <!-- 로그인 버튼 -->
                    <div class="submit">
                        <input type="submit" value="login">
                    </div>
                </form>
            </div>
            <!-- 회원가입 버튼 -->
            <div class="submit">
                <a href="/signup">
                    <input type="submit" value="Sign Up">
                </a>
            </div>
        </div>
    </div>
</body>
</html>

 

4. sginup.html : 회원가입 페이지

<!DOCTYPE html> <!-- HTML5 문서 유형 선언 -->
<html lang="en"> <!-- 문서 언어 설정 -->
<head> <!-- head 태그 시작 -->
    <meta charset="UTF-8"> <!-- 문자 인코딩 설정 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 반응형 웹 설정 -->
    <title>Document</title> <!-- 문서 제목 -->
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
    <!-- 구글 폰트 링크 -->
    <script src="https://kit.fontawesome.com/53a8c415f1.js" crossorigin="anonymous"></script>
    <!-- Font Awesome 아이콘 링크 -->
    <link rel="stylesheet" href="{{ url_for('static', filename='login.css')}}"> <!-- CSS 파일 링크 -->
</head> <!-- head 태그 종료 -->
<body>
    <div class="wrap">
        <div class="login">
            <h2>Log-in</h2>
            <div class="loginaction">
                <!-- 로그인 폼 -->
                <form action="{{ url_for('login')}}" method="post" autocomplete="off">
                    <!-- 메시지 출력 -->
                    <div class="msg">{{ msg }}</div>
                    <!-- 아이디 입력 -->
                    <div class="login_id">
                        <h4>ID</h4>
                        <input type="text" name="" id="" placeholder="ID">
                    </div>
                    <!-- 비밀번호 입력 -->
                    <div class="login_pw">
                        <h4>Password</h4>
                        <input type="password" name="" id="" placeholder="Password">
                    </div>
                    <!-- 기타 옵션 -->
                    <div class="login_etc">
                        <!-- 아이디 저장 체크박스 -->
                        <div class="checkbox">
                            <input type="checkbox" name="" id=""> 아이디 저장
                        </div>
                        <!-- 비밀번호 찾기 링크 -->
                        <div class="forgot_pw">
                            <a href="">비밀번호 찾기</a>
                        </div>
                    </div>
                    <!-- 로그인 버튼 -->
                    <div class="submit">
                        <input type="submit" value="login">
                    </div>
                </form>
            </div>
            <!-- 회원가입 버튼 -->
            <div class="submit">
                <a href="/signup">
                    <input type="submit" value="Sign Up">
                </a>
            </div>
        </div>
    </div>
</body>
</html>

 

5. app.py : flask를 DB와 파이썬 연결

from flask import Flask, render_template, request, redirect, url_for, session # Flask, render_template, request, redirect, url_for, session 모듈 import
from flask_mysqldb import MySQL  # Flask MySQL 모듈 import
import MySQLdb.cursors  # MySQLdb.cursors 모듈 import
import re  # re 모듈 import
app = Flask(__name__, static_folder='static') # Flask 객체 생성
app.secret_key = 'your secret key' # 세션 보안을 위한 secret key 설정
app.config['MYSQL_HOST'] = 'localhost' # MySQL 호스트 설정
app.config['MYSQL_USER'] = 'root' # MySQL 유저 설정
app.config['MYSQL_PASSWORD'] = '1234' # MySQL 비밀번호 설정
app.config['MYSQL_DB'] = 'ebook' # MySQL 데이터베이스 설정
mysql = MySQL(app) # MySQL 객체 생성
# 홈페이지
@app.route('/')  # '/' 경로로 들어왔을 때 실행되는 함수
def home():
    return render_template('main.html')  # 'main.html' 파일을 렌더링하여 반환
# 로그인 페이지
@app.route('/login', methods=['GET', 'POST'])
def login():
    msg = ''
    if request.method == 'POST' and 'account' in request.form and 'password' in request.form:  # 만약 요청 방식이 POST이고, 폼 데이터에 'account'와 'password'가 모두 있다면
        account = request.form['account']  # POST 요청에서 account 값을 가져옴
        password = request.form['password']  # POST 요청에서 password 값을 가져옴
        cursor = mysql.connection.cursor(
            MySQLdb.cursors.DictCursor)  # MySQLdb 모듈을 사용하여 커서 생성
        cursor.execute(
            'SELECT * FROM user WHERE account = % s \
            AND password = % s', (account, password, ))  # SQL 쿼리 실행
        user = cursor.fetchone()  # 쿼리 결과를 가져옴
        if user:  # 쿼리 결과가 있으면
            session['loggedin'] = True  # 로그인 상태를 True로 변경
            session['account'] = user['account']  # 세션에 account 값을 저장
            session['password'] = user['password']  # 세션에 password 값을 저장
            msg = 'Logged in successfully !'  # 로그인 성공 메시지
            return render_template('main.html', msg=msg) # main.html 페이지를 렌더링하며 msg 값을 전달
        else:  # 쿼리 결과가 없으면
            msg = 'Incorrect username / password !'  # 로그인 실패 메시지
    return render_template('login.html', msg=msg) # login.html 페이지를 렌더링하며 msg 값을 전달

# 로그아웃
@app.route('/logout')
def logout():
    session.pop('loggedin', None)  # 'loggedin' 세션 삭제
    session.pop('account', None)  # 'account' 세션 삭제
    session.pop('password', None)  # 'password' 세션 삭제
    return redirect(url_for('login'))  # login 페이지로 리다이렉트
# 회원가입 페이지
@app.route('/signup', methods=['GET', 'POST'])
def signup():
    msg = ''
    # 다음 코드는 POST 요청이 들어왔을 때, 모든 필드가 채워졌는지 확인하고, 채워졌다면 해당 값을 변수에 저장한다.
    if request.method == 'POST' \
            and 'account' in request.form \
            and 'password' in request.form \
            and 'username' in request.form \
            and 'email' in request.form \
            and 'gender' in request.form \
            and 'birth' in request.form \
            and 'phonenumber' in request.form:
        account = request.form['account']
        password = request.form['password']
        username = request.form['username']
        email = request.form['email']
        gender = request.form['gender']
        birth = request.form['birth']
        phonenumber = request.form['phonenumber']
        # 다음 코드는 MySQL 데이터베이스에 연결하고, 입력한 계정이 이미 존재하는지 확인한다.
        cursor = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
        cursor.execute(
            'SELECT * FROM user WHERE account = % s', (account, ))
        user = cursor.fetchone()
        # 다음 코드는 이미 존재하는 계정이라면 에러 메시지를 출력한다.
        if user:
            msg = '이미 존재하는 계정입니다 !'
        # 다음 코드는 이메일 형식이 올바르지 않다면 에러 메시지를 출력한다.
        elif not re.match(r'[^@]+@[^@]+\.[^@]+', email):
            msg = 'Email 형식에 맞지 않습니다 !'
        # 다음 코드는 이름이 올바른 형식이 아니라면 에러 메시지를 출력한다.
        elif not re.match(r'[A-Za-z0-9]+', username):
            msg = 'name must contain only characters and numbers !'
        # 다음 코드는 모든 조건을 만족한다면, 데이터베이스에 새로운 사용자 정보를 추가한다.
        else:
            cursor.execute('INSERT INTO user VALUES \
                (% s, % s, % s, % s, % s, % s, % s)',
                           (account, password, username, email, gender, birth, phonenumber))
            mysql.connection.commit()
            msg = '가입을 환영합니다 !'
    # 다음 코드는 POST 요청이 들어왔지만, 필드가 채워지지 않았다면 에러 메시지를 출력한다.
    elif request.method == 'POST':
        msg = 'Please fill out the form !'
    # 다음 코드는 GET 요청이 들어왔을 때, 회원가입 페이지를 렌더링한다.
    return render_template('signup.html', msg=msg)
# 메인 페이지
@app.route("/main")
def index():
    if 'loggedin' in session:  # 로그인 여부를 세션에서 확인
        return render_template("main.html")  # 로그인 되어 있으면 main.html 페이지 렌더링
    return redirect(url_for('login'))  # 로그인 되어 있지 않으면 login 페이지로 리다이렉트
# 마이페이지
@app.route('/mypage.html')  # '/mypage.html' 경로로 요청이 들어오면
def mypage():  # mypage 함수 실행
    return render_template('mypage.html')  # mypage.html 파일을 렌더링하여 반환

if __name__ == '__main__':  # 현재 파일이 실행되는 메인 파일일 경우
    app.run(debug=True)  # Flask 애플리케이션을 debug 모드로 실행

 

6. user.sql : DB

create database ebook default character set utf8 collate utf8_general_ci; -- ebook 데이터베이스 생성 및 문자 인코딩 설정
use ebook -- ebook 데이터베이스 사용
create table user ( -- user 테이블 생성
    account char(30) not null, -- 계정(아이디) 열 생성, null 값 불가능
    password varchar(255) not null, -- 비밀번호 열 생성, null 값 불가능
    username varchar(50) not null, -- 사용자 이름 열 생성, null 값 불가능
    email varchar(100) not null, -- 이메일 열 생성, null 값 불가능
    gender varchar(10) not null, -- 성별 열 생성, null 값 불가능
    birth date not null, -- 생년월일 열 생성, null 값 불가능
    phonenumber varchar(30), -- 전화번호 열 생성, null 값 가능
    primary key(account) -- account 열을 기본키로 설정
) engine=InnoDB default charset=utf8; -- InnoDB 스토리지 엔진 사용 및 문자 인코딩 설정
반응형
반응형

프로젝트 4일차에 프론트쪽은 거의 완성되가는 느낌입니다. 이외의 작업은 DB를 연결하고 기타 기능들도 구현을 해야했으나, 강사님께서 진행하는 속도보다 훨씬 빠른 속도로 진행이 되어버렸기 때문에 DB 부분은 잠시 쉬어가고 아래 페이지들을 조금씩 다듬었는데,  소소한 문제들이 계속 나와서 어떻게 해야될지 머리를 싸매 고민하고 있습니다.

 

잘하는 친구들이 있어 공부를 하는 방향성에 대해서 조금씩 듣고 있는데 개발을 처음부터 끝까지 한다는 것 자체가 쉽지 않다는 것을 깨달아 가고 있습니다. 왜 이걸 통틀어 하는 직업군은 없고 DB 전문가, 프론트엔드 전문가, 백엔드 전문가들이 나뉘어 한 프로젝트에 수십, 많으면 수백명이 달려드는지 알 수 있었습니다.

 

웹페이지를 하나 만드는데 html로 페이지의 뼈대를 만들고, css로 디테일한 디자인을 다듬고, JAVA등을 이용해 기능을 구현합니다. 또 DB에 연결을 하고, 책들에 대한 정보를 고객에게 보여주는데, 보안도 신경써야하고... 서버를 구현해야하기에 하드웨어도 신경써야합니다.

 

최근 생성형 AI가 엄청나게 개발되고 있는데 그게 아니였다면 이렇게 빠르게 만들 수 있었을까 싶기도 합니다. 원래 대학교 수준에서 웹페이지를 서비스하려고 하면 프론트 엔드만 2~3주 잡기도 한다는 말을 듣고 소름이 끼치더군요. 단순한 코딩작업을 생성형 AI가 도와주니 좋다고 생각이 들다가 최근 생성형 AI가 생각보다 개발을 잘해줘서 개발자들이 자리가 없어지는 것 아니냐고 고민하는 말들이 슬프게 들리기도 합니다.

 

프로젝트 진행에 대해 적어야하는데 공부가 부족해 일기를 쓰는 느낌이 되어버린 것 같아 웃기면서 조금 슬프기도 하네요. 얼른 또 공부를 하러 가봐야겠습니다. 개발자들의 고뇌를 조금이나마 깨닫게 되는 시간이었습니다.

main page.html

<!DOCTYPE html>
<html>
<head>
    <title>My Ebook Page</title>
    <link rel="stylesheet" href="main.css">
</head>
<body>
    <header>
        <div class="logo">
            <img src="logo.png" alt="My Ebook Logo">
        </div>
        <div class="search">
            <input type="text" placeholder="Search...">
        </div>
        <div class="user">
            <a href="login.html">로그인</a> |
            <a href="mypage.html">마이 페이지</a>
        </div>
    </header>
    <nav>
        <ul>
            <li><a href="#">일반</a></li>
            <li><a href="#">로맨스</a></li>
            <li><a href="#">판타지</a></li>
            <li><a href="#">미스테리</a></li>
            <li><a href="#">추리</a></li>
        </ul>
    </nav>
    <section>
        <h2>추천 도서</h2>
        <div class="book-list">
            <!-- Recommended book 1 -->
            <div class="book">
                <img src="book1.jpg" alt="Book 1">
                <h3>세이노의 가르침</h3>
                <p>000년부터 발표된 그의 주옥같은 글들. 독자들이 자발적으로 만든 제본서는 물론, 전자책과 앱까지 나왔던 《세이노의 가르침》이 드디어 전국 서점에서 독자들을 마주한다. </p>
                <button>Read Now</button>
            </div>
            <!-- Recommended book 2 -->
            <div class="book">
                <img src="book2.jpg" alt="Book 2">
                <h3>NCS 통합 기본서</h3>
                <p>기획재정부 시행 ‘공공기관 채용박람회’ NCS 초청강사 집필! 공사 공단 및 금융권 필기 전형 대비</p>
                <button>Read Now</button>
            </div>
            <!-- Recommended book 3 -->
            <div class="book">
                <img src="book3.jpg" alt="Book 3">
                <h3>대한민국 청약지도</h3>
                <p>추첨제 부활! 사상 최대 특별공급 확대! 중도금 대출 완화! 당신이 알던 ‘청약’의 모든 것이 달라졌다</p>
                <button>Read Now</button>
            </div>
        </div>
        <h2>신상 도서</h2>
        <div class="book-list">
            <!-- New book 1 -->
            <div class="book">
                <img src="book4.jpg" alt="Book 4">
                <h3>내일을 바꾸는 인생 정보</h3>
                <p>평온한 밤을 위한 인생의 클래식, 잠 못 이루는 오늘, 당신이 묻고 고전이 답하다! </p>
                <button>Read Now</button>
            </div>
            <!-- New book 2 -->
            <div class="book">
                <img src="book5.jpg" alt="Book 5">
                <h3>면접바이블</h3>
                <p>독보적 취업 유튜버 ‘면접왕 이형’의 베스트셀러 ‘면접 바이블’이 개정판으로 돌아왔다!</p>
                <button>Read Now</button>
            </div>
            <!-- New book 3 -->
            <div class="book">
                <img src="book6.jpg" alt="Book 6">
                <h3>장하준의 경제 레시피</h3>
                <p>“자유 시장의 자유에 맡겨 두면 경제가 저절로 발전할까?” “사람들이 가난한 건 게으르기 때문일까?”</p>
                <button>Read Now</button>
            </div>
        </div>
    </section>
    <footer>
        <p>Copyright &copy;
          <script>
            document.write(new Date().getFullYear())
          </script> Etevers e-book. All Rights Reserved.</p>
    </footer>
</body>
</html>
main page.css

body {
    font-family: Arial, sans-serif;
    margin: 0;  
    padding: 0;
}
 
header {
    position: relative;
    height: 100px;
    background-color: #ffffff;
    color: #000000;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
}
.logo {
    top: 0px;
    left: 0px;
}
.logo img {
    height: 50px;
}
.search {
    position: absolute;
    top: 50%;
    left: 55%;
    transform : translate(-50%, -10%);
}
.search input[type="text"] {
    min-width: 60%;
    max-width: 100%;
}
 
@media screen and (min-width: 480px) {
    .search input[type="text"] {
      min-width: 500px;
      max-width: 1000px;
      font-size: 18px;
    }
}
.search input[type="text"] {
    width: 100%;
    padding: 8px;
    font-size: 16px;
    border-color: #757575;
    border-radius: 20px;
    border: 2px solid #000000;
}
.user a {
    color: #000000;
    font-size: 16px;
    text-decoration: none;
}
 
nav {
    background-color: #f2f2f2;
    border-bottom: 1px solid #ccc;
    padding: 1rem;
}
 
nav ul {
    display: flex;
    flex-direction: row;
    justify-content: center;
    list-style: none;
    margin: 0;
    padding: 0;
}
 
nav li {
    margin: 0 1rem;
}
 
nav a {
    color: #333;
    font-size: 16px;
    font-style: bold;
    text-decoration: none;
    transition: color 0.2s ease-in-out;
}
 
nav a:hover {
    color: #666;
}
 
  section {
    padding: 1rem;
  }
 
  h2 {
    font-size: 24px;
    margin-bottom: 1rem;
  }
 
  .book-list {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    flex-wrap: wrap;
  }
 
  .book {
    width: 30%;
    margin-bottom: 1rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    border-radius: 5px;
    overflow: hidden;
  }
 
  .book img {
    width: 70%;
    height: 70%;
    object-fit: cover;
    margin: auto;
    display: block;
  }
 
  .book h3 {
    font-size: 18px;
    margin: 0;
    padding: 0.5rem;
  }
 
  .book p {
    font-size: 14px;
    margin: 0;
    padding: 0.5rem;
  }
 
  .book button {
    display: block;
    margin: 0 auto;
    padding: 0.5rem;
    border: none;
    border-radius: 20px;
    background-color: #333;
    color: #fff;
    font-size: 16px;
    cursor: pointer;
  }
 
  .book button:hover {
    background-color: #666;
  }
  footer {
    background-color: #ddd;
    text-align: center;
    padding: 1rem;
  }

 

login page.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
    <script src="https://kit.fontawesome.com/53a8c415f1.js" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="login.css">
</head>
<body>
    <div class="wrap">
        <div class="login">
            <h2>Log-in</h2>
            <div class="login_id">
                <h4>ID</h4>
                <input type="ID" name="" id="" placeholder="ID">
            </div>
            <div class="login_pw">
                <h4>Password</h4>
                <input type="password" name="" id="" placeholder="Password">
            </div>
            <div class="login_etc">
                <div class="checkbox">
                <input type="checkbox" name="" id=""> 아이디 저장
                </div>
                <div class="forgot_pw">
                <a href="">비밀번호 찾기</a>
                </div>
            </div>
            <div class="submit">
                <input type="submit" value="Login">
            </div>
            <div class="submit">
                <a href = "signup.html">
                <input type="submit" value="Sign Up">
                </a>
            </div>
        </div>
    </div>
</body>
</html>
login page.css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: "Noto Sans KR", sans-serif;
  }
 
  a {
    text-decoration: none;
    color: black;
  }
 
  li {
    list-style: none;
  }
 
  .wrap {
    width: 100%;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.1);
  }
 
  .login {
    width: 30%;
    height: 600px;
    background: white;
    border-radius: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
  }
 
  h2 {
    color: rgb(55, 0, 255);
    font-size: 2em;
  }
  .login_id {
    margin-top: 20px;
    width: 80%;
  }
 
  .login_id input {
    width: 100%;
    height: 50px;
    border-radius: 30px;
    margin-top: 10px;
    padding: 0px 20px;
    border: 1px solid lightgray;
    outline: none;
  }
 
  .login_pw {
    margin-top: 20px;
    width: 80%;
  }
 
  .login_pw input {
    width: 100%;
    height: 50px;
    border-radius: 30px;
    margin-top: 10px;
    padding: 0px 20px;
    border: 1px solid lightgray;
    outline: none;
  }
 
  .login_etc {
    padding: 10px;
    width: 80%;
    font-size: 14px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: bold;
  }
 
  .submit{
    margin-top: 10px;
    width: 80%;
  }
  .submit input {
    width: 100%;
    height: 50px;
    border: 0;
    outline: none;
    border-radius: 40px;
    background: linear-gradient(to left, rgb(55, 0, 255), rgb(11, 0, 71));
    color: white;
    font-size: 1.2em;
    letter-spacing: 2px;
    cursor: pointer;
  }

 

sign up page.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
    <script src="https://kit.fontawesome.com/53a8c415f1.js" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="signup.css">
</head>
<body>
    <div class="wrap">
        <div class="login">
            <h2>Sign Up</h2>
            <div class="login_id">
                <h4>ID</h4>
                <input type="ID" name="id" id="id" placeholder="ID" required>
            </div>
            <div class="login_pw">
                <h4>Password</h4>
                <input type="password" name="pw" id="pw" placeholder="Password" required>
            </div>
       
            <div class="name">
                <h4>Name</h4>
                <input type="text" id="name" name="name" placeholder="Name"required>
            </div>
            <div class="email">
                <h4>Email</h4>
                <input type="email" id="email" name="email" placeholder="Email"required>
            </div>
            <div class="gender">
                <h4>Gender</h4>
                <select id="gender" name="gender">
                  <option value="">Select gender</option>
                  <option value="male">Male</option>
                  <option value="female">Female</option>
                  <option value="other">Other</option>
                </select>
            </div>
            <div class="birth">
                <h4>birth</h4>
                <input type="date" id="birth" name="birth" required>
            </div>
            <div class="phone">
                <h4>phonenumber</h4>
                <input type="tel" id="phone" name="phone" placeholder="Enter your phone number" required>
            </div>
            <div class="submit">
                <input type="submit" value="Register">
            </div>
        </div>
    </div>
</body>
</html>
sign up page.css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: "Noto Sans KR", sans-serif;
}
 
  a {
    text-decoration: none;
    color: black;
  }
 
  li {
    list-style: none;
  }
  .wrap {
    width: 100%;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.1);
  }
 
  .login {
    width: 30%;
    height: 800px;
    background: white;
    border-radius: 20px;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
  }
 
  h2 {
    color: rgb(55, 0, 255);
    font-size: 2em;
  }
  .login_id {
    margin-top: 5px;
    width: 80%;
  }
 
  .login_id input {
    width: 100%;
    height: 50px;
    border-radius: 30px;
    margin-top: 10px;
    padding: 0px 20px;
    border: 1px solid lightgray;
    outline: none;
  }
 
  .login_pw {
    margin-top: 5px;
    width: 80%;
  }
 
  .login_pw input {
    width: 100%;
    height: 50px;
    border-radius: 30px;
    margin-top: 10px;
    padding: 0px 20px;
    border: 1px solid lightgray;
    outline: none;
  }
 
  .login_etc {
    padding: 10px;
    width: 80%;
    font-size: 14px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: bold;
  }
 
.name {
    width: 80%;
    margin-top: 5px;
}
.name input[type="text"] {
    width: 100%;
    height: 50px;
    border-radius: 30px;
    margin-top: 10px;
    padding: 0px 20px;
    border: 1px solid lightgray;
    outline: none;
}
.email {
    width: 80%;
    margin-top: 5px;
  }
 
.email input[type="email"] {
    width: 100%;
    height: 50px;
    border-radius: 30px;
    margin-top: 10px;
    padding: 0px 20px;
    border: 1px solid lightgray;
    outline: none;
}
.gender {
  width: 80%;
  margin-top: 5px;
}
.gender select {
  width: 100%;
  height: 50px;
  border-radius: 30px;
  margin-top: 10px;
  padding: 0px 20px;
  border: 1px solid lightgray;
  outline: none;
}
.birth {
    width: 80%;
    margin-top: 5px;
    }
   
.birth input[type="date"] {
    width: 100%;
    height: 50px;
    border-radius: 30px;
    margin-top: 10px;
    padding: 0px 20px;
    border: 1px solid lightgray;
    outline: none;
}
.phone {
  width: 80%;
  margin-top: 5px;
}
.phone input[type="tel"] {
  width: 100%;
  height: 50px;
  border-radius: 30px;
  margin-top: 10px;
  padding: 0px 20px;
  border: 1px solid lightgray;
  outline: none;
}
.submit{
    margin-top: 10px;
    width: 80%;
  }
  .submit input {
    width: 100%;
    height: 50px;
    border: 0;
    outline: none;
    border-radius: 40px;
    background: linear-gradient(to left, rgb(55, 0, 255), rgb(11, 0, 71));
    color: white;
    font-size: 1.2em;
    letter-spacing: 2px;
  }

 

my page.html

<!DOCTYPE html>
<html>
<head>
  <title>My Ebook Page</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Account</title>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="mypage.css">
</head>
<body>
  <header>
    <div class="logo">
      <img src="logo.png" alt="My Ebook Logo">
    </div>
    <div class="search">
      <input type="text" placeholder="Search...">
    </div>
    <div class="user">
      <a href="#">로그아웃</a> | <a href="#">마이 페이지</a>
    </div>
  </header>
  <nav>
    <ul>
      <li><a href="#">일반</a></li>
      <li><a href="#">로맨스</a></li>
      <li><a href="#">판타지</a></li>
      <li><a href="#">미스테리</a></li>
      <li><a href="#">추리</a></li>
    </ul>
  </nav>
    <main>
      <h2>나의 정보</h2>
      <dl>
        <dt>이름</dt>
        <dd>이주성</dd>
        <dt>이메일</dt>
        <dd>wntjd@example.com</dd>
        <dt>전화번호</dt>
        <dd>010-1234-5678</dd>
        <dt>주소</dt>
        <dd>서울시 강남구 역삼동 123-456</dd>
      </dl>
      <h2>최근 주문 내역</h2>
      <table>
        <thead>
          <tr>
            <th>주문 번호</th>
            <th>제목</th>
            <th>가격</th>
            <th>구매일자</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>12345</td>
            <td>해리포터와 마법사의 돌</td>
            <td>12,000원</td>
            <td>2022-04-18</td>
          </tr>
          <tr>
            <td>12344</td>
            <td>물고기의 언어</td>
            <td>9,000원</td>
            <td>2022-04-17</td>
          </tr>
        </tbody>
      </table>
    </main>
    <footer>
      <p>Copyright &copy;
        <script>
          document.write(new Date().getFullYear())
        </script> Etevers e-book. All Rights Reserved.</p>
    </footer>
  </body>
my page.css

/* 전체 body 스타일 */
body {
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
    font-size: 16px;
    line-height: 1.5;
  }
 
  /* header 스타일 */
  header {
    position: relative;
    height: 100px;
    background-color: #ffffff;
    color: #000000;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
  }
 
  .logo {
    top: 0px;
    left: 0px;
  }
 
  .logo img {
    height: 50px;
  }
 
  .search {
    position: absolute;
    top: 50%;
    left: 55%;
    transform : translate(-50%, -10%);
 
  }
 
  .search input[type="text"] {
    min-width: 200px;
    max-width: 80%;
  }
   
  @media screen and (min-width: 480px) {
    .search input[type="text"] {
      min-width: 500px;
      max-width: 1000px;
      font-size: 18px;
    }
  }
 
  .search input[type="text"] {
    width: 100%;
    padding: 8px;
    font-size: 16px;
    border-color: #757575;
    border-radius: 20px;
    border: 2px solid #000000;
  }
 
  .user a {
    color: #000000;
    font-size: 16px;
    text-decoration: none;
  }
  /* Main content styles */
  nav {
    background-color: #f2f2f2;
    border-bottom: 1px solid #ccc;
    padding: 1rem;
  }
   
  nav ul {
    display: flex;
    flex-direction: row;
    justify-content: center;
    list-style: none;
    margin: 0;
    padding: 0;
  }
   
  nav li {
    margin: 0 1rem;
  }
   
  nav a {
    color: #333;
    font-size: 16px;
    font-style: bold;
    text-decoration: none;
    transition: color 0.2s ease-in-out;
  }
   
  nav a:hover {
    color: #666;
  }
 
  /* main 스타일 */
  main {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }
 
  h2 {
    font-size: 1.5rem;
    margin-top: 3rem;
    margin-bottom: 1.5rem;
  }
 
  dl {
    display: grid;
    grid-template-columns: 150px 1fr;
    gap: 1rem;
  }
 
  dt, dd {
    padding: 0.5rem;
    border: 1px solid #ccc;
  }
 
  /* 최근 주문 내역 테이블 스타일 */
  table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 3rem;
  }
 
  thead {
    background-color: #333;
    color: #fff;
  }
 
  th, td {
    padding: 0.5rem;
    text-align: left;
    border: 1px solid #ccc;
  }
 
  tbody tr:nth-child(even) {
    background-color: #f2f2f2;
  }
 
  /* footer 스타일 */
  footer {
    background-color: #ddd;
    text-align: center;
    padding: 1rem;
  }
 

화면을 줄이면 이렇게 겹치는게 참 웃기면서 어떻게 해야하나 싶습니다.

반응형
반응형

초안을 간단한 툴들로 만들었습니다. html과 css로 타이핑해서 만드는 것도 좋지만, 요즘은 툴들이 너무 잘나와서 언어 몰라도 프로그래밍 할 수 있다는게 조금씩 무슨 말인지 알 것 같습니다. ChatGPT같은 생성형 AI가 짜주는 코드들도 잘 활용하면 멋진 작품이 탄생할 것 같다는 생각이 듭니다.

이미지를 넣어보려고 하는데 너무 뒤죽박죽으로 적용되고, 어디서부터 찾아나가야할지 조금더 알아봐야겠습니다. 뭔가 틀들이 잡혀나가는 모습을 보니 점점 재미있어지네요. 오늘은 이렇게 간단히 짚고 넘어가겠습니다.

반응형