SyuanYu 1 år sedan
förälder
incheckning
edcccf5c9e

+ 1 - 1
src/assets/css/style.css

@@ -216,7 +216,7 @@ input:focus-visible {
   border-bottom: 2px solid #fff;
 }
 .main-card .card-title h3 {
-  font-size: 20px;
+  font-size: 18px;
   font-weight: 400;
   text-align: center;
   line-height: 24px;

+ 1 - 1
src/assets/css/style.scss

@@ -221,7 +221,7 @@ input:focus-visible {
     border-bottom: 2px solid #fff;
 
     h3 {
-      font-size: 20px;
+      font-size: 18px;
       font-weight: 400;
       text-align: center;
       line-height: 24px;

+ 6 - 3
src/components/Navbar.vue

@@ -1,7 +1,9 @@
 <script setup>
 import { ref } from "vue";
+import { useMainStore } from "@/stores/store";
 import Login from "@/views/Login.vue";
 
+const store = useMainStore();
 let menuShow = ref(false);
 let collegeMenuShow = ref(false);
 let dialog = ref(false);
@@ -23,7 +25,7 @@ function handleClose(value) {
     </router-link>
     <ul class="menu align-center" :class="{ slider: menuShow }">
       <li>
-        <router-link :to="'/news'">最新消息</router-link>
+        <router-link :to="'/news'">重要訊息</router-link>
       </li>
       <li>
         <router-link :to="'/course-list'">探索課程</router-link>
@@ -67,12 +69,13 @@ function handleClose(value) {
         <!-- <router-link :to="'/login'">
           學員登入
         </router-link> -->
-        <v-dialog v-model="dialog" max-width="450">
+        <v-dialog v-model="dialog" max-width="450" v-if="!store.loginState">
           <template v-slot:activator="{ props }">
             <a href="javascript:;" v-bind="props">學員登入</a>
           </template>
           <Login @close="handleClose" />
         </v-dialog>
+        <router-link :to="'/user-profile'" v-else>學員專區</router-link>
       </li>
       <!-- <li>EN</li> -->
       <li class="d-none d-lg-block">
@@ -124,7 +127,7 @@ function handleClose(value) {
 
     @media (max-width: 1280px) {
       position: absolute;
-      top: 80.5px;
+      top: 82px;
       left: 0;
       right: 0;
       z-index: 100;

+ 1 - 1
src/components/NavbarSub.vue

@@ -14,7 +14,7 @@ function toggleMenu() {
       <img src="@/assets/img/logo.png" alt="" />
     </router-link>
     <ul class="menu d-md-flex align-center" :class="{ slider: menuShow }">
-      <li>最新消息</li>
+      <li>重要訊息</li>
       <li>
         <router-link :to="'/course-list'">探索課程</router-link>
       </li>

+ 23 - 0
src/router/index.js

@@ -1,8 +1,10 @@
 import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
 import { defineAsyncComponent } from 'vue';
+import { useMainStore } from "@/stores/store";
 
 const Home = defineAsyncComponent(() => import('@/views/Home.vue'));
 const Login = defineAsyncComponent(() => import('@/views/Login.vue'));
+const UserProfile = defineAsyncComponent(() => import('@/views/UserProfile.vue'));
 const News = defineAsyncComponent(() => import('@/views/News.vue'));
 const NewsDetail = defineAsyncComponent(() => import('@/views/NewsDetail.vue'));
 const CourseList = defineAsyncComponent(() => import('@/views/CourseList.vue'));
@@ -25,6 +27,11 @@ const routes = [
     name: 'Login',
     component: Login,
   },
+  {
+    path: '/user-profile',
+    name: 'UserProfile',
+    component: UserProfile,
+  },
   {
     path: '/news',
     name: 'News',
@@ -83,4 +90,20 @@ const router = createRouter({
   routes
 });
 
+// 檢查登入狀態
+router.beforeEach((to, from, next) => {
+  const token = localStorage.getItem('token');
+  const store = useMainStore();
+
+  if (token) {
+    store.setLoginState(true);
+    console.log('檢查登入狀態', store.loginState);
+  } else {
+    store.setLoginState(false);
+    console.log('檢查登入狀態', store.loginState);
+  }
+
+  next();
+});
+
 export default router;

+ 22 - 20
src/stores/store.js

@@ -1,26 +1,28 @@
 import { defineStore } from 'pinia'
 
 export const useMainStore = defineStore('mainStore', {
-    state: () => ({
-        count: 0
-    }),
-    getters: {
-        doubleCount() {
-            return this.count * 2;
-        },
-        tripleCount() {
-            return this.count * 3;
-        }
+  state: () => ({
+    count: 0,
+    loginState: false // 登入狀態
+  }),
+  getters: {
+    doubleCount() {
+      return this.count * 2;
     },
-    actions: {
-        // async incrementAsync() {
-        //     // 模擬非同步操作
-        //     await new Promise(resolve => setTimeout(resolve, 1000));
-        //     this.count++;
-        // }
-        getImageUrl(name) {
-            console.log('name',name);
-            return new URL(`/src/assets/img/${name}`, import.meta.url).href;
-        },
+    tripleCount() {
+      return this.count * 3;
+    }
+  },
+  actions: {
+    setLoginState(newState) {
+      this.loginState = newState;
     },
+    getImageUrl(name) {
+      console.log('name', name);
+      return new URL(`/src/assets/img/${name}`, import.meta.url).href;
+    },
+    getTotalPages(totalRecords, recordsPerPage) { // 計算頁數
+      return Math.ceil(totalRecords / recordsPerPage);
+    }
+  },
 })

+ 1 - 1
src/views/CollegeGroup/Teenager.vue

@@ -73,7 +73,7 @@ const categoryList = reactive([
     title: "獎助申請",
   },
   {
-    title: "最新消息",
+    title: "重要訊息",
   },
 ]);
 </script>

+ 132 - 35
src/views/Home.vue

@@ -1,29 +1,16 @@
 <script setup>
 import { ref, reactive, watch } from "vue";
+import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import moment from "moment";
 import Map from "@/components/Map.vue";
 import Navbar from "@/components/Navbar.vue";
 
+const store = useMainStore();
 let pageNum = ref(1); // 頁數(預設第一頁)
 let pageAmount = ref(4); // 每頁顯示筆數
 let totalPages = ref(1); // 總頁數
 
-// 取得所有課程總筆數
-(async () => {
-  try {
-    const response = await axios.get("https://cmm.ai:8088/api/get_class_name");
-    totalPages.value = getTotalPages(response.data.classes.length, 4);
-  } catch (error) {
-    console.error(error);
-  }
-})();
-
-// 計算頁數
-function getTotalPages(totalRecords, recordsPerPage) {
-  return Math.ceil(totalRecords / recordsPerPage);
-}
-
 // 切換分頁
 watch(pageNum, () => {
   getClass();
@@ -42,7 +29,7 @@ async function getClass() {
 
   try {
     const response = await axios.get(assignState.value ? assignUrl : url);
-    console.log("response", response.data);
+    totalPages.value = store.getTotalPages(response.data.total_num, 4);
     classes.data = response.data.classes;
   } catch (error) {
     console.error(error);
@@ -53,7 +40,6 @@ getClass();
 
 // 取得特定據點課程總筆數
 const getClassList = async (locationId) => {
-  console.log("locationId", locationId);
   assignState.value = true;
   assignLocationId.value = locationId;
 
@@ -63,7 +49,7 @@ const getClassList = async (locationId) => {
     );
     getClass();
     pageNum.value = 1;
-    totalPages.value = getTotalPages(response.data.classes.length, 4);
+    totalPages.value = store.getTotalPages(response.data.classes.length, 4);
   } catch (error) {
     console.log("error", error);
   }
@@ -77,50 +63,112 @@ const getClassList = async (locationId) => {
   </div>
   <Navbar />
 
-  <v-container class="pa-0 pt-16">
+  <v-container class="px-md-0">
+    <section class="text-center intro">
+      <h2 class="title">臺灣工藝學校</h2>
+      <p class="my-10">
+        以佈局具國際視野之工藝學習共享平台為目標,藉由「工藝學校」的主體概念,推動臺灣工藝學校全球學習平台,以共享、友善、全人、全民的終身工藝手作平台進行人才、課程、知識、教材之工藝資源嫁接媒合與內容設計,以在地、就近、線上、線下等多元方式提供不同型態之學習體驗內容及選擇。
+      </p>
+      <p>
+        With the goal of laying out a craft learning sharing platform with an
+        international perspective, through the main concept of "craft school",
+        promote the global learning platform of Taiwan craft schools, and use a
+        lifelong craft handicraft platform of sharing, friendliness, whole
+        people and the whole people to graft and match and design the craft
+        resources of talents, courses, knowledge and teaching materials, and
+        provide different types of learning experience content and choices in
+        local, nearby, online and offline ways.
+      </p>
+    </section>
+
     <v-row>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-12.png" alt="" />
+        <router-link to="/" class="img-info">
+          <img src="@/assets/img/home/首頁元素-12.png" alt="" />
+          <section>
+            <p>線上工藝</p>
+          </section>
+        </router-link>
       </v-col>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-11.png" alt="" />
+        <router-link to="/college-group/craft" class="img-info">
+          <img src="@/assets/img/home/首頁元素-11.png" alt="" />
+          <section>
+            <p>技藝工藝</p>
+          </section>
+        </router-link>
       </v-col>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-10.png" alt="" />
+        <router-link to="/college-group/repair" class="img-info">
+          <img src="@/assets/img/home/首頁元素-10.png" alt="" />
+          <section>
+            <p>修復工藝</p>
+          </section>
+        </router-link>
       </v-col>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-09.png" alt="" />
+        <router-link to="/college-group/cross" class="img-info">
+          <img src="@/assets/img/home/首頁元素-09.png" alt="" />
+          <section>
+            <p>跨域工藝</p>
+          </section>
+        </router-link>
       </v-col>
       <v-col cols="4" class="d-flex justify-center align-center">
         <h3 class="title">八大工藝學群</h3>
       </v-col>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-05.png" alt="" />
+        <router-link to="/college-group/generation" class="img-info">
+          <img src="@/assets/img/home/首頁元素-05.png" alt="" />
+          <section>
+            <p>世代工藝</p>
+          </section>
+        </router-link>
       </v-col>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-06.png" alt="" />
+        <router-link to="/college-group/future" class="img-info">
+          <img src="@/assets/img/home/首頁元素-06.png" alt="" />
+          <section>
+            <p>未來工藝</p>
+          </section>
+        </router-link>
       </v-col>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-07.png" alt="" />
+        <router-link to="/" class="img-info">
+          <img src="@/assets/img/home/首頁元素-07.png" alt="" />
+          <section>
+            <p>生活工藝</p>
+          </section>
+        </router-link>
       </v-col>
       <v-col cols="4">
-        <img src="@/assets/img/home/首頁元素-08.png" alt="" />
+        <router-link to="/college-group/teenager" class="img-info">
+          <img src="@/assets/img/home/首頁元素-08.png" alt="" />
+          <section>
+            <p>青年工藝</p>
+          </section>
+        </router-link>
       </v-col>
     </v-row>
 
     <div class="map-block">
       <h3 class="mb-10 title">全台工藝地圖</h3>
       <div class="v-row">
-        <v-col cols="7">
+        <v-col md="7" cols="12">
           <div class="map">
             <Map @locationId="getClassList" />
           </div>
         </v-col>
-        <v-col cols="5">
+        <v-col md="5" cols="12">
           <v-list lines="three" class="list pa-0">
             <v-list-item v-for="item in classes.data" :key="item.id">
               <div class="d-flex align-center">
-                <img :src="item.cover_img" alt="" />
+                <router-link
+                  :to="`/course-detail/${item.class_name_id}`"
+                  class="link"
+                >
+                  <img :src="item.cover_img" alt="" />
+                </router-link>
                 <section>
                   <h2>{{ item.name }}</h2>
                   <p>主辦單位:{{ item.organizer }}</p>
@@ -161,6 +209,13 @@ const getClassList = async (locationId) => {
   }
 }
 
+.intro {
+  margin: 120px 0 100px;
+  p {
+    line-height: 32px;
+  }
+}
+
 .title {
   font-size: 26px;
   font-weight: 400;
@@ -168,19 +223,61 @@ const getClassList = async (locationId) => {
   text-align: center;
 }
 
+.navbar {
+  margin-bottom: 0;
+}
+
+.img-info {
+  display: block;
+  position: relative;
+  section {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: rgba(167, 193, 204, 0.7);
+    cursor: pointer;
+    opacity: 0;
+    transition: all 0.5s;
+    &:hover {
+      opacity: 1;
+    }
+    p {
+      color: #fff;
+      font-size: 20px;
+      letter-spacing: 1px;
+    }
+  }
+}
+
 .map-block {
   margin: 100px auto;
   .map {
-    height: 100vh;
+    height: 100%;
+    min-height: 100vh;
   }
 
   .list {
-    img {
-      width: 200px;
-      height: 175px;
+    .link {
+      min-width: 180px;
       margin-right: 20px;
       border-radius: 5px;
-      box-shadow: 2px 2px 4px #aaaaaa;
+      overflow: hidden;
+      img {
+        // width: 100%;
+        height: 175px;
+        margin-right: 20px;
+        border-radius: 5px;
+        box-shadow: 2px 2px 4px #aaaaaa;
+        transition: all 0.3s;
+        object-fit: cover;
+        &:hover {
+          transform: scale(1.1);
+        }
+      }
     }
 
     h2 {

+ 59 - 16
src/views/Login.vue

@@ -1,10 +1,13 @@
 <script setup>
 import { ref, reactive, onMounted, watch } from "vue";
+import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
 
-let tab = ref("");
+const store = useMainStore();
 const emit = defineEmits(["close"]);
+const emailRegex = /^(?:\s*|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/;
+let tab = ref("");
 
 function close() {
   emit("close", false);
@@ -43,14 +46,48 @@ const handleSignIn = async (response) => {
 };
 
 let isLoading = ref(false);
+let loginAlert = ref(false);
 let userLogin = reactive({
-  account: "",
+  username: "",
   password: "",
 });
 
 // 登入
-function login() {
-  console.log("userLogin", userLogin);
+async function login() {
+  if (!emailRegex.test(userLogin.username)) {
+    alertText.value = "請檢查 E-mail 格式";
+    loginAlert.value = true;
+    alertClose(1500);
+    return;
+  }
+
+  const formData = new FormData();
+  for (const key in userLogin) {
+    formData.append(key, userLogin[key]);
+  }
+
+  try {
+    isLoading.value = true;
+    const response = await axios.post(
+      "https://cmm.ai:8088/api/login",
+      formData
+    );
+    console.log("response", response);
+
+    if (response.data.msg) {
+      alertText.value = response.data.msg;
+      loginAlert.value = true;
+      isLoading.value = false;
+      alertClose(2000);
+    }
+
+    setTimeout(() => {
+      localStorage.setItem("token", response.data.access_token);
+      store.loginState = true;
+    }, 1500);
+  } catch (error) {
+    console.error(error);
+  }
 }
 
 let userRegister = reactive({
@@ -73,8 +110,8 @@ async function register() {
     console.log("response", response);
     if (response.data.msg) {
       alertText.value = response.data.msg;
-      validateState.value = true;
-      alertClose(3000);
+      registerAlert.value = true;
+      alertClose(2000);
     }
     isLoading.value = false;
   } catch (error) {
@@ -92,9 +129,8 @@ function checkEmptyFields() {
   return true;
 }
 
-let validateState = ref(false);
+let registerAlert = ref(false);
 let alertText = ref("");
-const emailRegex = /^(?:\s*|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/;
 
 // 檢查註冊欄位
 function validateRegister() {
@@ -105,14 +141,14 @@ function validateRegister() {
 
   if (!emailRegex.test(userRegister.email)) {
     alertText.value = "請檢查 E-mail 格式";
-    validateState.value = true;
+    registerAlert.value = true;
     alertClose(1500);
     return;
   }
 
   if (userRegister.password !== userRegister.re_password) {
     alertText.value = "密碼與確認密碼不相符";
-    validateState.value = true;
+    registerAlert.value = true;
     alertClose(1500);
     return;
   }
@@ -122,7 +158,8 @@ function validateRegister() {
 
 function alertClose(second) {
   setTimeout(() => {
-    validateState.value = false;
+    loginAlert.value = false;
+    registerAlert.value = false;
   }, second);
 }
 
@@ -186,7 +223,7 @@ console.log("JWT 過期時間:", formattedExpiration);
     </v-tabs>
     <v-window v-model="tab">
       <v-window-item :value="1">
-        <div class="px-15 pt-10">
+        <div class="px-15 pt-10 d-flex flex-column align-center">
           <div
             id="g_id_onload"
             data-client_id="626437744072-q6djn202411is5vdk2v0tu8fo7n07qr0.apps.googleusercontent.com"
@@ -204,9 +241,9 @@ console.log("JWT 過期時間:", formattedExpiration);
 
           <v-form @submit.prevent>
             <v-text-field
-              v-model="userLogin.account"
-              label="帳號"
-              :rules="[(v) => !!v || '請輸入您的帳號']"
+              v-model="userLogin.username"
+              label="信箱"
+              :rules="[(v) => !!v || '請輸入您的信箱']"
               prepend-inner-icon="mdi-account"
               variant="solo"
               density="compact"
@@ -225,9 +262,15 @@ console.log("JWT 過期時間:", formattedExpiration);
               color="primary"
               block
               @click="login()"
+              :loading="isLoading"
               >登入</v-btn
             >
           </v-form>
+          <div class="alert" v-if="loginAlert">
+            <v-alert border="start" border-color="primary" elevation="2">
+              {{ alertText }}
+            </v-alert>
+          </div>
         </div>
       </v-window-item>
       <v-window-item :value="2">
@@ -277,7 +320,7 @@ console.log("JWT 過期時間:", formattedExpiration);
               >註冊</v-btn
             >
           </v-form>
-          <div class="alert" v-if="validateState">
+          <div class="alert" v-if="registerAlert">
             <v-alert border="start" border-color="primary" elevation="2">
               {{ alertText }}
             </v-alert>

+ 31 - 7
src/views/News.vue

@@ -1,17 +1,22 @@
 <script setup>
-import { ref, reactive } from "vue";
+import { ref, reactive, watch, computed } from "vue";
+import { useMainStore } from "@/stores/store";
 import axios from "axios";
 import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
 
+const store = useMainStore();
+let searchInput = ref("");
+let searchError = ref(false);
+let currentPage = ref(1); // 當前頁數(預設第一頁)
+let itemsPerPage = ref(5); // 每頁顯示筆數
+let totalPages = ref(1); // 總頁數
 const newsAll = reactive({
   list: [],
 });
 const newsData = reactive({
   list: [],
 });
-let searchInput = ref("");
-let searchError = ref(false);
 
 // 取得資料
 (async function getData() {
@@ -20,6 +25,7 @@ let searchError = ref(false);
     console.log("response", response.data.news);
     newsAll.list = response.data.news;
     newsData.list = response.data.news;
+    totalPages.value = store.getTotalPages(response.data.news.length, 5); // 計算頁數
   } catch (error) {
     console.error(error);
   }
@@ -29,7 +35,6 @@ let searchError = ref(false);
 async function search() {
   searchError.value = false;
   let keyword = searchInput.value;
-  console.log("keyword", keyword);
   if (keyword !== "") {
     try {
       const response = await axios.get(
@@ -48,6 +53,20 @@ async function search() {
   }
 }
 
+const searchLocation = ref(null);
+
+// 切換分頁時回到列表上方
+watch(currentPage, () => {
+  searchLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+// 分頁顯示
+const paginatedList = computed(() => {
+  const startIndex = (currentPage.value - 1) * itemsPerPage.value;
+  const endIndex = startIndex + itemsPerPage.value;
+  return newsData.list.slice(startIndex, endIndex);
+});
+
 const breadcrumbs = reactive([
   {
     title: "首頁",
@@ -55,7 +74,7 @@ const breadcrumbs = reactive([
     href: "/",
   },
   {
-    title: "最新消息",
+    title: "重要訊息",
     disabled: true,
     href: "/news",
   },
@@ -117,7 +136,7 @@ const categoryList = reactive([
           </v-btn>
         </div>
 
-        <div class="search mt-5 mb-10 me-16">
+        <div class="search pt-5 mb-10 me-16" ref="searchLocation">
           <span>
             <input v-model="searchInput" type="text" @keyup.enter="search()" />
             <button @click="search()">
@@ -133,7 +152,7 @@ const categoryList = reactive([
           </div>
         </div>
         <ul>
-          <li v-for="(item, index) in newsData.list" :key="index" class="mb-10">
+          <li v-for="(item, index) in paginatedList" :key="index" class="mb-10">
             <section class="d-flex">
               <p class="category mb-5">
                 <span></span>
@@ -165,6 +184,11 @@ const categoryList = reactive([
             </v-card>
           </li>
         </ul>
+        <v-pagination
+          v-model="currentPage"
+          class="my-4"
+          :length="totalPages"
+        ></v-pagination>
       </div>
     </v-container>
     <img src="@/assets/img/news/news-01.png" alt="" class="material-img" />

+ 1 - 1
src/views/NewsDetail.vue

@@ -71,7 +71,7 @@ const news = reactive({
       </div>
     </v-container>
     <router-link to="/news" class="mt-16 back-link"
-      >< 返回最新消息</router-link
+      >< 返回重要訊息</router-link
     >
     <img src="@/assets/img/news/news-01.png" alt="" class="material-img" />
   </div>

+ 102 - 0
src/views/UserProfile.vue

@@ -0,0 +1,102 @@
+<script setup>
+import { ref, reactive, watch, computed } from "vue";
+import { useMainStore } from "@/stores/store";
+import axios from "axios";
+import moment from "moment";
+import Navbar from "@/components/Navbar.vue";
+
+const store = useMainStore();
+const user = reactive({
+  data: [],
+});
+let token = localStorage.getItem("token");
+console.log("getItem token", token);
+let username = ref("");
+
+// 取得資料
+(async () => {
+  try {
+    const response = await axios.get(
+      `https://cmm.ai:8088/api/information?token=${token}`
+    );
+    console.log("response", response.data.msg);
+    username.value = response.data.msg.username;
+  } catch (error) {
+    console.error(error);
+  }
+})();
+
+const firstName = computed(() => {
+  const firstChar = username.value.charAt(0);
+  return firstChar.toUpperCase();
+});
+
+let tab = ref(null);
+</script>
+
+<template>
+  <Navbar />
+  <v-container>
+    <div class="user-info ma-5">
+      <div class="img">
+        <span> {{ firstName }}</span>
+      </div>
+      <p class="name">{{ username }}</p>
+    </div>
+
+    <v-card>
+      <v-tabs v-model="tab" color="primary" align-tabs="start">
+        <v-tab :value="1">我的收藏</v-tab>
+        <v-tab :value="2">我的學習</v-tab>
+      </v-tabs>
+      <v-window v-model="tab">
+        <v-window-item :value="1">
+          <v-container>
+            <v-row>
+              <v-col> 我的收藏 </v-col>
+            </v-row>
+          </v-container>
+        </v-window-item>
+        <v-window-item :value="2">
+          <v-container>
+            <v-row>
+              <v-col> 我的學習 </v-col>
+            </v-row>
+          </v-container>
+        </v-window-item>
+      </v-window>
+    </v-card>
+  </v-container>
+</template>
+
+<style lang="scss" scoped>
+.user-info {
+  display: flex;
+  align-items: center;
+  .img {
+    width: 70px;
+    height: 70px;
+    background-color: var(--main-color);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 100px;
+    span {
+      color: #fff;
+      font-size: 34px;
+      font-weight: 500;
+    }
+  }
+  .name {
+    font-size: 28px;
+    margin-left: 15px;
+  }
+}
+
+.v-card {
+  padding: 25px;
+  .v-btn {
+    font-size: 18px;
+  }
+}
+</style>