7. 로그인 화면 구성

강혜성·2023년 10월 2일
0

사이드 프로젝트

목록 보기
7/12

프로젝트의 뼈대를 만드는 작업 중이므로 HTTP에 맞는 Stateless 구현하기 위해 JWT 방식과 DB Resource 측면 및 서버 확장 가능성 등을 고려해 JWT, OAuth 로그인 방식을 구현하기로 결정 추후 필요시 Session을 추가로 구현하는 방식을 고려하며 로그인 기능을 구현할 예정이다.

화면 디자인의 경우에는 모바일과 웹에서 사용자 경험을 높일 수 있도록 반응형 웹앱 형태로 변경할 예정이다.
(HelloWorlde.vue 컴포넌트 이름도 변경할 예정, Vue 파일 관리 방식도 확인 필요)

우선 로그인 및 회원 가입 기능을 구현하고 메인 페이지에 반영하도록 적용한 후에 디자인은 추후 적용할 예정이다.

작업 Branch 생성

git branch login_signup_screen feature

로그인 화면 구성

가장 기본적으로 로그인 화면을 생성하고 해당 화면에서 ID, PW를 입력받도록 화면을 구성한다.

로그인 할 수 있도록 오른쪽 상단에 위치한 Toggle order 옆에 로그인 버튼을 생성하고 로그인 화면으로 갈 수 있도록 만든다.

로그인 아이콘은 뷰티파이 아이콘을 사용한다.

mdi-login / mdi-login-variant 로고를 임시로 사용한다.

뷰티파이 아이콘 적용

      <v-btn icon>
        <v-icon>mdi-login</v-icon>
      </v-btn>

아이콘이 적용되고 클릭도 되는 것을 확인할 수 있다. (코드는 전체 코드 참고)

버튼 클릭 후 로그인 페이지로 이동

1. 로그인 화면 생성

  • components 밑 LoginPage.vue 생성 / 로그인 페이지를 확인할 수 있도록 Login Page 문구 출력하도록 코드 구성
<template>
    <div>
        Login Page
    </div>
</template>

<script>

</script>

2. Router Path 생성

  • router 밑 index.js에 login에 해당하는 경로 설정
    LoginPage import / path, name, component 생성
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginPage from '../components/LoginPage.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  },
  {
    path: '/login',
    name: 'login',
    component: LoginPage
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

3. 버튼 클릭 시 Page 이동

  • router-link 또는 @click에 함수를 적용해 구현할 수 있지만 $router.push를 이용해 로그인 페이지로 이동하게 만들었다.
      <v-btn icon @click="$router.push('/login')">
        <v-icon>mdi-login</v-icon>
      </v-btn>

클릭 시 로그인 페이지(LoginPage.vue)로 이동하는 것을 확인했다.

로그인 페이지 화면 구성

뷰티파이 text-fileds 이용한다.

LoginPage.vue 코드

<template>
    <v-sheet class="pa-12" rounded>
        <v-card class="mx-auto px-6 py-8" max-width="60%" :elevation="12">
            <v-form
            v-model="form"
            @submit.prevent="onSubmit"
            >
                <v-text-field
                    v-model="email"
                    :readonly="loading"
                    :rules="[required]"
                    class="mb-2"
                    clearable
                    label="Email"
                ></v-text-field>
        
                <v-text-field
                    v-model="password"
                    :readonly="loading"
                    :rules="[required]"
                    clearable
                    label="Password"
                    placeholder="Enter your password"
                ></v-text-field>
        
                <br>
        
                <v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="success"
                    size="large"
                    type="submit"
                    variant="elevated"
                >
                    Sign In
                </v-btn>

                <br>
                
                <v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="primary"
                    size="large"
                    type="submit"
                    variant="elevated"
                >
                    Sign Up
                </v-btn>
            </v-form>
        </v-card>
    </v-sheet>
</template>

<script>

</script>

회원 가입 페이지 화면 구성

  • components 밑 SignUpPage.vue 생성, 로그인 페이지에서 SIGN IN 버튼만 제거한 후 생성

SignUpPage.vue 코드

<template>
    <v-sheet class="pa-12" rounded>
        <v-card class="mx-auto px-6 py-8" max-width="60%" :elevation="12">
            <v-form
            v-model="form"
            @submit.prevent="onSubmit"
            >
                <v-text-field
                    v-model="email"
                    :readonly="loading"
                    :rules="[required]"
                    class="mb-2"
                    clearable
                    label="Email"
                ></v-text-field>
        
                <v-text-field
                    v-model="password"
                    :readonly="loading"
                    :rules="[required]"
                    clearable
                    label="Password"
                    placeholder="Enter your password"
                ></v-text-field>

                <br>
                
                <v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="primary"
                    size="large"
                    type="submit"
                    variant="elevated"
                >
                    Sign Up
                </v-btn>
            </v-form>
        </v-card>
    </v-sheet>
</template>

<script>

</script>

LoginPage.vue에서 Sign Up 버튼을 눌렀을 경우 해당 페이지로 이동할 수 있도록 index.js에서 route 설정 및 버튼 이벤트 추가

index.js 코드

...
import LoginPage from '../components/LoginPage.vue'
import SignUpPage from '../components/SignUpPage.vue'

const routes = [
  ...
  {
    path: '/login',
    name: 'login',
    component: LoginPage
  },
  {
    path: '/signUp',
    name: 'signUp',
    component: SignUpPage
  }
]

LoginPage.vue 코드

...
				<v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="success"
                    size="large"
                    type="submit"
                    variant="elevated"
                >
                    Sign In
                </v-btn>

                <br>
                
                <v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="primary"
                    size="large"
                    type="submit"
                    variant="elevated"
                    @click="$router.push('/signUp')"
                >
                    Sign Up
                </v-btn>
...

로그인 페이지, 회원 가입 페이지 화면 구성 완료.
단순히 화면만 구성 완료된 것으로 Script를 통해 백엔드 통신 및 Password가 노출되지 않도록 type 지정 추가 필요.

LoginPage와 SignUpPage의 버튼에서 form을 검사하는 구문이 있다.
LoginPage에서 Sign Up의 경우에는 양식 검사 없이 바로 접근할 수 있어야 하므로 코드를 삭제한다.

:disabled="!form"

기능 구현이 완료되면 UX를 고려한 디자인도 적용할 예정...!

Git push

git add / commit / push

git add .

git commit -m "HelloWorlde.vue : 로그인 아이콘 생성,
LoginPage.vue : 로그인 페이지 화면 생성,
SignUpPage.vue : 회원가입 페이지 화면 생성,
index.js : login, signUp 경로 생성"

git push origin login_signup_screen

브랜치 정상반영 확인, Compare & pull request

pull request & Merge 이후 delete branch

전체 코드

1. 아이콘 적용 후 전체 코드

  • HelloWorld.vue
<template>
  <v-app id="inspire">
    <v-navigation-drawer v-model="drawer">
      <v-sheet
        color="grey-lighten-4"
        class="pa-4"
      >
        <v-avatar
          class="mb-4"
          color="grey-darken-1"
          size="64"
        ></v-avatar>
        <div>john@google.com</div>
      </v-sheet>

      <v-divider></v-divider>

      <v-list>
        <v-list-item
          v-for="[icon, text] in links"
          :key="icon"
          link
        >
          <template v-slot:prepend>
            <v-icon>{{ icon }}</v-icon>
          </template>

          <v-list-item-title>{{ text }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-navigation-drawer>
    
    <v-app-bar
      :order="order"
      flat
      title="Application bar"
    >

      <template v-slot:append>
        <v-switch
          v-model="order"
          hide-details
          inset
          label="Toggle order"
          true-value="-1"
          false-value="0"
        ></v-switch>
      </template>

      <!-- Activator Slot Start -->
      <v-menu>
        <template v-slot:activator="{ props }">
          <v-btn
            color="primary"
            v-bind="props"
          >
            Activator slot
          </v-btn>
        </template>
        <v-list>
          <v-list-item
            v-for="(item, index) in items"
            :key="index"
            :value="index"
          >
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
      <!-- Activator Slot End -->

      <!-- Activator2 Slot Start -->
      <v-menu>
        <template v-slot:activator="{ props }">
          <v-btn
            color="primary"
            v-bind="props"
          >
            Activator2 slot
          </v-btn>
        </template>
        <v-list>
          <v-list-item
            v-for="(item, index) in items2"
            :key="index"
            :value="index"
          >
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
      <!-- Activator2 Slot End -->

      
      <v-btn icon>
        <v-icon>mdi-login</v-icon>
      </v-btn>

    </v-app-bar>

    <v-main class="bg-grey-lighten-2">
      <v-container>
        <v-row>
          <template v-for="n in 4" :key="n">
            <v-col
              class="mt-2"
              cols="12"
            >
              <strong>Category {{ n }}</strong>
            </v-col>

            <v-col
              v-for="j in 6"
              :key="`${n}${j}`"
              cols="6"
              md="2"
            >
              <v-sheet height="150"></v-sheet>
            </v-col>
          </template>
        </v-row>
      </v-container>
    </v-main>
  </v-app>
  
</template>

<script>

export default {
  name: 'HelloWorld',

  data: () => ({
    order: 0,
    drawer: null,
    items: [
        { title: 'Item1 Click Me 1' },
        { title: 'Item1 Click Me 2' },
        { title: 'Item1 Click Me 3' },
        { title: 'Item1 Click Me 4' },
      ],
    items2: [
        { title: 'Item2 Click Me 1' },
        { title: 'Item2 Click Me 2' },
        { title: 'Item2 Click Me 3' },
        { title: 'Item2 Click Me 4' },
      ],
      links: [
        ['mdi-inbox-arrow-down', 'Inbox'],
        ['mdi-send', 'Send'],
        ['mdi-delete', 'Trash'],
        ['mdi-alert-octagon', 'Spam'],
      ],
  }),
}
</script>

2. 로그인 페이지 이동 후 전체 코드

  • HelloWorld.vue
<template>
  <v-app id="inspire">
    <v-navigation-drawer v-model="drawer">
      <v-sheet
        color="grey-lighten-4"
        class="pa-4"
      >
        <v-avatar
          class="mb-4"
          color="grey-darken-1"
          size="64"
        ></v-avatar>
        <div>john@google.com</div>
      </v-sheet>

      <v-divider></v-divider>

      <v-list>
        <v-list-item
          v-for="[icon, text] in links"
          :key="icon"
          link
        >
          <template v-slot:prepend>
            <v-icon>{{ icon }}</v-icon>
          </template>

          <v-list-item-title>{{ text }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-navigation-drawer>
    
    <v-app-bar
      :order="order"
      flat
      title="Application bar"
    >

      <template v-slot:append>
        <v-switch
          v-model="order"
          hide-details
          inset
          label="Toggle order"
          true-value="-1"
          false-value="0"
        ></v-switch>
      </template>

      <!-- Activator Slot Start -->
      <v-menu>
        <template v-slot:activator="{ props }">
          <v-btn
            color="primary"
            v-bind="props"
          >
            Activator slot
          </v-btn>
        </template>
        <v-list>
          <v-list-item
            v-for="(item, index) in items"
            :key="index"
            :value="index"
          >
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
      <!-- Activator Slot End -->

      <!-- Activator2 Slot Start -->
      <v-menu>
        <template v-slot:activator="{ props }">
          <v-btn
            color="primary"
            v-bind="props"
          >
            Activator2 slot
          </v-btn>
        </template>
        <v-list>
          <v-list-item
            v-for="(item, index) in items2"
            :key="index"
            :value="index"
          >
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
      <!-- Activator2 Slot End -->

      
      <v-btn icon @click="$router.push('/login')">
        <v-icon>mdi-login</v-icon>
      </v-btn>

    </v-app-bar>

    <v-main class="bg-grey-lighten-2">
      <v-container>
        <v-row>
          <template v-for="n in 4" :key="n">
            <v-col
              class="mt-2"
              cols="12"
            >
              <strong>Category {{ n }}</strong>
            </v-col>

            <v-col
              v-for="j in 6"
              :key="`${n}${j}`"
              cols="6"
              md="2"
            >
              <v-sheet height="150"></v-sheet>
            </v-col>
          </template>
        </v-row>
      </v-container>
    </v-main>
  </v-app>
  
</template>

<script>

export default {
  name: 'HelloWorld',

  data: () => ({
    order: 0,
    drawer: null,
    items: [
        { title: 'Item1 Click Me 1' },
        { title: 'Item1 Click Me 2' },
        { title: 'Item1 Click Me 3' },
        { title: 'Item1 Click Me 4' },
      ],
    items2: [
        { title: 'Item2 Click Me 1' },
        { title: 'Item2 Click Me 2' },
        { title: 'Item2 Click Me 3' },
        { title: 'Item2 Click Me 4' },
      ],
      links: [
        ['mdi-inbox-arrow-down', 'Inbox'],
        ['mdi-send', 'Send'],
        ['mdi-delete', 'Trash'],
        ['mdi-alert-octagon', 'Spam'],
      ],
  }),
}
</script>
  • index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginPage from '../components/LoginPage.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  },
  {
    path: '/login',
    name: 'login',
    component: LoginPage
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

3. 회원 가입 페이지 이후 전체 코드

LoginPage.vue

<template>
    <v-sheet class="pa-12" rounded>
        <v-card class="mx-auto px-6 py-8" max-width="60%" :elevation="12">
            <v-form
            v-model="form"
            @submit.prevent="onSubmit"
            >
                <v-text-field
                    v-model="email"
                    :readonly="loading"
                    :rules="[required]"
                    class="mb-2"
                    clearable
                    label="Email"
                ></v-text-field>
        
                <v-text-field
                    v-model="password"
                    :readonly="loading"
                    :rules="[required]"
                    clearable
                    label="Password"
                    type="password"
                    placeholder="Enter your password"
                ></v-text-field>
        
                <br>
        
                <v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="success"
                    size="large"
                    type="submit"
                    variant="elevated"
                >
                    Sign In
                </v-btn>

                <br>
                
                <v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="primary"
                    size="large"
                    type="submit"
                    variant="elevated"
                    @click="$router.push('/signUp')"
                >
                    Sign Up
                </v-btn>
            </v-form>
        </v-card>
    </v-sheet>
</template>

<script>

</script>

SignUpPage.vue

<template>
    <v-sheet class="pa-12" rounded>
        <v-card class="mx-auto px-6 py-8" max-width="60%" :elevation="12">
            <v-form
            v-model="form"
            @submit.prevent="onSubmit"
            >
                <v-text-field
                    v-model="email"
                    :readonly="loading"
                    :rules="[required]"
                    class="mb-2"
                    clearable
                    label="Email"
                ></v-text-field>
        
                <v-text-field
                    v-model="password"
                    :readonly="loading"
                    :rules="[required]"
                    clearable
                    label="Password"
                    placeholder="Enter your password"
                ></v-text-field>

                <br>
                
                <v-btn
                    :disabled="!form"
                    :loading="loading"
                    block
                    color="primary"
                    size="large"
                    type="submit"
                    variant="elevated"
                >
                    Sign Up
                </v-btn>
            </v-form>
        </v-card>
    </v-sheet>
</template>

<script>

</script>

index.js

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginPage from '../components/LoginPage.vue'
import SignUpPage from '../components/SignUpPage.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  },
  {
    path: '/login',
    name: 'login',
    component: LoginPage
  },
  {
    path: '/signUp',
    name: 'signUp',
    component: SignUpPage
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

HelloWorld.vue

<template>
  <v-app id="inspire">
    <v-navigation-drawer v-model="drawer">
      <v-sheet
        color="grey-lighten-4"
        class="pa-4"
      >
        <v-avatar
          class="mb-4"
          color="grey-darken-1"
          size="64"
        ></v-avatar>
        <div>john@google.com</div>
      </v-sheet>

      <v-divider></v-divider>

      <v-list>
        <v-list-item
          v-for="[icon, text] in links"
          :key="icon"
          link
        >
          <template v-slot:prepend>
            <v-icon>{{ icon }}</v-icon>
          </template>

          <v-list-item-title>{{ text }}</v-list-item-title>
        </v-list-item>
      </v-list>
    </v-navigation-drawer>
    
    <v-app-bar
      :order="order"
      flat
      title="Application bar"
    >

      <template v-slot:append>
        <v-switch
          v-model="order"
          hide-details
          inset
          label="Toggle order"
          true-value="-1"
          false-value="0"
        ></v-switch>
      </template>

      <!-- Activator Slot Start -->
      <v-menu>
        <template v-slot:activator="{ props }">
          <v-btn
            color="primary"
            v-bind="props"
          >
            Activator slot
          </v-btn>
        </template>
        <v-list>
          <v-list-item
            v-for="(item, index) in items"
            :key="index"
            :value="index"
          >
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
      <!-- Activator Slot End -->

      <!-- Activator2 Slot Start -->
      <v-menu>
        <template v-slot:activator="{ props }">
          <v-btn
            color="primary"
            v-bind="props"
          >
            Activator2 slot
          </v-btn>
        </template>
        <v-list>
          <v-list-item
            v-for="(item, index) in items2"
            :key="index"
            :value="index"
          >
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item>
        </v-list>
      </v-menu>
      <!-- Activator2 Slot End -->

      
      <v-btn icon @click="$router.push('/login')">
        <v-icon>mdi-login</v-icon>
      </v-btn>

    </v-app-bar>

    <v-main class="bg-grey-lighten-2">
      <v-container>
        <v-row>
          <template v-for="n in 4" :key="n">
            <v-col
              class="mt-2"
              cols="12"
            >
              <strong>Category {{ n }}</strong>
            </v-col>

            <v-col
              v-for="j in 6"
              :key="`${n}${j}`"
              cols="6"
              md="2"
            >
              <v-sheet height="150"></v-sheet>
            </v-col>
          </template>
        </v-row>
      </v-container>
    </v-main>
  </v-app>
  
</template>

<script>

export default {
  name: 'HelloWorld',

  data: () => ({
    order: 0,
    drawer: null,
    items: [
        { title: 'Item1 Click Me 1' },
        { title: 'Item1 Click Me 2' },
        { title: 'Item1 Click Me 3' },
        { title: 'Item1 Click Me 4' },
      ],
    items2: [
        { title: 'Item2 Click Me 1' },
        { title: 'Item2 Click Me 2' },
        { title: 'Item2 Click Me 3' },
        { title: 'Item2 Click Me 4' },
      ],
      links: [
        ['mdi-inbox-arrow-down', 'Inbox'],
        ['mdi-send', 'Send'],
        ['mdi-delete', 'Trash'],
        ['mdi-alert-octagon', 'Spam'],
      ],
  }),
}
</script>

0개의 댓글