ソースを参照

Login function

NanceTide 4 ヶ月 前
コミット
c952e12c84

ファイルの差分が大きいため隠しています
+ 644 - 35
package-lock.json


+ 1 - 1
package.json

@@ -15,6 +15,7 @@
     "joi-browser": "^13.4.0",
     "lucide-vue-next": "^0.445.0",
     "pinia": "^2.2.2",
+    "pinia-plugin-persistedstate": "^4.1.1",
     "teacher-management-system": "file:",
     "vue": "^3.2.37",
     "vue-router": "^4.4.5",
@@ -32,4 +33,3 @@
     "vite": "^3.0.1"
   }
 }
-

+ 4 - 2
src/App.vue

@@ -1,6 +1,8 @@
 <template>
   <div id="app">
-    <Header />
+    <Suspense>
+      <Header />
+    </Suspense>
     <main>
       <router-view></router-view>
     </main>
@@ -14,7 +16,7 @@ import Header from './components/Header.vue';
 import Footer from './components/Footer.vue';
 
 const authStore = useAuthStore();
-
+authStore.authFromLocal()
 
 </script>
 

+ 17 - 0
src/api/Users.js

@@ -69,6 +69,21 @@ class UserApi {
       data: formData
     });
   }
+
+  async getRole() {
+    return request({
+      url: `${this.basePath}/role`,
+      method: 'GET',
+    }).role
+  }
+
+  async getMe() {
+    return request({
+      url: `${this.basePath}/me`,
+      method: `GET`,
+    });
+  }
+
 }
 
 const userApi = new UserApi();
@@ -80,6 +95,8 @@ export const userForgetPost = (data) => userApi.userForgetPost(data);
 export const userPasswordPut = (data) => userApi.userPasswordPut(data);
 export const userDownloadGet = (id, status) => userApi.userDownloadGet(id, status);
 export const userUploadPost = (file) => userApi.userUploadPost(file);
+export const getRole = () => userApi.getRole();
+export const getMe = () => userApi.getMe();
 
 export default userApi;
 

+ 6 - 4
src/components/AdminMenu.vue

@@ -1,15 +1,15 @@
 <template>
     <div class="relative">
       <div @click="toggleMenu" class="flex items-center cursor-pointer">
-        <img src="@/assets/admin-avatar.png" alt="Admin" class="w-8 h-8 rounded-full mr-2" />
-        <span class="text-gray-800">管理员</span>
+        <el-icon :size="28" color="white" style="margin-right: 8px;"><User/></el-icon>
+        <span class="text-white">{{ user.name }}</span>
       </div>
       <div v-if="isOpen" class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10">
         <router-link to="/change-password" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">修改密码</router-link>
         <a @click="logout" class="block px-4 py-2 text-gray-800 hover:bg-gray-100 cursor-pointer">登出</a>
       </div>
     </div>
-  </template>
+  </template> 
   
   <script setup>
   import { ref } from 'vue';
@@ -20,6 +20,8 @@
   const router = useRouter();
   const isOpen = ref(false);
 
+  const user = authStore.user;
+
   const toggleMenu = () => {
     isOpen.value = !isOpen.value;
   };
@@ -30,7 +32,7 @@
 
   const logout = async () => {
   try {
-    await authStore.logout(); // 使用 Pinia 的 action
+    await authStore.removeAuth();
     router.push('/');
     closeMenu();
   } catch (error) {

+ 15 - 12
src/components/Header.vue

@@ -13,28 +13,31 @@
         </template>
       </div>
       <div class="auth">
-        <template v-if="!isLoggedIn">
-          <router-link to="/login" class="auth-link">登录</router-link>
-          <router-link to="/register" class="auth-link">注册</router-link>
-        </template>
-        <template v-else>
-          <UserMenu v-if="!isAdmin" />
-          <AdminMenu v-else />
-        </template>
+        <Suspense>
+
+          <template v-if="!isLoggedIn">
+            <router-link to="/login" class="auth-link">登录</router-link>
+            <router-link to="/register" class="auth-link">注册</router-link>
+          </template>
+          <template v-else>
+            <UserMenu v-if="isAdmin" />
+            <AdminMenu v-else />
+          </template>
+        </Suspense>
       </div>
     </nav>
   </header>
 </template>
 
 <script setup>
-import { computed } from 'vue';
 import UserMenu from './UserMenu.vue';
 import AdminMenu from './AdminMenu.vue';
+import { computed, onMounted, onUpdated } from 'vue';
 import { useAuthStore } from '@/store/modules/auth';
 
-const store = useAuthStore();
-const isLoggedIn = computed(() => store.isAuthenticated);
-const isAdmin = computed(() => store.isAdmin);
+const authStore = useAuthStore();
+const isLoggedIn = computed(() => authStore.user != null);
+const isAdmin = computed(() => authStore.user != null && authStore.user.role !== 'admin');
 </script>
 
 <style scoped>

+ 6 - 21
src/components/TeacherCard.vue

@@ -1,28 +1,13 @@
 <template>
   <div class="bg-white shadow-lg rounded-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-2">
-    <div class="relative pb-2/3">
-      <img :src="teacher.image" :alt="teacher.name" class="h-full w-full object-cover" />
-      <div class="inset-0 bg-gradient-to-t from-black to-transparent opacity-60"></div>
-      <div class="bottom-0 left-0 p-4 text-white">
-        <h3 class="text-xl font-semibold mb-1">{{ teacher.name }}</h3>
-        <p class="text-sm mb-1 text-gray-500">高性能计算</p>
-      </div>
+    <div class="relative h-48">
+      <img :src="teacher.image" :alt="teacher.name" class="h-full w-full object-contain" />
+    </div>
+    <div class="inset-0 bg-gradient-to-t from-black to-transparent opacity-60"></div>
+    <div class="bottom-0 left-0 p-4 text-white">
+      <h3 class="text-xl font-semibold mb-1 text-center">{{ teacher.name }}</h3>
     </div>
-    <!-- <div class="p-4">
-      <router-link 
-        :to="{ name: 'TeacherDetail', params: { id: teacher.id } }" 
-        class="block w-full text-center bg-primary text-white px-4 py-2 rounded-md hover:bg-primary-dark transition-colors duration-300"
-      >
-        查看详情
-      </router-link>
-    </div> -->
   </div>
-    <!-- <el-card :header="teacher.name" shadow="hover" style="max-width: 240px; margin-bottom: 12px; margin-top: 12px;">
-      <img
-        :src="teacher.image"
-        style="width: 100%"
-      />
-    </el-card> -->
 </template>
 
 <script setup>

+ 17 - 14
src/components/UserMenu.vue

@@ -1,14 +1,15 @@
 <template>
   <div class="relative">
     <div @click="toggleMenu" class="flex items-center cursor-pointer">
-      <img :src="user.profileImage" :alt="user.name" class="w-8 h-8 rounded-full mr-2" />
-      <span class="text-gray-800">{{ user.name }}</span>
+      <!-- <img :src="user.profileImage" :alt="user.name" class="w-8 h-8 rounded-full mr-2" /> -->
+      <el-icon :size="28" color="white" style="margin-right: 8px;"><User/></el-icon>
+      <span class="text-white">{{ authStore.user.name }}</span>
     </div>
     <transition name="fade">
       <div v-if="isOpen" class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10">
-        <router-link @click="closeMenu" to="/profile" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">个人资料</router-link>
+        <router-link @click="closeMenu" :to="'/teacher/' + authStore.user.id" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">个人资料</router-link>
         <router-link @click="closeMenu" to="/change-password" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">修改密码</router-link>
-        <a @click="logoutAndCloseMenu" class="block px-4 py-2 text-gray-800 hover:bg-gray-100 cursor-pointer">登出</a>
+        <a @click="logout" class="block px-4 py-2 text-gray-800 hover:bg-gray-100 cursor-pointer">登出</a>
       </div>
     </transition>
   </div>
@@ -16,15 +17,13 @@
 
 <script setup>
 import { ref } from 'vue';
-import { useStore } from 'vuex';
-import { useRouter } from 'vue-router';
+import { useRouter } from 'vue-router'; 
+import { useAuthStore } from '../store/modules/auth';
 
-const store = useStore();
+const authStore = useAuthStore();
 const router = useRouter();
 const isOpen = ref(false);
 
-const user = store.getters['auth/user'];
-
 const toggleMenu = () => {
   isOpen.value = !isOpen.value;
 };
@@ -33,11 +32,15 @@ const closeMenu = () => {
   isOpen.value = false;
 };
 
-const logoutAndCloseMenu = () => {
-  store.dispatch('auth/logout');
-  router.push('/');
-  closeMenu();
-};
+const logout = async () => {
+  try {
+    await authStore.removeAuth();
+    router.push('/');
+    closeMenu();
+  } catch (error) {
+    console.error('Logout failed:', error);
+  }
+}
 </script>
 
 <style scoped>

+ 4 - 0
src/main.js

@@ -3,12 +3,16 @@ import { createPinia } from 'pinia';
 import App from './App.vue';
 import router from './router'; // 直接从 ./router 导入已经配置好的 router
 import ElementPlus from 'element-plus'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import './styles/main.css';
 import 'element-plus/theme-chalk/index.css';
 import './assets/tailwind.css';
 
 const app = createApp(App);
 const pinia = createPinia();
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+}
 
 app.use(pinia);
 app.use(router); // 直接使用从 ./router 导入的 router

+ 7 - 3
src/router/index.js

@@ -96,11 +96,15 @@ const router = createRouter({
 })
 
 // Navigation guard
-router.beforeEach((to, from, next) => {
+router.beforeEach(async (to, from, next) => {
   const authStore = useAuthStore();
-  const isAuthenticated = authStore.isAuthenticated; // This should be replaced with actual auth check
-  const isAdmin = authStore.isAdmin; // This should be replaced with actual admin check
 
+  if(!authStore.user)
+    await authStore.authFromLocal()
+  const isAuthenticated = authStore.user != null;
+  const isAdmin = authStore.user != null && authStore.user.role == 'admin'; // TODO
+
+  
   if (to.matched.some(record => record.meta.requiresAuth)) {
     if (!isAuthenticated) {
       next({ name: 'Login', query: { redirect: to.fullPath } })

+ 126 - 70
src/store/modules/auth.js

@@ -1,74 +1,130 @@
+import { ref } from 'vue';
 import { defineStore } from 'pinia';
-import { userLoginPost, userLogoutPost } from '../../api/Users';
+import { getMe, userLoginPost, userLogoutPost } from '../../api/Users';
 import { userRegisterPost, userForgetPost, userPasswordPut } from '../../api/Users';
 
-export const useAuthStore = defineStore('auth', {
-  state: () => ({
-    user: null,
-    isAuthenticated: false,
-    isAdmin: false,
-  }),
-  actions: {
-    async login(username, password) {
-      try {
-        const response = await userLoginPost({ username, password });
-        this.user = response.data;
-        this.isAuthenticated = true;
-        this.isAdmin = this.user.role === 'admin';
-        return true;
-      } catch (error) {
-        this.handleError('登录失败', error);
-        return false;
-      }
-    },
-    async logout() {
-      try {
-        await userLogoutPost();
-        this.resetAuthState();
-      } catch (error) {
-        this.handleError('登出失败', error);
-      }
-    },
-    async register(userData) {
-      try {
-        await userRegisterPost(userData);
-        return true;
-      } catch (error) {
-        this.handleError('注册失败', error);
-        return false;
-      }
-    },
-    async forgotPassword(userData) {
-      try {
-        await userForgetPost(userData);
-        return true;
-      } catch (error) {
-        this.handleError('密码重置请求失败', error);
-        return false;
-      }
-    },
-    async changePassword(oldPassword, newPassword) {
-      try {
-        await userPasswordPut({ oldPassword, newPassword });
-        return true;
-      } catch (error) {
-        this.handleError('密码更改失败', error);
-        return false;
-      }
-    },
-    resetAuthState() {
-      this.user = null;
-      this.isAuthenticated = false;
-      this.isAdmin = false;
-    },
-    handleError(message, error) {
-      console.error(message, error);
-    },
+// export const useAuthStore = defineStore('auth', {
+//   state: () => ({
+//     user: null,
+//     isAuthenticated: false,
+//     isAdmin: false,
+//     first: false
+//   }),
+//   actions: {
+//     async login(username, password) {
+//       try {
+//         const response = await userLoginPost({ username, password });
+//         this.user = response.data;
+//         this.isAuthenticated = true;
+//         this.isAdmin = this.user.role === 'admin';
+//         return true;
+//       } catch (error) {
+//         this.handleError('登录失败', error);
+//         return false;
+//       }
+//     },
+//     async logout() {
+//       try {
+//         await userLogoutPost();
+//         this.resetAuthState();
+//       } catch (error) {
+//         this.handleError('登出失败', error);
+//       }
+//     },
+//     async register(userData) {
+//       try {
+//         await userRegisterPost(userData);
+//         return true;
+//       } catch (error) {
+//         this.handleError('注册失败', error);
+//         return false;
+//       }
+//     },
+//     async forgotPassword(userData) {
+//       try {
+//         await userForgetPost(userData);
+//         return true;
+//       } catch (error) {
+//         this.handleError('密码重置请求失败', error);
+//         return false;
+//       }
+//     },
+//     async changePassword(oldPassword, newPassword) {
+//       try {
+//         await userPasswordPut({ oldPassword, newPassword });
+//         return true;
+//       } catch (error) {
+//         this.handleError('密码更改失败', error);
+//         return false;
+//       }
+//     },
+//     resetAuthState() {
+//       this.user = null;
+//       this.isAuthenticated = false;
+//       this.isAdmin = false;
+//     },
+//     handleError(message, error) {
+//       console.error(message, error);
+//     },
 
-    setUser(user) {
-      this.user = user;
-      this.isAuthenticated = true;
-      this.isAdmin = user.role === 'admin';
-    },
-  },
-});
+//     setUser(user) {
+//       this.user = user;
+//       this.isAuthenticated = true;
+//       this.isAdmin = user.role === 'admin';
+//     },
+//   },
+// });
+
+
+export const useAuthStore = defineStore('auth', () => {
+  const user = ref(null);
+
+  const setAuth = (token, userInfo) => {
+    localStorage.setItem('token', token);
+    delete userInfo.token;
+    user.value = userInfo;
+  };
+
+  const authFromLocal = async () => {
+    const token = localStorage.getItem('token');
+    if (!token) return false;
+  
+    try {
+      const response = await getMe();
+      setAuth(token, response.data); // 在获取成功后设置 auth
+      return true;
+    } catch (error) {
+      console.error("Error fetching user data:", error);
+      return false;
+    }
+  };
+
+  const getAuth = async () => {
+    if(user)
+      return user;
+    const token = localStorage.getItem('token');
+    if (!token) await authFromLocal();
+    if (!token) return null;
+    try {
+      const response = await getMe();
+      setAuth(token, response.data);
+      return response.data;
+    } catch (error) {
+      removeAuth(); // 如果出错,清除无效 token
+      return null;
+    }
+  };
+
+  const removeAuth = () => {
+    localStorage.removeItem('token');
+    user.value = null;
+  };
+
+  return {
+    user,
+    setAuth,
+    getAuth,
+    authFromLocal,
+    removeAuth,
+  };
+});

+ 3 - 0
src/utils/util.js

@@ -0,0 +1,3 @@
+export const isNotEmpty = (seq) => {
+    return seq != undefined && seq != null && seq.length > 0
+}

+ 3 - 0
src/views/ChangePassword.vue

@@ -46,6 +46,7 @@
 import { ref } from 'vue';
 import { useRouter } from 'vue-router';
 import { userPasswordPut } from '@/api/Users';
+import { useAuthStore } from '../store/modules/auth';
 
 export default {
   name: 'ChangePassword',
@@ -54,6 +55,7 @@ export default {
     const oldPassword = ref('');
     const newPassword = ref('');
     const confirmNewPassword = ref('');
+    const authStore = useAuthStore();
 
     const handleChangePassword = async () => {
       if (newPassword.value !== confirmNewPassword.value) {
@@ -66,6 +68,7 @@ export default {
           newPassword: newPassword.value
         });
         alert('密码修改成功');
+        authStore.removeAuth();
         router.push('/login');
       } catch (error) {
         alert('修改密码失败:' + error.message);

+ 0 - 1
src/views/EditProfile.vue

@@ -4,7 +4,6 @@
       
       <!-- 基本信息部分 -->
       <div class="basic-info">
-        <img :src="teacher.image" alt="Profile Image" class="profile-image">
         <div>
           <label for="name">姓名:</label>
           <input type="text" v-model="teacher.name" id="name">

+ 1 - 2
src/views/Home.vue

@@ -8,7 +8,7 @@
         <SearchBar @search="handleSearch"/>
       </div>
 
-      <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 mb-16">
+      <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-x-8 gap-y-8 mb-16 px-12">
         <TeacherCard
           v-for="teacher in teachersStore.teachers"
           :key="teacher.id"
@@ -70,7 +70,6 @@ watchEffect(() => {
   fetchTeachers();
 });
 
-console.log(teachersStore);
 
 </script>
 

+ 9 - 5
src/views/Login.vue

@@ -38,6 +38,7 @@ import { ref } from 'vue';
 import { useRouter } from 'vue-router';
 import { userLoginPost } from '@/api/Users';
 import { useAuthStore } from '@/store/modules/auth';
+import { isNotEmpty } from '../utils/util';
 
 export default {
   name: 'Login',
@@ -51,13 +52,16 @@ export default {
       try {
         const response = await userLoginPost({ username: username.value, password: password.value });
         if(response != null && response.data != null){
-          localStorage.setItem("token", response.data.token);
-          authStore.setUser(response.data);
+          console.log(response)
+          authStore.setAuth(response.data.token, response.data);
         }
-        if (authStore.role === 'admin') {
-          router.push('/admin');
+        console.log('user:', authStore.user)
+        if (authStore.user) {
+          router.replace('/');
+          console.log('up')
         } else {
-          router.push('/');
+          router.replace('/');
+          console.log('down')
         }
       } catch (error) {
         console.error('Login failed:', error);

+ 61 - 31
src/views/TeacherDetail.vue

@@ -2,7 +2,10 @@
   <div class="teacher-detail">
     <header class="teacher-header">
       <img :src="teacher.image" :alt="teacher.name" class="profile-image">
-      <h1>{{ teacher.name }}</h1>
+      <p>
+        <h1>{{ teacher.name }}</h1>
+        <button @click="editProfile" class="edit-button" v-if="authStore.user && authStore.user.id == teacher.id">编辑资料</button>
+      </p>
     </header>
     
     <section class="basic-info">
@@ -11,17 +14,18 @@
       <p style="white-space: pre-line"><strong>本科课程:</strong> <p></p> {{ teacher.undergraduateCourse }}</p>
       <p style="white-space: pre-line"><strong>研究生课程:</strong> <p></p> {{ teacher.graduateCourse }}</p>
       <p style="white-space: pre-line"><strong>研究领域:</strong> <p></p> {{ teacher.researchField }}</p>
+      <p style="white-space: pre-line"><strong>奖项:</strong> <p></p> {{ teacher.awards }}</p>
       <p><strong>邮箱:</strong> {{ teacher.email }}</p>
       <p><strong>电话:</strong> {{ teacher.phoneNumber }}</p>
     </section>
 
-    <section class="additional-info">
-      <CollapsibleSection title="专利" :expanded="expandedSections.patents">
+    <section class="additional-info" v-if="isNotEmpty(teacher.patent) || isNotEmpty(awards) || isNotEmpty(theses) || isNotEmpty(works)">
+      <CollapsibleSection title="专利" :expanded="expandedSections.patents" v-if="isNotEmpty(teacher.patent)">
         <div v-for="patent in teacher.patent" :key="patent.patentNum" class="info-item">
-          <p><strong>专利号:</strong> {{ patent.patentNum }}</p>
-          <p><strong>授权号:</strong> {{ patent.authorizationNum }}</p>
-          <p><strong>证书号:</strong> {{ patent.certificateNum }}</p>
-          <p><strong>日期:</strong> {{ patent.date }}</p>
+          <p v-if="isNotEmpty(patent.patentNum)"><strong>专利号:</strong> {{ patent.patentNum }}</p>
+          <p v-if="isNotEmpty(patent.authorizationNum)"><strong>授权号:</strong> {{ patent.authorizationNum }}</p>
+          <p v-if="isNotEmpty(patent.certificateNum)"><strong>证书号:</strong> {{ patent.certificateNum }}</p>
+          <p v-if="isNotEmpty(patent.date)"><strong>日期:</strong> {{ patent.date }}</p>
         </div>
         <Pagination
           :current-page="patentPage"
@@ -31,11 +35,11 @@
         />
       </CollapsibleSection>
 
-      <CollapsibleSection title="获奖" :expanded="expandedSections.awards">
+      <CollapsibleSection title="获奖" :expanded="expandedSections.awards" v-if="isNotEmpty(awards)">
         <div v-for="award in awards" :key="award.id" class="info-item">
           <p><strong>名称:</strong> {{ award.name }}</p>
-          <p><strong>时间:</strong> {{ award.time }}</p>
-          <img :src="award.image" :alt="award.name" class="info-image">
+          <p v-if="isNotEmpty(award.time)"><strong>时间:</strong> {{ award.time }}</p>
+          <img v-if="isNotEmpty(award.image)" :src="award.image" :alt="award.name" class="info-image">
         </div>
         <Pagination
           :current-page="awardPage"
@@ -45,13 +49,14 @@
         />
       </CollapsibleSection>
 
-      <CollapsibleSection title="论文" :expanded="expandedSections.theses">
+      <CollapsibleSection title="论文" :expanded="expandedSections.theses" v-if="isNotEmpty(theses)">
         <div v-for="thesis in theses" :key="thesis.id" class="info-item">
           <p><strong>名称:</strong> {{ thesis.name }}</p>
-          <p><strong>发表时间:</strong> {{ thesis.time }}</p>
-          <a :href="thesis.website" target="_blank" class="info-link">查看详情</a>
-          <img :src="thesis.image" :alt="thesis.name" class="info-image">
-          <a :href="thesis.file" target="_blank" class="info-link">下载文件</a>
+          <p v-if="isNotEmpty(thesis.time)"><strong>发表时间:</strong> {{ thesis.time }}</p>
+          <p v-if="isNotEmpty(thesis.website)" :href="thesis.website" target="_blank" class="info-link">查看详情</p>
+          <img v-if="isNotEmpty(thesis.image)" :src="thesis.image" :alt="thesis.name" class="info-image">
+          <div/>
+          <p :href="thesis.file" target="_blank" class="info-link">下载文件</p>
         </div>
         <Pagination
           :current-page="thesisPage"
@@ -61,12 +66,12 @@
         />
       </CollapsibleSection>
 
-      <CollapsibleSection title="著作" :expanded="expandedSections.works">
+      <CollapsibleSection title="著作" :expanded="expandedSections.works" v-if="isNotEmpty(works)">
         <div v-for="work in works" :key="work.id" class="info-item">
           <p><strong>名称:</strong> {{ work.name }}</p>
-          <p><strong>出版社:</strong> {{ work.press }}</p>
-          <p><strong>出版时间:</strong> {{ work.time }}</p>
-          <img :src="work.image" :alt="work.name" class="info-image">
+          <p v-if="isNotEmpty(work.press)"><strong>出版社:</strong> {{ work.press }}</p>
+          <p v-if="isNotEmpty(work.time)"><strong>出版时间:</strong> {{ work.time }}</p>
+          <img v-if="isNotEmpty(work.image)" :src="work.image" :alt="work.name" class="info-image">
           <a :href="work.file" target="_blank" class="info-link">下载文件</a>
         </div>
         <Pagination
@@ -77,16 +82,18 @@
         />
       </CollapsibleSection>
     </section>
-  </div>
+    </div>
 </template>
 
 <script>
 import { ref, reactive, watch } from 'vue';
-import { useRoute } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 import { useTeachersStore } from '@/store/modules/teachers';
 import CollapsibleSection from '@/components/CollapsibleSection.vue';
 import Pagination from '@/components/Pagination.vue';
 import { getThesisPage, getWorksPage, getAwards } from '@/api/Open'
+import { isNotEmpty } from '@/utils/util';
+import { useAuthStore } from '../store/modules/auth';
 
 export default {
   name: 'TeacherDetail',
@@ -96,7 +103,9 @@ export default {
   },
   setup() {
     const teachersStore = useTeachersStore();
+    const authStore = useAuthStore()
     const route = useRoute();
+    const router = useRouter();
     const teacherId = ref(route.params.id);
     
     const teacher = ref({});
@@ -115,10 +124,10 @@ export default {
     const patentTotal = ref(1);
 
     const expandedSections = reactive({
-      patents: false,
-      awards: false,
-      theses: false,
-      works: false
+      patents: true,
+      awards: true,
+      theses: true,
+      works: true
     });
 
     const fetchTeacherData = async () => {
@@ -181,6 +190,10 @@ export default {
       teacher.value.patent = teacher.value.patent.slice((page - 1) * pageSize.value, page * pageSize.value);
     };
 
+    const editProfile = () => {
+      router.push('/editprofile');
+    }
+
     watch(teacherId, () => {
       fetchTeacherData();
       fetchAwards();
@@ -203,10 +216,13 @@ export default {
       patentPage,
       patentTotal,
       expandedSections,
+      authStore,
+      editProfile,
       handleAwardPageChange,
       handleThesisPageChange,
       handleWorkPageChange,
-      handlePatentPageChange
+      handlePatentPageChange,
+      isNotEmpty
     };
   }
 };
@@ -229,12 +245,12 @@ export default {
 }
 
 .profile-image {
-  width: 150px;
-  height: 150px;
-  object-fit: cover;
-  border-radius: 50%;
+  width: 180px;
+  height: 180px;
+  object-fit: contain;
+  /* border-radius: 50%; */
   margin-right: 2rem;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  /* box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); */
 }
 
 h1 {
@@ -304,5 +320,19 @@ strong {
   margin-top: 1.5rem;
 }
 
+.edit-button, .upload-button {
+  margin-top: 20px;
+  padding: 10px 20px;
+  background-color: #4CAF50;
+  color: white;
+  border: none;
+  cursor: pointer;
+  text-decoration: none;
+  display: inline-block;
+}
+
+.edit-button:hover, .upload-button:hover {
+  background-color: #45a049;
+}
 /* Add any additional styles for the CollapsibleSection and Pagination components here */
 </style>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません