Ver Fonte

Merge branch 'master' of ssh://101.43.129.26:10022/nancetide/teacherTeam-Frontend

NanceTide há 4 meses atrás
pai
commit
4076247bb9

+ 1 - 2
package-lock.json

@@ -3750,10 +3750,9 @@
     },
     "node_modules/unplugin-auto-import": {
       "version": "0.18.3",
-      "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.18.3.tgz",
+      "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.18.3.tgz",
       "integrity": "sha512-q3FUtGQjYA2e+kb1WumyiQMjHM27MrTQ05QfVwtLRVhyYe+KF6TblBYaEX9L6Z0EibsqaXAiW+RFfkcQpfaXzg==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "@antfu/utils": "^0.7.10",
         "@rollup/pluginutils": "^5.1.0",

+ 22 - 9
src/components/AdminMenu.vue

@@ -1,11 +1,11 @@
 <template>
-    <div class="relative" @mouseleave="isOpen = false">
-      <div @mouseenter="isOpen = true" class="flex items-center cursor-pointer">
+    <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>
       </div>
       <div v-if="isOpen" class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg z-10">
-        <router-link to="/admin/change-password" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">修改密码</router-link>
+        <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>
@@ -13,15 +13,28 @@
   
   <script setup>
   import { ref } from 'vue';
-  import { useStore } from 'vuex';
+  import { useAuthStore } from '@/store/modules/auth';
   import { useRouter } from 'vue-router';
   
-  const store = useStore();
+  const authStore = useAuthStore();
   const router = useRouter();
   const isOpen = ref(false);
-  
-  const logout = () => {
-    store.dispatch('auth/logout');
-    router.push('/');
+
+  const toggleMenu = () => {
+    isOpen.value = !isOpen.value;
   };
+
+  const closeMenu = () => {
+    isOpen.value = false;
+  };
+
+  const logout = async () => {
+  try {
+    await authStore.logout(); // 使用 Pinia 的 action
+    router.push('/');
+    closeMenu();
+  } catch (error) {
+    console.error('Logout failed:', error);
+  }
+};
   </script>

+ 2 - 2
src/components/CollapsibleSection.vue

@@ -70,12 +70,12 @@
   <style scoped>
   .collapse-enter-active,
   .collapse-leave-active {
-    transition: height 0.3s ease-out;
+    /* transition: height 0.3s ease-out; */
     overflow: hidden;
   }
   
   .collapse-enter-from,
   .collapse-leave-to {
-    height: 0;
+    /* height: 0; */
   }
   </style>

+ 12 - 12
src/components/Header.vue

@@ -43,20 +43,9 @@ const isAdmin = computed(() => store.isAdmin);
   box-shadow: var(--shadow-md);
   padding: 1rem 0;
   position: relative;
-  overflow: hidden;
+  overflow: visible;
 }
 
-.header::before {
-  content: '';
-  position: absolute;
-  top: -50%;
-  left: -50%;
-  width: 200%;
-  height: 200%;
-  background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%);
-  animation: shimmer 15s infinite linear;
-  pointer-events: none;
-}
 
 @keyframes shimmer {
   0% { transform: rotate(0deg); }
@@ -122,6 +111,17 @@ const isAdmin = computed(() => store.isAdmin);
   gap: 1rem;
 }
 
+.relative {
+  position: relative; /* 确保父元素是相对定位 */
+}
+
+.absolute {
+  position: absolute; /* 下拉菜单绝对定位 */
+  top: 100%; /* 确保下拉菜单在 header 下面 */
+  right: 0; /* 右对齐 */
+  z-index: 10; /* 确保下拉菜单在其他元素之上 */
+}
+
 @media (max-width: 768px) {
   .header-nav {
     flex-direction: column;

+ 6 - 3
src/components/Pagination.vue

@@ -41,8 +41,11 @@ const props = defineProps({
 
 const emit = defineEmits(['page-change']);
 
-const totalPages = computed(() => Math.ceil(props.totalItems / props.itemsPerPage));
-// console.log("This!", totalPages);
+const totalPages = computed(() => {
+  const totalItems = props.totalItems || 1; // 如果 totalItems 为空或为 0,默认为 1
+  const itemsPerPage = props.itemsPerPage || 1; // 如果 itemsPerPage 为空或为 0,默认为 1
+  return Math.ceil(totalItems / itemsPerPage);
+});
 
 const totalPagesDisplay = computed(() => totalPages.value == NaN ? '1' : totalPages.value);
 
@@ -51,4 +54,4 @@ const onPageChange = (page) => {
     emit('page-change', page);
   }
 };
-</script>
+</script>

+ 60 - 27
src/components/UserMenu.vue

@@ -1,30 +1,63 @@
 <template>
-    <div class="relative" @mouseleave="isOpen = false">
-      <div @mouseenter="isOpen = true" 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>
-      </div>
+  <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>
+    </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 to="/profile" class="block px-4 py-2 text-gray-800 hover:bg-gray-100">个人资料</router-link>
-        <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>
+        <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="/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>
       </div>
-    </div>
-  </template>
-  
-  <script setup>
-  import { ref } from 'vue';
-  import { useStore } from 'vuex';
-  import { useRouter } from 'vue-router';
-  
-  const store = useStore();
-  const router = useRouter();
-  const isOpen = ref(false);
-  
-  const user = store.getters['auth/user'];
-  
-  const logout = () => {
-    store.dispatch('auth/logout');
-    router.push('/');
-  };
-  </script>
+    </transition>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useStore } from 'vuex';
+import { useRouter } from 'vue-router';
+
+const store = useStore();
+const router = useRouter();
+const isOpen = ref(false);
+
+const user = store.getters['auth/user'];
+
+const toggleMenu = () => {
+  isOpen.value = !isOpen.value;
+};
+
+const closeMenu = () => {
+  isOpen.value = false;
+};
+
+const logoutAndCloseMenu = () => {
+  store.dispatch('auth/logout');
+  router.push('/');
+  closeMenu();
+};
+</script>
+
+<style scoped>
+.relative {
+  position: relative;
+}
+.absolute {
+  position: absolute;
+  top: 100%;
+  right: 0;
+  z-index: 10;
+}
+
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 4 - 2
src/router/index.js

@@ -12,6 +12,7 @@ import PaperUpload from '../views/PaperUpload.vue'
 import SharedLibrary from '../views/SharedLibrary.vue'
 import AdminAccount from '../views/AdminAccount.vue'
 import AdminFileManagement from '../views/AdminFileManagement.vue'
+import { useAuthStore } from '@/store/modules/auth';
 
 const routes = [
   {
@@ -96,8 +97,9 @@ const router = createRouter({
 
 // Navigation guard
 router.beforeEach((to, from, next) => {
-  const isAuthenticated = false // This should be replaced with actual auth check
-  const isAdmin = false // This should be replaced with actual admin check
+  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 (to.matched.some(record => record.meta.requiresAuth)) {
     if (!isAuthenticated) {

+ 6 - 0
src/store/modules/auth.js

@@ -64,5 +64,11 @@ export const useAuthStore = defineStore('auth', {
     handleError(message, error) {
       console.error(message, error);
     },
+
+    setUser(user) {
+      this.user = user;
+      this.isAuthenticated = true;
+      this.isAdmin = user.role === 'admin';
+    },
   },
 });

+ 2 - 1
src/utils/request.js

@@ -1,4 +1,5 @@
 import axios from 'axios'
+import { useAuthStore } from '@/store/modules/auth';
 
 const request = axios.create({
   // baseURL: import.meta.env.VITE_APP_API_BASE_URL, // 使用环境变量设置基础URL
@@ -8,7 +9,7 @@ const request = axios.create({
 
 request.interceptors.request.use(
   (config) => {
-    const token = localStorage.getItem('token')
+    const token = localStorage.getItem("token");
     if (token) {
       config.headers['token'] = token // 修改为 'token',而不是 'Authorization'
     }

+ 1 - 1
src/views/ChangePassword.vue

@@ -66,7 +66,7 @@ export default {
           newPassword: newPassword.value
         });
         alert('密码修改成功');
-        router.push('/profile');
+        router.push('/login');
       } catch (error) {
         alert('修改密码失败:' + error.message);
       }

+ 5 - 5
src/views/Login.vue

@@ -50,11 +50,11 @@ export default {
     const handleLogin = async () => {
       try {
         const response = await userLoginPost({ username: username.value, password: password.value });
-        localStorage.setItem('token', response.token);
-        authStore.setUser(response.user);
-        authStore.login();
-        
-        if (response.user.role === 'admin') {
+        if(response != null && response.data != null){
+          localStorage.setItem("token", response.data.token);
+          authStore.setUser(response.data);
+        }
+        if (authStore.role === 'admin') {
           router.push('/admin');
         } else {
           router.push('/');

+ 10 - 5
src/views/SharedLibrary.vue

@@ -44,7 +44,7 @@
 </template>
 
 <script>
-import { ref, computed} from 'vue';
+import { ref, computed, watchEffect} from 'vue';
 import { useAuthStore } from '@/store/modules/auth';
 import FileItem from '@/components/FileItem.vue';
 import Pagination from '@/components/Pagination.vue';
@@ -75,10 +75,11 @@ export default {
     );
     
     const privateFiles = computed(() => 
-      isLoggedIn.value ? resources.value.filter(file => file.visibility === 'private') : []
+      resources.value
+      //isLoggedIn.value ? resources.value.filter(file => file.visibility === 'private') : []
     );
 
-    const totalFiles = computed(() => resources.value.length);
+    const totalFiles = computed(() => resources.value.total);
 
     const handleSearch = () => {
       fetchResources();
@@ -102,7 +103,8 @@ export default {
     const fetchResources = async () => {
       try {
         const response = await getResourcesPage(currentPage.value, pageSize.value, searchQuery.value);
-        resources.value = response.data;
+        resources.value = response.data.records;
+        totalFiles.value = response.data.total;
       } catch (error) {
         console.error('Failed to fetch resources:', error);
       }
@@ -117,7 +119,9 @@ export default {
       }
     };
 
-  
+    watchEffect(() => {
+      fetchResources();
+    });
 
     return {
       searchQuery,
@@ -136,6 +140,7 @@ export default {
     };
   }
 };
+
 </script>