Quellcode durchsuchen

新建编辑荣誉类型、荣誉教师列表接口对接

liurongli vor 2 Wochen
Ursprung
Commit
0c19b42570
86 geänderte Dateien mit 1487 neuen und 387 gelöschten Zeilen
  1. 37 6
      common/request.js
  2. 44 0
      common/safeArea.js
  3. 10 3
      components/common-header.vue
  4. 96 16
      components/commonTabbar.vue
  5. 2 2
      components/nav-bar.vue
  6. 11 2
      components/no-data.vue
  7. 157 18
      components/notice-header.vue
  8. 4 2
      components/page-header.vue
  9. 10 1
      components/tree-node.vue
  10. 22 31
      pages.json
  11. 5 5
      pages/login/index.vue
  12. 189 0
      pages/materialCollection/reviewMaterial/index.vue
  13. 260 0
      pages/materialCollection/submitMaterial/index.vue
  14. 348 0
      pages/materialCollection/taskOverview/index.vue
  15. 16 9
      pages/message/index.vue
  16. 1 3
      pages/my/updatePassword.vue
  17. 0 2
      pages/notice/mine/detail.vue
  18. 8 5
      pages/notice/mine/index.vue
  19. 20 11
      pages/notice/overview/index.vue
  20. 50 24
      pages/notice/overview/noticeDetail.vue
  21. 25 18
      pages/notice/publish/index.vue
  22. 17 3
      pages/notice/publish/personList.vue
  23. 18 7
      pages/notice/publish/selectObject.vue
  24. 12 31
      pages/todo/index.vue
  25. 67 80
      pages/workspace/index.vue
  26. 33 55
      pages/workspace/schedule.vue
  27. 2 2
      polyfill.js
  28. BIN
      static/image/materialCollection/shenhe.png
  29. BIN
      static/image/materialCollection/shenheIcon.png
  30. BIN
      static/image/materialCollection/tijiao.png
  31. BIN
      static/image/materialCollection/tijiaoIcon.png
  32. BIN
      static/image/workspace/add.png
  33. 13 7
      store/modules/user.js
  34. 4 1
      tsconfig.json
  35. 1 17
      uni_modules/uni-icons/components/uni-icons/uni-icons.vue
  36. 0 4
      uni_modules/uni-popup/components/uni-popup/uni-popup.vue
  37. 1 1
      unpackage/cache/certdata
  38. 0 1
      unpackage/cache/wgt/__UNI__07352BD/app-config-service.js
  39. 0 0
      unpackage/cache/wgt/__UNI__07352BD/app-service.js
  40. 0 0
      unpackage/cache/wgt/__UNI__07352BD/app.css
  41. BIN
      unpackage/cache/wgt/__UNI__07352BD/assets/uvicons.04d281cc.ttf
  42. 0 0
      unpackage/cache/wgt/__UNI__07352BD/manifest.json
  43. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/login/agreement.css
  44. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/login/index.css
  45. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/materialCollection/reviewMaterial/index.css
  46. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/materialCollection/submitMaterial/index.css
  47. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/materialCollection/taskOverview/index.css
  48. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/message/index.css
  49. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/my/index.css
  50. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/my/updatePassword.css
  51. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/mine/detail.css
  52. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/mine/index.css
  53. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/mine/preview.css
  54. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/overview/index.css
  55. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/overview/noticeDetail.css
  56. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/publish/index.css
  57. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/publish/personList.css
  58. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/notice/publish/selectObject.css
  59. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/studentStudy/index.css
  60. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/studentStudy/learningMonitor/taskDetails.css
  61. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/studentStudy/videoDetails.css
  62. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/teacherHonor/honorOverview/honorTypeDetail.css
  63. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/teacherHonor/index.css
  64. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/teacherStudy/index.css
  65. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/teacherStudy/learningMonitor/taskDetails.css
  66. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/teacherStudy/videoDetails.css
  67. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/todo/index.css
  68. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/workspace/index.css
  69. 0 0
      unpackage/cache/wgt/__UNI__07352BD/pages/workspace/schedule.css
  70. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/icon/delete.png
  71. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/icon/edit.png
  72. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/icon/honor_cup.png
  73. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/icon/honor_seal.png
  74. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorAudit.png
  75. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorAudit_cur.png
  76. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorOverview.png
  77. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorOverview_cur.png
  78. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorTeacher.png
  79. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorTeacher_cur.png
  80. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/myHonor.png
  81. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/myHonor_cur.png
  82. BIN
      unpackage/cache/wgt/__UNI__07352BD/static/image/workspace/add.png
  83. 0 15
      unpackage/dist/cache/.vite/deps/_metadata.json
  84. 0 3
      unpackage/dist/cache/.vite/deps/package.json
  85. BIN
      unpackage/release/apk/__UNI__07352BD__20260729160304.apk
  86. 4 2
      vite.config.js

+ 37 - 6
common/request.js

@@ -1,4 +1,3 @@
-
 import config from '@/config.js'; // 引入配置文件
 import store from '@/store'
 
@@ -11,6 +10,10 @@ if (process.env.NODE_ENV === 'development') {//本地开发环境
   apiBaseUrl = config.apiBaseUrl
 }
 console.log(process.env.NODE_ENV,apiBaseUrl)
+
+// 防止 401 重复处理的标志
+let isHandling401 = false;
+
 const request = (options) => {
 	if(options.url.endsWith('.js')) return;
 	
@@ -74,14 +77,42 @@ const isLoginPage = () => {
   return currentPage.route === 'pages/login/index';
 };
 
+const clearUserInfoAndRedirect = () => {
+  // 清除用户信息
+  uni.removeStorageSync('token');
+  uni.removeStorageSync('userInfo');
+  uni.removeStorageSync('schoolsList');
+  
+  // 清除store中的信息
+  store.commit('userStore/SET_TOKEN', '');
+  store.commit('userStore/SET_USER_INFO', {});
+  store.commit('userStore/SET_SCHOOLSLIST', '');
+  
+  // 跳转到登录页
+  if (!isLoginPage()) {
+    uni.reLaunch({
+      url: '/pages/login/index'
+    });
+  }
+};
+
 const responseInterceptor = (response) => {
   if (response.data.code === 401) {
-    store.dispatch('logout', true);
-    if (!isLoginPage()) {
-      uni.redirectTo({
-        url: '/pages/login/index'
-      });
+    // 如果已经在处理 401,直接抛出错误,防止重复调用 sign_out
+    if (isHandling401) {
+      throw new Error(response.data.message || response.data.msg || '未授权,请重新登录');
     }
+    
+    isHandling401 = true;
+    
+    // 清除用户信息并跳转到登录页
+    clearUserInfoAndRedirect();
+    
+    // 延迟重置标志位,避免连续请求都触发401
+    setTimeout(() => {
+      isHandling401 = false;
+    }, 2000);
+    
     throw new Error(response.data.message || response.data.msg || '未授权,请重新登录');
   }
 

+ 44 - 0
common/safeArea.js

@@ -0,0 +1,44 @@
+import { ref } from 'vue';
+
+export const statusBarHeight = ref(20);
+export const safeAreaBottom = ref(0);
+export const safeAreaTop = ref(20);
+
+export const useSafeArea = () => {
+	const initSafeArea = () => {
+		try {
+			const systemInfo = uni.getSystemInfoSync();
+			statusBarHeight.value = systemInfo.statusBarHeight || 20;
+			
+			const safeAreaInsets = systemInfo.safeAreaInsets || {};
+			safeAreaTop.value = safeAreaInsets.top || statusBarHeight.value;
+			safeAreaBottom.value = safeAreaInsets.bottom || 0;
+		} catch (e) {
+			statusBarHeight.value = 20;
+			safeAreaBottom.value = 0;
+			safeAreaTop.value = 20;
+		}
+	};
+
+	return {
+		statusBarHeight,
+		safeAreaBottom,
+		safeAreaTop,
+		initSafeArea
+	};
+};
+
+export const initSafeArea = () => {
+	try {
+		const systemInfo = uni.getSystemInfoSync();
+		statusBarHeight.value = systemInfo.statusBarHeight || 20;
+		
+		const safeAreaInsets = systemInfo.safeAreaInsets || {};
+		safeAreaTop.value = safeAreaInsets.top || statusBarHeight.value;
+		safeAreaBottom.value = safeAreaInsets.bottom || 0;
+	} catch (e) {
+		statusBarHeight.value = 20;
+		safeAreaBottom.value = 0;
+		safeAreaTop.value = 20;
+	}
+};

+ 10 - 3
components/common-header.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="common_header">
+	<view class="common_header" :style="{ paddingTop: statusBarHeight + 62 + 'rpx' }">
 		<view class="header_left" @click="handleBack">
 			<uni-icons type="back" size="24" color="#333333"></uni-icons>
 		</view>
@@ -11,6 +11,9 @@
 </template>
 
 <script setup>
+import { onMounted } from 'vue';
+import { useSafeArea } from '@/common/safeArea';
+import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 const props = defineProps({
 	title: {
 		type: String,
@@ -19,6 +22,11 @@ const props = defineProps({
 });
 
 const emit = defineEmits(['back']);
+const { statusBarHeight, initSafeArea } = useSafeArea();
+
+onMounted(() => {
+	initSafeArea();
+});
 
 const handleBack = () => {
 	emit('back');
@@ -35,10 +43,9 @@ const handleBack = () => {
 	display: flex;
 	align-items: center;
 	justify-content: space-between;
-	padding-top: env(safe-area-inset-top);
-	height: calc(96rpx + env(safe-area-inset-top));
 	padding-left: 24rpx;
 	padding-right: 24rpx;
+	padding-bottom: 24rpx;
 	background-color: #FFFFFF;
 	border-bottom: 2rpx solid #F3F3F3;
 

+ 96 - 16
components/commonTabbar.vue

@@ -1,7 +1,25 @@
 <template>
-	<view v-if="currentType === 'notice'" class="common_tabbar">
-		<view v-for="(item, idx) in noticeTabs" :key="idx" class="tabbar_item" :class="{ active: currentPath === item.path }" @click="switchTab(item.path)">
-			<image :src="currentPath === item.path ? item.selectedIconPath : item.iconPath" class="tabbar_icon" mode="aspectFit"></image>
+	<view v-if="currentType === 'home'" class="common_tabbar" :style="{ paddingBottom: 24 + 'rpx' }">
+		<view v-for="(item, idx) in homeTabs" :key="idx" class="tabbar_item"
+			:class="{ active: currentPath === item.path }" @click="switchTab(item.path)">
+			<image :src="currentPath === item.path ? item.selectedIconPath : item.iconPath" class="tabbar_icon"
+				mode="aspectFit"></image>
+			<text class="tabbar_text">{{ item.text }}</text>
+		</view>
+	</view>
+	<view v-else-if="currentType === 'notice'" class="common_tabbar" :style="{ paddingBottom: 24 + 'rpx' }">
+		<view v-for="(item, idx) in noticeTabs" :key="idx" class="tabbar_item"
+			:class="{ active: currentPath === item.path }" @click="switchTab(item.path)">
+			<image :src="currentPath === item.path ? item.selectedIconPath : item.iconPath" class="tabbar_icon"
+				mode="aspectFit"></image>
+			<text class="tabbar_text">{{ item.text }}</text>
+		</view>
+	</view>
+	<view v-else-if="currentType === 'materialCollection'" class="common_tabbar" :style="{ paddingBottom: 24 + 'rpx' }">
+		<view v-for="(item, idx) in materialCollectionTabs" :key="idx" class="tabbar_item"
+			:class="{ active: currentPath === item.path }" @click="switchTab(item.path)">
+			<image :src="currentPath === item.path ? item.selectedIconPath : item.iconPath" class="tabbar_icon"
+				mode="aspectFit"></image>
 			<text class="tabbar_text">{{ item.text }}</text>
 		</view>
 	</view>
@@ -9,8 +27,37 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
+import { useSafeArea } from '@/common/safeArea';
 
 const currentPath = ref('');
+const { safeAreaBottom, initSafeArea } = useSafeArea();
+
+const homeTabs = [
+	{
+		path: '/pages/workspace/index',
+		iconPath: '/static/image/tabbar/workspace.png',
+		selectedIconPath: '/static/image/tabbar/workspace_cur.png',
+		text: '工作台'
+	},
+	{
+		path: '/pages/todo/index',
+		iconPath: '/static/image/tabbar/todo.png',
+		selectedIconPath: '/static/image/tabbar/todo_cur.png',
+		text: '待办'
+	},
+	{
+		path: '/pages/message/index',
+		iconPath: '/static/image/tabbar/message.png',
+		selectedIconPath: '/static/image/tabbar/message_cur.png',
+		text: '消息'
+	},
+	{
+		path: '/pages/my/index',
+		iconPath: '/static/image/tabbar/my.png',
+		selectedIconPath: '/static/image/tabbar/my_cur.png',
+		text: '我的'
+	}
+];
 
 const noticeTabs = [
 	{
@@ -33,11 +80,43 @@ const noticeTabs = [
 	}
 ];
 
+const materialCollectionTabs = [
+	{
+		path: '/pages/materialCollection/taskOverview/index',
+		iconPath: '/static/image/overview/zonglan.png',
+		selectedIconPath: '/static/image/overview/zonglanIcon.png',
+		text: '任务总览'
+	},
+	{
+		path: '/pages/materialCollection/submitMaterial/index',
+		iconPath: '/static/image/materialCollection/tijiao.png',
+		selectedIconPath: '/static/image/materialCollection/tijiaoIcon.png',
+		text: '提交材料'
+	},
+	{
+		path: '/pages/materialCollection/reviewMaterial/index',
+		iconPath: '/static/image/materialCollection/shenhe.png',
+		selectedIconPath: '/static/image/materialCollection/shenheIcon.png',
+		text: '审核材料'
+	}
+];
+
 const currentType = computed(() => {
 	if (currentPath.value.startsWith('/pages/notice/')) {
 		return 'notice';
 	}
-	return 'home';
+	if (currentPath.value.startsWith('/pages/materialCollection/taskOverview/') ||
+		currentPath.value.startsWith('/pages/materialCollection/submitMaterial/') ||
+		currentPath.value.startsWith('/pages/materialCollection/reviewMaterial/')) {
+		return 'materialCollection';
+	}
+	if (currentPath.value.startsWith('/pages/workspace/') ||
+		currentPath.value.startsWith('/pages/todo/') ||
+		currentPath.value.startsWith('/pages/message/') ||
+		currentPath.value.startsWith('/pages/my/')) {
+		return 'home';
+	}
+	return '';
 });
 
 const updateCurrentPath = () => {
@@ -50,16 +129,16 @@ const updateCurrentPath = () => {
 
 const switchTab = (path) => {
 	if (currentPath.value === path) return;
-	uni.redirectTo({ url: path });
+	if (currentType.value === 'materialCollection') {
+		uni.redirectTo({ url: path });
+		return;
+	}
+	uni.reLaunch({ url: path });
 };
 
 onMounted(() => {
+	initSafeArea();
 	updateCurrentPath();
-	if (currentType.value === 'notice') {
-		uni.hideTabBar({
-			fail: () => {}
-		});
-	}
 });
 </script>
 
@@ -71,11 +150,11 @@ onMounted(() => {
 	right: 0;
 	display: flex;
 	justify-content: space-around;
+	align-items: center;
 	background-color: #FFFFFF;
-	padding: 16rpx 0;
-	padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
+	padding-top: 24rpx;
+	padding-bottom: 24rpx;
 	border-top: 1rpx solid #F0F0F0;
-	height: 84px;
 	box-sizing: border-box;
 	z-index: 999;
 
@@ -84,10 +163,11 @@ onMounted(() => {
 		flex-direction: column;
 		align-items: center;
 		justify-content: center;
+		padding: 4rpx 16rpx;
 
 		.tabbar_icon {
-			width: 48rpx;
-			height: 48rpx;
+			width: 44rpx;
+			height: 44rpx;
 		}
 
 		.tabbar_text {
@@ -103,4 +183,4 @@ onMounted(() => {
 		}
 	}
 }
-</style>
+</style>

+ 2 - 2
components/nav-bar.vue

@@ -148,11 +148,11 @@
 	}
 	const GoBack = () => {
 		if (props.backPath == 'myIndex') {
-			uni.switchTab({
+			uni.redirectTo({
 				url: '/pages/my/index'
 			});
 		} else if (props.backPath == 'workspace') { //跳转工作台
-			uni.switchTab({
+			uni.redirectTo({
 				url: '/pages/workspace/index'
 			});
 		} else {

+ 11 - 2
components/no-data.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="no_data">
+	<view class="no_data" :class="{ 'no_data_centered': centered }">
 		<image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
 		<text class="no_data_text">{{text}}</text>
 	</view>
@@ -10,6 +10,10 @@
 		text:{
 			type: String,
 			default: '暂无数据'
+		},
+		centered:{
+			type: Boolean,
+			default: false
 		}
 	})
 </script>
@@ -32,4 +36,9 @@
 			line-height: 36rpx;
 		}
 	}
-</style>
+	.no_data_centered{
+		flex: 1;
+		height: auto;
+		min-height: 0;
+	}
+</style>

+ 157 - 18
components/notice-header.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="notice_header">
+	<view class="notice_header" :style="{ paddingTop: statusBarHeight + 'rpx' }">
 		<!-- 顶部栏 -->
 		<view class="header_top">
 			<view class="header_left">
@@ -12,16 +12,14 @@
 			</view>
 			<text v-else-if="showSave" class="save_btn" @click="handleSave">保存</text>
 		</view>
-		<!-- 过滤栏 -->
-		<view v-if="showFilter" class="filter_bar">
-			<!-- 通知类型筛选 -->
+		<!-- 通知模式过滤栏 -->
+		<view v-if="showFilter && mode === 'notice'" class="filter_bar">
 			<picker mode="selector" :range="typeOptions" :value="typeIndex" @change="handleTypeChange">
 				<view class="filter_item">
 					<text class="filter_text">{{ typeOptions[typeIndex] }}</text>
 					<uni-icons type="down" size="16" color="#999999"></uni-icons>
 				</view>
 			</picker>
-			<!-- 时间筛选 -->
 			<picker mode="selector" :range="timeOptions" :value="timeIndex" @change="handleTimeChange">
 				<view class="filter_item">
 					<text class="filter_text">{{ timeOptions[timeIndex] }}</text>
@@ -29,10 +27,40 @@
 				</view>
 			</picker>
 		</view>
-		<!-- 标签栏 -->
-		<view v-if="showTabs" class="tabs_bar">
+		<!-- 通知模式标签栏 -->
+		<view v-if="showTabs && mode === 'notice'" class="tabs_bar">
 			<text v-for="(tab, idx) in tabs" :key="idx" class="tab_item" :class="{ active: activeTabIndex === idx }" @click="handleTabChange(idx)">{{ tab }}</text>
 		</view>
+		
+		<!-- 任务总览模式过滤栏 -->
+		<view v-if="showFilter && mode === 'taskOverview'" class="filter_bar task_overview_filter">
+			<picker mode="selector" class="picker_wide" :range="academicYearOptions" :value="academicYearIndex" @change="handleAcademicYearChange">
+				<view class="filter_item">
+					<text class="filter_text">{{ academicYearOptions[academicYearIndex] }}</text>
+					<uni-icons type="down" size="16" color="#999999"></uni-icons>
+				</view>
+			</picker>
+			<picker mode="selector" class="picker_narrow" :range="categoryOptions" :value="categoryIndex" @change="handleCategoryChange">
+				<view class="filter_item">
+					<text class="filter_text">{{ categoryOptions[categoryIndex] }}</text>
+					<uni-icons type="down" size="16" color="#999999"></uni-icons>
+				</view>
+			</picker>
+			<picker mode="selector" class="picker_narrow" :range="materialTypeOptions" :value="materialTypeIndex" @change="handleMaterialTypeChange">
+				<view class="filter_item">
+					<text class="filter_text">{{ materialTypeOptions[materialTypeIndex] }}</text>
+					<uni-icons type="down" size="16" color="#999999"></uni-icons>
+				</view>
+			</picker>
+		</view>
+		<!-- 任务总览模式标签栏 -->
+		<view v-if="(showTabs || showCreateBtn) && mode === 'taskOverview'" class="tabs_bar task_overview_tabs">
+			<text v-for="(tab, idx) in tabs" :key="idx" class="tab_item" :class="{ active: activeTabIndex === idx }" @click="handleTabChange(idx)">{{ tab }}</text>
+			<view v-if="showCreateBtn" class="create_btn" @click="handleCreate">
+				<image src="/static/image/workspace/add.png" class="add_icon" mode="aspectFit"></image>
+				<text class="create_text">新建</text>
+			</view>
+		</view>
 	</view>
 </template>
 
@@ -40,6 +68,9 @@
 import { ref, onMounted, computed } from 'vue';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import overviewApi from '@/reqApi/overview.js';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, initSafeArea } = useSafeArea();
 
 const typeIndex = ref(0);
 const timeIndex = ref(0);
@@ -47,11 +78,20 @@ const typeData = ref([{ id: '0', name: '全部类型' }]);
 const typeOptions = ref(['全部类型']);
 const searchWord = ref('');
 
+const academicYearIndex = ref(0);
+const categoryIndex = ref(0);
+const materialTypeIndex = ref(0);
+
 const props = defineProps({
 	title: {
 		type: String,
 		default: ''
 	},
+	mode: {
+		type: String,
+		default: 'notice',
+		validator: (value) => ['notice', 'taskOverview'].includes(value)
+	},
 	showSearch: {
 		type: Boolean,
 		default: false
@@ -68,10 +108,26 @@ const props = defineProps({
 		type: Array,
 		default: () => ['全部时间', '最近3天', '最近7天', '最近1月', '最近3月']
 	},
+	academicYearOptions: {
+		type: Array,
+		default: () => ['2025-2026学年', '2024-2025学年', '2023-2024学年']
+	},
+	categoryOptions: {
+		type: Array,
+		default: () => ['全部分类', '日常教学', '考试测评', '教研活动']
+	},
+	materialTypeOptions: {
+		type: Array,
+		default: () => ['全部类目', '教案', '课件', '试卷', '作业']
+	},
 	showTabs: {
 		type: Boolean,
 		default: false
 	},
+	showCreateBtn: {
+		type: Boolean,
+		default: false
+	},
 	tabs: {
 		type: Array,
 		default: () => ['全部', '待发布', '已发布', '已关闭']
@@ -95,7 +151,10 @@ const activeTabIndex = computed(() => {
 	return statusMap.indexOf(props.activeTab);
 });
 
-const emit = defineEmits(['save', 'tabChange', 'typeChange', 'timeChange', 'searchChange']);
+const emit = defineEmits([
+	'save', 'tabChange', 'typeChange', 'timeChange', 'searchChange',
+	'academicYearChange', 'categoryChange', 'materialTypeChange', 'create'
+]);
 
 const fetchNoticeTypeList = async () => {
 	try {
@@ -114,7 +173,10 @@ const fetchNoticeTypeList = async () => {
 };
 
 onMounted(() => {
-	fetchNoticeTypeList();
+	initSafeArea();
+	if (props.mode === 'notice') {
+		fetchNoticeTypeList();
+	}
 });
 
 const handleTabChange = (idx) => {
@@ -137,8 +199,23 @@ const handleTimeChange = (e) => {
 	emit('timeChange', timeIndex.value);
 };
 
+const handleAcademicYearChange = (e) => {
+	academicYearIndex.value = e.detail.value;
+	emit('academicYearChange', academicYearIndex.value);
+};
+
+const handleCategoryChange = (e) => {
+	categoryIndex.value = e.detail.value;
+	emit('categoryChange', categoryIndex.value);
+};
+
+const handleMaterialTypeChange = (e) => {
+	materialTypeIndex.value = e.detail.value;
+	emit('materialTypeChange', materialTypeIndex.value);
+};
+
 const goBack = () => {
-	uni.switchTab({
+	uni.redirectTo({
 		url: '/pages/workspace/index'
 	});
 };
@@ -150,6 +227,10 @@ const handleSave = () => {
 const handleSearchInput = () => {
 	emit('searchChange', searchWord.value);
 };
+
+const handleCreate = () => {
+	emit('create');
+};
 </script>
 
 <style lang="scss" scoped>
@@ -162,14 +243,12 @@ const handleSearchInput = () => {
 	flex-direction: column;
 	background-color: #FFFFFF;
 	z-index: 999;
-	padding-top: env(safe-area-inset-top);
-	// border-bottom: 2rpx solid #EBEEF5;
 
 	.header_top {
 		display: flex;
 		align-items: center;
 		justify-content: space-between;
-		padding: 24rpx;
+		padding:36rpx 24rpx 24rpx;
 
 		.header_left {
 			display: flex;
@@ -232,7 +311,6 @@ const handleSearchInput = () => {
 			background-color: #FFFFFF;
 			border-radius: 8rpx;
 			min-width: 240rpx;
-			// height: 72rpx;
 			border: 2rpx solid #DCDFE6;
 
 			.filter_text {
@@ -243,10 +321,36 @@ const handleSearchInput = () => {
 		}
 	}
 
+	.task_overview_filter {
+		gap: 16rpx;
+
+		.picker_wide {
+			flex: 1.2;
+		}
+
+		.picker_narrow {
+			flex: 1;
+		}
+
+		.filter_item {
+			width: 100%;
+			min-width: 0;
+			padding: 16rpx 12rpx;
+			box-sizing: border-box;
+			.filter_text {
+				font-size: 24rpx;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+			}
+		}
+	}
+
 	.tabs_bar {
 		display: flex;
 		height: 56rpx;
-		padding:4rpx 24rpx 24rpx 24rpx;
+		padding: 4rpx 24rpx 12rpx 24rpx;
+
 		.tab_item {
 			font-size: 32rpx;
 			color: #999999;
@@ -264,13 +368,48 @@ const handleSearchInput = () => {
 					bottom: 0rpx;
 					left: 50%;
 					transform: translateX(-50%);
-					width: 48rpx;
+					width: 100%;
 					height: 4rpx;
 					background-color: #2E64FA;
-					// border-radius: 2rpx;
 				}
 			}
 		}
 	}
+
+	.task_overview_tabs {
+		justify-content: flex-start;
+		align-items: center;
+		height: 72rpx;
+		.tab_item {
+			margin-right: 48rpx;
+			&.active {
+
+				&::after {
+					bottom: -8rpx;
+				}
+			}
+		}
+
+		.create_btn {
+			margin-left: auto;
+			background-color: #2E64FA;
+			border-radius: 8rpx;
+			width: 148rpx;
+			height: 72rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			gap: 8rpx;
+			.add_icon {
+				width: 28rpx;
+				height: 28rpx;	
+			}
+			.create_text {
+				color: #FFFFFF;
+				font-size: 28rpx;
+				font-weight: 500;
+			}
+		}
+	}
 }
-</style>
+</style>

+ 4 - 2
components/page-header.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="page_header">
+	<view class="page_header" :style="{ paddingTop: 80 + 'rpx' }">
 		<text class="header_title">{{ schoolName }}</text>
 	</view>
 </template>
@@ -7,11 +7,14 @@
 <script setup>
 	import { ref, onMounted } from 'vue';
 	import { useStore } from 'vuex';
+	import { useSafeArea } from '@/common/safeArea';
 
 	const store = useStore();
 	const schoolName = ref('');
+	const { statusBarHeight, initSafeArea } = useSafeArea();
 
 	onMounted(() => {
+		initSafeArea();
 		schoolName.value = store.state.userStore.userInfo.schoolName || '';
 	});
 </script>
@@ -23,7 +26,6 @@
 	left: 0;
 	right: 0;
 	background-color: #FFFFFF;
-	padding-top: calc(26rpx + env(safe-area-inset-top));
 	padding-bottom: 26rpx;
 	padding-left: 24rpx;
 	padding-right: 24rpx;

+ 10 - 1
components/tree-node.vue

@@ -8,7 +8,10 @@
 						<image v-else-if="item.checked && (!item.children || item.children.length === 0)" src="/static/image/publish/correct.png" class="checkbox_check_image" mode="aspectFit"></image>
 					</view>
 				</view>
-				<text class="row_text" :class="{ 'leaf-node': !item.children || item.children.length === 0 }">{{ item.name }}</text>
+				<text class="row_text" :class="{ 'leaf-node': !item.children || item.children.length === 0 }">
+					{{ item.name }}
+					<text v-if="item.userAccount" class="user-account">({{ item.userAccount }})</text>
+				</text>
 				<view v-if="item.children && item.children.length > 0" class="expand_icon">
 					<image src="/static/image/icon/xiala.png" class="expand-arrow" :class="{ expanded: expandedIds.includes(item.id) }" mode="aspectFit"></image>
 				</view>
@@ -126,6 +129,12 @@ export default {
 			font-size: 28rpx;
 			color: #333333;
 
+			.user-account {
+				font-size: 24rpx;
+				color: #999999;
+				margin-left: 8rpx;
+			}
+
 			&.leaf-node {
 				color: #666666;
 			}

+ 22 - 31
pages.json

@@ -182,6 +182,27 @@
 				"navigationBarTitleText": "教师发展-新建/修改荣誉类型",
 				"navigationStyle": "custom"
 			}
+		},
+		{
+			"path": "pages/materialCollection/taskOverview/index",
+			"style": {
+				"navigationBarTitleText": "任务总览",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/materialCollection/submitMaterial/index",
+			"style": {
+				"navigationBarTitleText": "提交材料",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/materialCollection/reviewMaterial/index",
+			"style": {
+				"navigationBarTitleText": "审核材料",
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"easycom": {
@@ -196,35 +217,5 @@
 		"navigationBarBackgroundColor": "#F8F8F8",
 		"backgroundColor": "#F8F8F8"
 	},
-	"uniIdRouter": {},
-	"tabBar": {
-		"color": "#7A7E83",
-		"selectedColor": "#2E64FA",
-		"borderStyle": "black",
-		"fontSize": "12px",
-		"backgroundColor": "#FFFFFF",
-		"height": "136rpx",
-		"list": [{
-				"pagePath": "pages/workspace/index",
-				"iconPath": "static/image/tabbar/workspace.png",
-				"selectedIconPath": "static/image/tabbar/workspace_cur.png",
-				"text": "工作台"
-			}, {
-				"pagePath": "pages/todo/index",
-				"iconPath": "static/image/tabbar/todo.png",
-				"selectedIconPath": "static/image/tabbar/todo_cur.png",
-				"text": "待办"
-			}, {
-				"pagePath": "pages/message/index",
-				"iconPath": "static/image/tabbar/message.png",
-				"selectedIconPath": "static/image/tabbar/message_cur.png",
-				"text": "消息"
-			}, {
-				"pagePath": "pages/my/index",
-				"iconPath": "static/image/tabbar/my.png",
-				"selectedIconPath": "static/image/tabbar/my_cur.png",
-				"text": "我的"
-			}
-		]
-	}
+	"uniIdRouter": {}
 }

+ 5 - 5
pages/login/index.vue

@@ -130,10 +130,10 @@ onMounted(() => {
 				uni.setStorageSync('token', token); // 存储 token 到本地
 				store.dispatch('setUserInfo');
 
-				uni.switchTab({
-					url: '/pages/workspace/index'
-				});
-			} else {
+				uni.redirectTo({
+				url: '/pages/workspace/index'
+			});
+		} else {
 				uni.showToast({
 					title: res.msg || '账号不存在或者密码错误!',
 					icon: 'none'
@@ -449,7 +449,7 @@ const LoginSuccess = () => {
 	});
 	store.dispatch('setUserInfo');
 
-	uni.switchTab({
+	uni.redirectTo({
 		url: '/pages/workspace/index'
 	});
 }

+ 189 - 0
pages/materialCollection/reviewMaterial/index.vue

@@ -0,0 +1,189 @@
+<template>
+	<view class="page_review" :style="{paddingTop: statusBarHeight + 120 + 'rpx', paddingBottom: safeAreaBottom + 100 + 'rpx' }">
+		<notice-header title="审核材料" mode="review">
+		</notice-header>
+
+		<view class="review_content">
+			<view class="review_list">
+				<scroll-view v-if="reviewList.length > 0" scroll-y>
+					<view v-for="item in reviewList" :key="item.id" class="review_item">
+						<view class="item_header">
+							<text class="submitter_name">{{ item.submitter }}</text>
+							<view class="review_status" :class="item.statusClass">
+								<text>{{ item.status }}</text>
+							</view>
+						</view>
+						<view class="item_info">
+							<text class="info_text">提交时间: {{ item.submitTime }}</text>
+							<text class="info_text">材料: {{ item.materialCount }}个</text>
+						</view>
+						<view class="item_actions">
+							<view class="action_btn reject" @click="handleReject(item)">
+								<text>驳回</text>
+							</view>
+							<view class="action_btn approve" @click="handleApprove(item)">
+								<text>通过</text>
+							</view>
+						</view>
+					</view>
+				</scroll-view>
+
+				<no-data v-else centered text="暂无待审核材料" />
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import NoticeHeader from '@/components/notice-header.vue';
+import NoData from '@/components/no-data.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
+
+const reviewList = ref([
+	{
+		id: 1,
+		submitter: '张老师',
+		status: '待审核',
+		statusClass: 'status-pending',
+		submitTime: '2025-07-28 10:30',
+		materialCount: 5
+	},
+	{
+		id: 2,
+		submitter: '李老师',
+		status: '待审核',
+		statusClass: 'status-pending',
+		submitTime: '2025-07-28 11:20',
+		materialCount: 3
+	},
+	{
+		id: 3,
+		submitter: '王老师',
+		status: '待审核',
+		statusClass: 'status-pending',
+		submitTime: '2025-07-27 16:45',
+		materialCount: 8
+	}
+]);
+
+onMounted(() => {
+	initSafeArea();
+});
+
+const handleApprove = (item) => {
+	uni.showModal({
+		title: '确认通过',
+		content: `确定要通过 ${item.submitter} 提交的材料吗?`,
+		success: (res) => {
+			if (res.confirm) {
+				uni.showToast({ title: '审核通过', icon: 'success' });
+				reviewList.value = reviewList.value.filter(i => i.id !== item.id);
+			}
+		}
+	});
+};
+
+const handleReject = (item) => {
+	uni.showModal({
+		title: '确认驳回',
+		content: `确定要驳回 ${item.submitter} 提交的材料吗?`,
+		success: (res) => {
+			if (res.confirm) {
+				uni.showToast({ title: '已驳回', icon: 'none' });
+				reviewList.value = reviewList.value.filter(i => i.id !== item.id);
+			}
+		}
+	});
+};
+</script>
+
+<style lang="scss" scoped>
+.page_review {
+	min-height: 100vh;
+	background-color: #F5F7FA;
+}
+
+.review_content {
+	flex: 1;
+	padding: 24rpx;
+}
+
+.review_list {
+	background-color: #FFFFFF;
+	border-radius: 16rpx;
+	overflow: hidden;
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+}
+
+.review_item {
+	padding: 24rpx;
+	border-bottom: 1rpx solid #EBEEF5;
+
+	&:last-child {
+		border-bottom: none;
+	}
+
+	.item_header {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		margin-bottom: 16rpx;
+
+		.submitter_name {
+			font-size: 30rpx;
+			font-weight: 500;
+			color: #333333;
+		}
+
+		.review_status {
+			font-size: 24rpx;
+			padding: 8rpx 16rpx;
+			border-radius: 8rpx;
+
+			&.status-pending {
+				background: rgba(230, 162, 60, 0.1);
+				color: #E6A23C;
+			}
+		}
+	}
+
+	.item_info {
+		display: flex;
+		gap: 32rpx;
+		margin-bottom: 20rpx;
+
+		.info_text {
+			font-size: 26rpx;
+			color: #999999;
+		}
+	}
+
+	.item_actions {
+		display: flex;
+		justify-content: flex-end;
+		gap: 24rpx;
+
+		.action_btn {
+			font-size: 28rpx;
+			padding: 16rpx 32rpx;
+			border-radius: 12rpx;
+
+			&.reject {
+				background-color: #FFFFFF;
+				border: 2rpx solid #E4E7ED;
+				color: #666666;
+			}
+
+			&.approve {
+				background-color: #2E64FA;
+				color: #FFFFFF;
+			}
+		}
+	}
+}
+</style>

+ 260 - 0
pages/materialCollection/submitMaterial/index.vue

@@ -0,0 +1,260 @@
+<template>
+	<view class="page_submit" :style="{paddingTop: statusBarHeight + 200 + 'rpx', paddingBottom: safeAreaBottom + 100 + 'rpx' }">
+		<notice-header 
+			:title="isEdit ? '编辑任务' : '新建任务'" 
+			mode="submit"
+			:showSave="true"
+			@save="handleSave">
+		</notice-header>
+
+		<view class="form_container">
+			<scroll-view scroll-y class="form_scroll">
+				<view class="form_section">
+					<view class="section_title">基本信息</view>
+					
+					<view class="form_item">
+						<text class="form_label">任务标题</text>
+						<input class="form_input" v-model="formData.title" placeholder="请输入任务标题" />
+					</view>
+
+					<view class="form_item">
+						<text class="form_label">学年</text>
+						<picker mode="selector" :range="academicYearOptions" :value="academicYearIndex" @change="handleAcademicYearChange">
+							<view class="picker_item">
+								<text>{{ academicYearOptions[academicYearIndex] }}</text>
+								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							</view>
+						</picker>
+					</view>
+
+					<view class="form_item">
+						<text class="form_label">归属分类</text>
+						<picker mode="selector" :range="categoryOptions" :value="categoryIndex" @change="handleCategoryChange">
+							<view class="picker_item">
+								<text>{{ categoryOptions[categoryIndex] }}</text>
+								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							</view>
+						</picker>
+					</view>
+
+					<view class="form_item">
+						<text class="form_label">材料类目</text>
+						<picker mode="selector" :range="materialTypeOptions" :value="materialTypeIndex" @change="handleMaterialTypeChange">
+							<view class="picker_item">
+								<text>{{ materialTypeOptions[materialTypeIndex] }}</text>
+								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							</view>
+						</picker>
+					</view>
+				</view>
+
+				<view class="form_section">
+					<view class="section_title">时间设置</view>
+					
+					<view class="form_item">
+						<text class="form_label">开始日期</text>
+						<picker mode="date" :value="formData.startDate" @change="handleStartDateChange">
+							<view class="picker_item">
+								<text>{{ formData.startDate || '请选择开始日期' }}</text>
+								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							</view>
+						</picker>
+					</view>
+
+					<view class="form_item">
+						<text class="form_label">截止日期</text>
+						<picker mode="date" :value="formData.deadline" @change="handleDeadlineChange">
+							<view class="picker_item">
+								<text>{{ formData.deadline || '请选择截止日期' }}</text>
+								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							</view>
+						</picker>
+					</view>
+				</view>
+
+				<view class="form_section">
+					<view class="section_title">任务描述</view>
+					<textarea 
+						class="form_textarea" 
+						v-model="formData.description" 
+						placeholder="请输入任务描述" 
+						placeholder-class="textarea_placeholder"
+						maxlength="500"
+					></textarea>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { onLoad } from '@dcloudio/uni-app';
+import NoticeHeader from '@/components/notice-header.vue';
+import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
+
+const taskId = ref(null);
+const isEdit = computed(() => !!taskId.value);
+
+const academicYearOptions = ['2025-2026学年', '2024-2025学年', '2023-2024学年'];
+const categoryOptions = ['全部分类', '日常教学', '考试测评', '教研活动'];
+const materialTypeOptions = ['全部类目', '教案', '课件', '试卷', '作业'];
+
+const academicYearIndex = ref(0);
+const categoryIndex = ref(0);
+const materialTypeIndex = ref(0);
+
+const formData = ref({
+	title: '',
+	startDate: '',
+	deadline: '',
+	description: ''
+});
+
+onLoad((options) => {
+	if (options && options.id) {
+		taskId.value = options.id;
+		loadTaskDetail(options.id);
+	}
+});
+
+onMounted(() => {
+	initSafeArea();
+});
+
+const loadTaskDetail = (id) => {
+	// 模拟加载任务详情
+	formData.value = {
+		title: '2025年秋季学期教案材料收集',
+		startDate: '2025-07-28',
+		deadline: '2025-08-15',
+		description: '请各位教师按时提交本学期的教案材料'
+	};
+};
+
+const handleAcademicYearChange = (e) => {
+	academicYearIndex.value = e.detail.value;
+};
+
+const handleCategoryChange = (e) => {
+	categoryIndex.value = e.detail.value;
+};
+
+const handleMaterialTypeChange = (e) => {
+	materialTypeIndex.value = e.detail.value;
+};
+
+const handleStartDateChange = (e) => {
+	formData.value.startDate = e.detail.value;
+};
+
+const handleDeadlineChange = (e) => {
+	formData.value.deadline = e.detail.value;
+};
+
+const handleSave = () => {
+	if (!formData.value.title) {
+		uni.showToast({ title: '请输入任务标题', icon: 'none' });
+		return;
+	}
+	
+	uni.showModal({
+		title: '提示',
+		content: isEdit.value ? '确定要保存修改吗?' : '确定要创建任务吗?',
+		success: (res) => {
+			if (res.confirm) {
+				uni.showToast({ 
+					title: isEdit.value ? '保存成功' : '创建成功', 
+					icon: 'success' 
+				});
+				setTimeout(() => {
+					uni.navigateBack();
+				}, 1500);
+			}
+		}
+	});
+};
+</script>
+
+<style lang="scss" scoped>
+.page_submit {
+	min-height: 100vh;
+	background-color: #F5F7FA;
+}
+
+.form_container {
+	flex: 1;
+	padding: 24rpx;
+}
+
+.form_scroll {
+	height: 100%;
+}
+
+.form_section {
+	background-color: #FFFFFF;
+	border-radius: 16rpx;
+	padding: 24rpx;
+	margin-bottom: 24rpx;
+
+	.section_title {
+		font-size: 30rpx;
+		font-weight: 600;
+		color: #333333;
+		margin-bottom: 24rpx;
+	}
+}
+
+.form_item {
+	display: flex;
+	align-items: center;
+	padding: 24rpx 0;
+	border-bottom: 1rpx solid #EBEEF5;
+
+	&:last-child {
+		border-bottom: none;
+	}
+
+	.form_label {
+		width: 180rpx;
+		font-size: 28rpx;
+		color: #666666;
+	}
+
+	.form_input {
+		flex: 1;
+		font-size: 28rpx;
+		color: #333333;
+		text-align: right;
+	}
+
+	.picker_item {
+		flex: 1;
+		display: flex;
+		align-items: center;
+		justify-content: flex-end;
+		font-size: 28rpx;
+		color: #333333;
+		gap: 8rpx;
+	}
+}
+
+.form_textarea {
+	width: 100%;
+	min-height: 200rpx;
+	font-size: 28rpx;
+	color: #333333;
+	padding: 16rpx;
+	background-color: #F9FAFF;
+	border-radius: 12rpx;
+	box-sizing: border-box;
+}
+
+.textarea_placeholder {
+	color: #999999;
+	font-size: 28rpx;
+}
+</style>

+ 348 - 0
pages/materialCollection/taskOverview/index.vue

@@ -0,0 +1,348 @@
+<template>
+	<view class="page_notice" :style="{paddingTop: statusBarHeight + 310 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
+		<notice-header 
+			title="任务总览" 
+			mode="taskOverview"
+			:showSearch="true" 
+			:showFilter="true" 
+			:showTabs="true" 
+			:showCreateBtn="true"
+			:tabs="tabs"
+			:activeTab="activeTab" 
+			@tabChange="handleTabChange" 
+			@academicYearChange="handleAcademicYearChange"
+			@categoryChange="handleCategoryChange"
+			@materialTypeChange="handleMaterialTypeChange"
+			@searchChange="handleSearchChange"
+			@create="handleCreate">
+		</notice-header>
+
+		<view class="notice_content">
+			<view class="notice_list">
+				<scroll-view scroll-y v-for="item in taskList" :key="item.id" class="notice_item">
+					<view class="item_content">
+						<view class="notice_title_row">
+							<image class="notice_icon" :src="item.icon" mode="aspectFit"></image>
+							<text class="notice_title">{{ item.title }}</text>
+						</view>
+						<view class="notice_meta">
+							<view class="status_tag" :class="item.statusClass">
+								<text>{{ item.status }}</text>
+							</view>
+							<text class="meta_text textLength">{{ item.academicYear }}</text>
+							<text class="meta_text textLength">{{ item.category }}</text>
+							<text class="meta_text">{{ item.time }}</text>
+						</view>
+					</view>
+					<view class="item_bottom">
+						<view class="notice_stats">
+							<text class="stats_text">提交人: {{ item.submitter }}</text>
+							<text class="stats_text">截止: {{ item.deadline }}</text>
+						</view>
+						<view class="notice_actions">
+							<view class="action_btn detail" @click="handleDetail(item.id)">
+								<text>详情</text>
+							</view>
+						</view>
+					</view>
+				</scroll-view>
+
+				<no-data v-if="taskList.length === 0" centered text="暂无任务" />
+			</view>
+		</view>
+
+		<common-tabbar></common-tabbar>
+	</view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import NoticeHeader from '@/components/notice-header.vue';
+import CommonTabbar from '@/components/commonTabbar.vue';
+import NoData from '@/components/no-data.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
+
+const tabs = ['全部', '待开始', '进行中', '已结束'];
+const activeTab = ref(-1);
+const academicYearIndex = ref(0);
+const categoryIndex = ref(0);
+const materialTypeIndex = ref(0);
+const word = ref('');
+const taskList = ref([]);
+
+const statusMap = {
+	0: '待开始',
+	1: '进行中',
+	2: '已结束'
+};
+
+const statusClassMap = {
+	0: 'status-pending',
+	1: 'status-progress',
+	2: 'status-ended'
+};
+
+const mockTaskList = [
+	{
+		id: 1,
+		title: '2025年秋季学期教案材料收集',
+		status: '待开始',
+		statusClass: 'status-pending',
+		academicYear: '2025-2026学年',
+		category: '日常教学',
+		time: '2025-07-28',
+		submitter: '张老师',
+		deadline: '2025-08-15',
+		icon: '/static/image/workspace/icon2.png'
+	},
+	{
+		id: 2,
+		title: '期中考试试卷提交任务',
+		status: '进行中',
+		statusClass: 'status-progress',
+		academicYear: '2025-2026学年',
+		category: '考试测评',
+		time: '2025-07-25',
+		submitter: '李老师',
+		deadline: '2025-08-30',
+		icon: '/static/image/workspace/icon1.png'
+	},
+	{
+		id: 3,
+		title: '课件资源库更新任务',
+		status: '进行中',
+		statusClass: 'status-progress',
+		academicYear: '2025-2026学年',
+		category: '日常教学',
+		time: '2025-07-20',
+		submitter: '王老师',
+		deadline: '2025-09-01',
+		icon: '/static/image/workspace/icon3.png'
+	},
+	{
+		id: 4,
+		title: '教研活动材料归档',
+		status: '已结束',
+		statusClass: 'status-ended',
+		academicYear: '2024-2025学年',
+		category: '教研活动',
+		time: '2025-06-15',
+		submitter: '赵老师',
+		deadline: '2025-07-01',
+		icon: '/static/image/workspace/icon4.png'
+	}
+];
+
+const fetchTaskList = () => {
+	let filteredList = [...mockTaskList];
+	
+	if (activeTab.value >= 0) {
+		filteredList = filteredList.filter(item => {
+			const statusIndex = Object.keys(statusMap).findIndex(key => statusMap[key] === item.status);
+			return statusIndex === activeTab.value;
+		});
+	}
+	
+	if (word.value) {
+		filteredList = filteredList.filter(item => 
+			item.title.includes(word.value)
+		);
+	}
+	
+	taskList.value = filteredList;
+};
+
+onMounted(() => {
+	initSafeArea();
+	fetchTaskList();
+});
+
+const handleTabChange = (status) => {
+	activeTab.value = status;
+	fetchTaskList();
+};
+
+const handleAcademicYearChange = (index) => {
+	academicYearIndex.value = index;
+	fetchTaskList();
+};
+
+const handleCategoryChange = (index) => {
+	categoryIndex.value = index;
+	fetchTaskList();
+};
+
+const handleMaterialTypeChange = (index) => {
+	materialTypeIndex.value = index;
+	fetchTaskList();
+};
+
+const handleSearchChange = (keyword) => {
+	word.value = keyword;
+	fetchTaskList();
+};
+
+const handleCreate = () => {
+	uni.navigateTo({
+		url: '/pages/materialCollection/submitMaterial/index'
+	});
+};
+
+const handleDetail = (id) => {
+	uni.navigateTo({
+		url: `/pages/materialCollection/submitMaterial/index?id=${id}`
+	});
+};
+</script>
+
+<style lang="scss">
+.page_notice {
+	min-height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
+	box-sizing: border-box;
+}
+
+.notice_content {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding-left: 0rpx;
+	padding-right: 0rpx;
+}
+
+.notice_list {
+	flex: 1;
+	overflow-y: auto;
+	overflow-x: hidden;
+	padding: 0 24rpx;
+	display: flex;
+	flex-direction: column;
+}
+
+.notice_item {
+	background-color: #F9FAFF;
+	border-radius: 20rpx;
+	margin-bottom: 24rpx;
+	border: 2rpx solid #E4E7ED;
+
+	.item_content {
+		padding: 24rpx;
+
+		.notice_title_row {
+			display: flex;
+			align-items: center;
+			margin-bottom: 16rpx;
+
+			.notice_icon {
+				width: 32rpx;
+				height: 32rpx;
+				margin-right: 16rpx;
+			}
+
+			.notice_title {
+				flex: 1;
+				font-weight: 500;
+				font-size: 28rpx;
+				color: #333333;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+			}
+		}
+
+		.notice_meta {
+			display: flex;
+			align-items: center;
+			flex-wrap: wrap;
+			gap: 16rpx;
+
+			.status_tag {
+				font-size: 24rpx;
+				height: 48rpx;
+				width: 96rpx;
+				text-align: center;
+				line-height: 48rpx;
+				border-radius: 4rpx;
+				font-weight: 400;
+				border-radius: 8rpx;
+
+				&.status-pending {
+					background: rgba(46, 100, 250, 0.1);
+					color: #2E64FA;
+				}
+
+				&.status-progress {
+					background: rgba(230, 162, 60, 0.1);
+					color: #E6A23C;
+				}
+
+				&.status-ended {
+					background: rgba(144, 147, 153, 0.1);
+					color: #909399;
+				}
+			}
+
+			.meta_text {
+				font-size: 24rpx;
+				color: #999999;
+				line-height: 48rpx;
+			}
+			.textLength {
+				max-width: 160rpx;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+			}
+		}
+	}
+
+	.item_bottom {
+		border-top: 2rpx solid #E4E7ED;
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		padding: 24rpx 16rpx;
+
+		.notice_stats {
+			display: flex;
+
+			.stats_text {
+				font-size: 24rpx;
+				color: #999999;
+
+				&:first-child {
+					margin-right: 32rpx;
+				}
+			}
+		}
+
+		.notice_actions {
+			display: flex;
+			justify-content: flex-end;
+			gap: 16rpx;
+
+			.action_btn {
+				font-size: 24rpx;
+				padding: 12rpx 24rpx;
+				border-radius: 8rpx;
+				border: 2rpx solid transparent;
+
+				&.detail {
+					background-color: #2E64FA;
+					color: #FFFFFF;
+				}
+			}
+		}
+	}
+
+	&:last-child {
+		margin-bottom: 0;
+	}
+}
+</style>

+ 16 - 9
pages/message/index.vue

@@ -1,12 +1,12 @@
 <template>
-	<view class="page_message">
+	<view class="page_message" :style="{paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
 		<page-header></page-header>
 		<view class="message_content">
 			<view class="message_header">
 				<text class="message_title">系统消息({{ unreadCount }}项)</text>
 				<text class="mark_all_read" @click="mark_all_read">全部已读</text>
 			</view>
-			<view class="message_list">
+			<view class="message_list" v-if="messageList.length > 0">
 				<view v-for="item in messageList" :key="item.id" class="message_item">
 					<view class="message_left">
 						<view class="message_info">
@@ -26,10 +26,11 @@
 					</view>
 				</view>
 			</view>
-			<view v-if="messageList.length === 0" class="schedule_empty">
+			<!-- <view v-if="messageList.length === 0" class="schedule_empty">
 				<image src="@/static/image/bg/no_data.png" class="empty_image" mode="aspectFit"></image>
 				<text class="empty_text">暂无系统消息</text>
-			</view>
+			</view> -->
+			<no-data v-if="messageList.length === 0" centered text="暂无系统消息"></no-data>
 		</view>
 		<common-tabbar></common-tabbar>
 	</view>
@@ -40,8 +41,12 @@
 	import { onShow } from '@dcloudio/uni-app';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 	import PageHeader from '@/components/page-header.vue';
+	import CommonTabbar from '@/components/commonTabbar.vue';
 	import workspace from '@/reqApi/workspace.js';
+	import { useSafeArea } from '@/common/safeArea';
+	import NoData from '@/components/no-data.vue';
 
+	const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 	const messageList = ref([]);
 	const unreadCount = ref(0);
 	const getTagStyle = (status) => {
@@ -102,6 +107,10 @@
 			console.error('标记所有消息已读失败:', error);
 		}
 	};
+	onMounted(() => {
+		initSafeArea();
+	});
+
 	onShow(() => {
 		fetchMessageList();
 	});
@@ -109,11 +118,10 @@
 
 <style lang="scss">
 .page_message {
-	height: 100vh;
+	min-height: 100vh;
 	display: flex;
 	flex-direction: column;
 	background-color: #FFFFFF;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
 }
 
@@ -122,8 +130,6 @@
 	display: flex;
 	flex-direction: column;
 	overflow: hidden;
-	padding-top: calc(140rpx + env(safe-area-inset-top));
-	padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
 	padding-left: 0rpx;
 	padding-right: 0rpx;
 }
@@ -268,7 +274,8 @@
 	font-size: 28rpx;
 	color: #999999;
 	position: relative;
-
+	height: 100%;
+	overflow: hidden;
 	.empty_image {
 		width: 400rpx;
 		height: 400rpx;

+ 1 - 3
pages/my/updatePassword.vue

@@ -74,9 +74,7 @@
 	}
 	//返回
 	const GoBack = () => {
-		uni.switchTab({
-			url: '/pages/my/index'
-		});
+		uni.navigateBack();
 	}
 	//修改密码
 	const HandleUpdatePassword = () => {

+ 0 - 2
pages/notice/mine/detail.vue

@@ -112,7 +112,6 @@ const goBack = () => {
 	display: flex;
 	flex-direction: column;
 	background-color: #FFFFFF;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
 }
 
@@ -121,7 +120,6 @@ const goBack = () => {
 	display: flex;
 	flex-direction: column;
 	overflow: hidden;
-	padding-top: calc(140rpx + env(safe-area-inset-top));
 }
 
 .detail_header {

+ 8 - 5
pages/notice/mine/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="page_mine">
+	<view class="page_mine" :style="{paddingTop: statusBarHeight + 322 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
 		<notice-header title="我的通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
 			:activeTab="activeTab" tabMode="index" @tabChange="handleTabChange" @typeChange="handleTypeChange" @timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
 
@@ -25,7 +25,7 @@
 					</view>
 				</scroll-view>
 
-				<no-data v-if="noticeList.length === 0" text="暂无通知" />
+				<no-data v-if="noticeList.length === 0" centered text="暂无通知" />
 			</view>
 		</view>
 
@@ -39,6 +39,9 @@ import { onShow, onLoad } from '@dcloudio/uni-app';
 import NoticeHeader from '@/components/notice-header.vue';
 import NoData from '@/components/no-data.vue';
 import overviewApi from '@/reqApi/overview.js';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const tabs = ['全部', '未读', '已读'];
 const activeTab = ref(0);
@@ -154,7 +157,6 @@ const handleDetail = (id) => {
 	display: flex;
 	flex-direction: column;
 	background-color: #FFFFFF;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
 }
 
@@ -163,8 +165,7 @@ const handleDetail = (id) => {
 	display: flex;
 	flex-direction: column;
 	overflow: hidden;
-	padding-top: calc(325rpx + env(safe-area-inset-top));
-	padding-bottom: calc(208rpx + env(safe-area-inset-bottom));
+
 	padding-left: 0rpx;
 	padding-right: 0rpx;
 }
@@ -174,6 +175,8 @@ const handleDetail = (id) => {
 	overflow-y: auto;
 	overflow-x: hidden;
 	padding: 0 24rpx;
+	display: flex;
+	flex-direction: column;
 }
 
 .notice_item {

+ 20 - 11
pages/notice/overview/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="page_notice">
+	<view class="page_notice" :style="{paddingTop: statusBarHeight + 322 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
 		<notice-header title="校内通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
 			:activeTab="activeTab" @tabChange="handleTabChange" @typeChange="handleTypeChange"
 			@timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
@@ -17,8 +17,8 @@
 							<view class="status_tag" :class="item.statusClass">
 								<text>{{ item.status }}</text>
 							</view>
-							<text class="meta_text">{{ item.category }}</text>
-							<text class="meta_text">{{ item.department }}</text>
+							<text class="meta_text textLength">{{ item.category }}</text>
+							<text class="meta_text textLength">{{ item.department }}</text>
 							<text class="meta_text">{{ item.time }}</text>
 						</view>
 					</view>
@@ -56,13 +56,16 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onMounted } from 'vue';
 import { onShow } from '@dcloudio/uni-app';
 import NoticeHeader from '@/components/notice-header.vue';
 import ConfirmModal from '@/components/confirm-modal.vue';
 import overviewApi from '@/reqApi/overview.js';
 import store from '@/store/index.js';
 import NoData from '@/components/no-data.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const tabs = ['全部', '待发布', '已发布', '已关闭'];
 const activeTab = ref(-1);
@@ -120,6 +123,10 @@ const fetchNoticeList = async () => {
 	}
 };
 
+onMounted(() => {
+	initSafeArea();
+});
+
 onShow(() => {
 	fetchNoticeList();
 });
@@ -191,18 +198,16 @@ const handleDetail = (id) => {
 		uni.showToast({ title: '待发布通知暂无法查看详情', icon: 'none' });
 		return;
 	}
-	const itemStr = encodeURIComponent(JSON.stringify(item));
-	uni.navigateTo({ url: `/pages/notice/overview/noticeDetail?id=${id}&item=${itemStr}` });
+	uni.navigateTo({ url: `/pages/notice/overview/noticeDetail?id=${id}` });
 };
 </script>
 
 <style lang="scss">
 .page_notice {
-	height: 100vh;
+	min-height: 100vh;
 	display: flex;
 	flex-direction: column;
 	background-color: #FFFFFF;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
 }
 
@@ -211,8 +216,6 @@ const handleDetail = (id) => {
 	display: flex;
 	flex-direction: column;
 	overflow: hidden;
-	padding-top: calc(325rpx + env(safe-area-inset-top));
-	padding-bottom: calc(208rpx + env(safe-area-inset-bottom));
 	padding-left: 0rpx;
 	padding-right: 0rpx;
 }
@@ -292,6 +295,12 @@ const handleDetail = (id) => {
 				color: #999999;
 				line-height: 48rpx;
 			}
+			.textLength {
+					max-width: 160rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
 		}
 	}
 
@@ -369,4 +378,4 @@ const handleDetail = (id) => {
 		margin-bottom: 0;
 	}
 }
-</style>
+</style>

+ 50 - 24
pages/notice/overview/noticeDetail.vue

@@ -1,6 +1,6 @@
 <template>
 	<view class="notice_detail">
-		<view class="detail_header">
+		<view class="detail_header" :style="{ paddingTop: statusBarHeight+64 + 'rpx' }">
 			<uni-icons type="back" size="24" color="#333333" @click="handleBack"></uni-icons>
 			<text class="header_title">通知详情</text>
 			<text class="header_placeholder"></text>
@@ -42,10 +42,10 @@
 				</view>
 
 				<view class="stats_row">
-					<text class="stats_text">共 {{ noticeItem.readCount + noticeItem.unreadCount }} 人</text>
+					<text class="stats_text">共 {{ readerList.length }} 人</text>
 				</view>
 
-				<scroll-view scroll-y class="reader_list">
+				<scroll-view v-if="filteredReaders.length > 0" scroll-y class="reader_list">
 					<view v-for="item in filteredReaders" :key="item.id" class="reader_item">
 						<view class="reader_row reader_row_first">
 							<text class="reader_name">{{ item.name }}</text>
@@ -67,9 +67,9 @@
 							<text v-if="item.readTime" class="reader_detail">{{ item.readTime }}</text>
 						</view>
 					</view>
-
-					<no-data v-if="filteredReaders.length === 0" text="暂无数据" />
 				</scroll-view>
+
+				<no-data v-else centered text="暂无数据" />
 			</view>
 		</view>
 	</view>
@@ -77,14 +77,24 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
+import { onLoad } from '@dcloudio/uni-app';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import overviewApi from '@/reqApi/overview.js';
 import NoData from '@/components/no-data.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const tabs = ['全部', '已读', '未读'];
 const activeTab = ref(0);
 const searchWord = ref('');
-const noticeItem = ref({});
+const noticeItem = ref({
+	title: '',
+	category: '',
+	department: '',
+	time: '',
+	noticeStatus: ''
+});
 const readerList = ref([]);
 const noticeId = ref('');
 
@@ -122,6 +132,27 @@ const filteredReaders = computed(() => {
 	return list;
 });
 
+const fetchNoticeInfo = async () => {
+	if (!noticeId.value) {
+		return;
+	}
+
+	try {
+		const res = await overviewApi.notices_edit({ id: noticeId.value });
+		if (res.data) {
+			noticeItem.value = {
+			title: res.data.noticeName || '',
+			category: res.data.typeName || '',
+			department: res.data.createTeacherName || '',
+			time: res.data.releaseTime || '',
+			noticeStatus: res.data.noticeStatus
+		};
+		}
+	} catch (error) {
+		console.error('获取通知详情失败:', error);
+	}
+};
+
 const fetchReaderList = async () => {
 	try {
 		const readStatusMap = [-1, 1, 0];
@@ -132,7 +163,7 @@ const fetchReaderList = async () => {
 			readStatus: readStatusMap[activeTab.value],
 			word: searchWord.value || ''
 		};
-		const res = await overviewApi.noticeDetail(params);
+		const res = await overviewApi.user_page(params);
 		if (res.data && res.data.records && res.data.records.length > 0 && res.data.records[0].userList) {
 			readerList.value = res.data.records[0].userList;
 		} else {
@@ -168,22 +199,13 @@ const handleBack = () => {
 };
 
 onMounted(() => {
-	const pages = getCurrentPages();
-	const currentPage = pages[pages.length - 1];
-	const itemStr = currentPage.options?.item;
-	const id = currentPage.options?.id;
-
-	if (itemStr) {
-		try {
-			const item = JSON.parse(decodeURIComponent(itemStr));
-			noticeItem.value = item;
-		} catch (e) {
-			console.error('解析详情数据失败:', e);
-		}
-	}
+	initSafeArea();
+});
 
-	if (id) {
-		noticeId.value = id;
+onLoad((options) => {
+	if (options && options.id) {
+		noticeId.value = options.id;
+		fetchNoticeInfo();
 		fetchReaderList();
 	}
 });
@@ -205,7 +227,9 @@ onMounted(() => {
 		display: flex;
 		align-items: center;
 		justify-content: space-between;
-		padding: 24rpx;
+		padding-left: 24rpx;
+		padding-right: 24rpx;
+		padding-bottom: 24rpx;
 		background-color: #FFFFFF;
 		border-bottom: 2rpx solid #F3F3F3;
 
@@ -380,6 +404,8 @@ onMounted(() => {
 		.reader_list {
 			flex: 1;
 			overflow-y: auto;
+			display: flex;
+			flex-direction: column;
 			::-webkit-scrollbar {
 				display: none;
 			}
@@ -435,4 +461,4 @@ onMounted(() => {
 		}
 	}
 }
-</style>
+</style>

+ 25 - 18
pages/notice/publish/index.vue

@@ -1,8 +1,8 @@
 <template>
-	<view class="page_publish">
+	<view class="page_publish" >
 		<common-header :title="isEdit ? '编辑通知' : '发布通知'" @back="goBack"></common-header>
 
-		<scroll-view scroll-y class="publish_content">
+		<scroll-view scroll-y class="publish_content" :style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 'rpx' }">
 			<view class="form_item">
 				<view class="form_row form_row_type">
 					<text class="form_label">通知类型</text>
@@ -99,7 +99,7 @@
 			</view>
 		</scroll-view>
 
-		<view class="publish_footer">
+		<view class="publish_footer" :style="{ paddingBottom: safeAreaBottom + 24 + 'rpx' }">
 			<view class="confirm_btn" @click="handleSave">
 				<text class="btn_text">确定</text>
 			</view>
@@ -109,12 +109,15 @@
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue';
-import { onShow } from '@dcloudio/uni-app';
+import { onLoad, onShow } from '@dcloudio/uni-app';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue';
 import overviewApi from '@/reqApi/overview.js';
 import CustomRadio from '@/components/customRadio.vue';
 import CommonHeader from '@/components/common-header.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const showTypePicker = ref(false);
 const publishType = ref(0);
@@ -180,14 +183,16 @@ const fetchNoticeTypeList = async () => {
 };
 
 onMounted(() => {
-	fetchNoticeTypeList();
-	
-	const pages = getCurrentPages();
-	const currentPage = pages[pages.length - 1];
-	if (currentPage.options && currentPage.options.id) {
+	initSafeArea();
+});
+
+onLoad(async (options) => {
+	await fetchNoticeTypeList();
+
+	if (options && options.id) {
 		isEdit.value = true;
-		editId.value = currentPage.options.id;
-		fetchNoticeDetail(currentPage.options.id);
+		editId.value = options.id;
+		fetchNoticeDetail(options.id);
 	}
 });
 
@@ -205,6 +210,9 @@ const fetchNoticeDetail = async (id) => {
 			
 			const typeIndexVal = typeData.value.findIndex(item => item.id === data.typeId);
 			typeIndex.value = typeIndexVal >= 0 ? typeIndexVal : -1;
+			if (typeIndexVal >= 0) {
+				formData.noticeType = typeData.value[typeIndexVal].name;
+			}
 			
 			publishType.value = data.sendTimeType === 2 ? 1 : 0;
 			publishDateTime.value = data.releaseTime || getCurrentDateTime();
@@ -461,15 +469,13 @@ const handleSave = async () => {
 	background-color: #FFFFFF;
 	display: flex;
 	flex-direction: column;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
 }
 
 .publish_content {
 	flex: 1;
 	box-sizing: border-box;
-	padding-top: calc(120rpx + env(safe-area-inset-top));
-	padding-bottom: 158rpx;
+	padding-top: 36rpx;
 	padding-left: 24rpx;
 	padding-right: 24rpx;
 }
@@ -519,14 +525,16 @@ const handleSave = async () => {
 			line-height: 72rpx;
 			background-color: #FFFFFF;
 			border-radius: 8rpx;
-			width: 240rpx;
+			width: 320rpx;
 			border: 2rpx solid #DCDFE6;
 
 			.filter_text {
 				font-size: 28rpx;
 				color: #333333;
 				margin-right: 8rpx;
-
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
 				&.placeholder {
 					color: #909399;
 				}
@@ -701,7 +709,6 @@ const handleSave = async () => {
 
 .publish_footer {
 	padding: 24rpx;
-	padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
 	background-color: #FFFFFF;
 
 	.confirm_btn {
@@ -718,4 +725,4 @@ const handleSave = async () => {
 		}
 	}
 }
-</style>
+</style>

+ 17 - 3
pages/notice/publish/personList.vue

@@ -2,9 +2,9 @@
 	<view class="page_person_list">
 		<CommonHeader :title="headerTitle" @back="goBack"></CommonHeader>
 
-		<SearchBar v-model="searchWord" @search="handleSearch" :isFixed="true" :top="114" :border="false"></SearchBar>
+		<SearchBar v-model="searchWord" @search="handleSearch" :isFixed="true" :top="searchBarTop + 'rpx'" :border="false"></SearchBar>
 
-		<scroll-view scroll-y class="person_list">
+		<scroll-view scroll-y class="person_list" :style="{ paddingTop: contentPaddingTop + 'rpx' }">
 			<view v-for="(person, index) in filteredList" :key="person.userId || person.id" class="person_item">
 				<view class="person_info">
 					<text class="person_name">{{ person.thirdName || person.name }}</text>
@@ -37,6 +37,16 @@ import { ref, computed, onMounted } from 'vue';
 import CommonHeader from '@/components/common-header.vue';
 import SearchBar from '@/components/search-bar.vue';
 import ConfirmModal from '@/components/confirm-modal.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, initSafeArea } = useSafeArea();
+
+const HEADER_CONTENT_HEIGHT = 174;
+const SEARCHBAR_HEIGHT = 60;
+const SPACING = 24;
+
+const searchBarTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT);
+const contentPaddingTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + SEARCHBAR_HEIGHT + SPACING);
 
 const searchWord = ref('');
 
@@ -95,6 +105,8 @@ const goBack = () => {
 };
 
 onMounted(() => {
+	initSafeArea();
+
 	const data = uni.getStorageSync('personListData');
 	if (data) {
 		try {
@@ -124,7 +136,9 @@ onMounted(() => {
 
 .person_list {
 	flex: 1;
-	padding: 222rpx 24rpx 24rpx 24rpx;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+	padding-bottom: 24rpx;
 	box-sizing: border-box;
 }
 

+ 18 - 7
pages/notice/publish/selectObject.vue

@@ -2,16 +2,16 @@
 	<view class="page_select_object">
 		<CommonHeader title="选择通知对象" @back="goBack"></CommonHeader>
 
-		<view class="tabs_container">
+		<view class="tabs_container" :style="{ top: tabsTop + 'rpx' }">
 			<view v-for="tab in tabs" :key="tab.key" class="tab_item" :class="{ active: activeTab === tab.key, disabled: editMode }"
 				@click="!editMode && handleTabChange(tab.key)">
 				<text class="tab_text">{{ tab.label }}</text>
 			</view>
 		</view>
 
-		<SearchBar v-model="searchKeyword" @search="searchTree" :border="true" :isFixed="true" :top="212" :zIndex="100"></SearchBar>
+		<SearchBar v-model="searchKeyword" @search="searchTree" :border="true" :isFixed="true" :top="searchBarTop + 'rpx'" :zIndex="100"></SearchBar>
 
-		<scroll-view scroll-y class="content_list">
+		<scroll-view scroll-y class="content_list" :style="{ paddingTop: contentPaddingTop + 'rpx' }">
 			<TreeNode :tree-data="treeData" :expanded-ids="expandedIds" @toggle-expand="toggleExpand"
 				@toggle-select="toggleSelect" />
 		</scroll-view>
@@ -26,11 +26,22 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
-import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import TreeNode from '@/components/tree-node.vue';
 import SearchBar from '@/components/search-bar.vue';
 import CommonHeader from '@/components/common-header.vue';
 import overview from '@/reqApi/overview.js';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, initSafeArea } = useSafeArea();
+
+const HEADER_CONTENT_HEIGHT = 130;
+const TABS_HEIGHT = 120;
+const SEARCHBAR_HEIGHT = 84;
+const SPACING = 16;
+
+const tabsTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT);
+const searchBarTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + TABS_HEIGHT);
+const contentPaddingTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + TABS_HEIGHT + SEARCHBAR_HEIGHT + SPACING);
 
 const activeTab = ref('department');
 const searchKeyword = ref('');
@@ -587,6 +598,8 @@ const setSelectedUsersById = (userIds) => {
 };
 
 onMounted(async () => {
+	initSafeArea();
+
 	const pages = getCurrentPages();
 	if (pages.length === 1) {
 		uni.redirectTo({ url: '/pages/notice/publish/index' });
@@ -664,7 +677,6 @@ onMounted(async () => {
 
 .tabs_container {
 	position: fixed;
-	top: 96rpx;
 	left: 0;
 	right: 0;
 	z-index: 99;
@@ -708,7 +720,7 @@ onMounted(async () => {
 
 .content_list {
 	flex: 1;
-	padding: 330rpx 0 150rpx 0;
+	padding-bottom: 150rpx;
 }
 
 .select_footer {
@@ -717,7 +729,6 @@ onMounted(async () => {
 	left: 0;
 	right: 0;
 	padding: 24rpx;
-	padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
 	background-color: #FFFFFF;
 	border-top: 2rpx solid #F3F3F3;
 

+ 12 - 31
pages/todo/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="page_todo">
+	<view class="page_todo" :style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
 		<page-header></page-header>
 		<view class="todo_content">
 			<!-- 待办事项 -->
@@ -7,7 +7,7 @@
 				<text class="todo_title">待办事项({{ todoList.length }}项)</text>
 			</view>
 			<!-- 待办事项列表 -->
-			<view class="todo_list">
+			<view class="todo_list" v-if="todoList.length > 0">
 				<view v-for="item in todoList" :key="item.id" class="todo_item">
 					<view class="todo_left">
 						<view class="todo_info">
@@ -37,10 +37,7 @@
 					</view>
 				</view>
 			</view>
-			<view v-if="todoList.length === 0" class="schedule_empty">
-				<image src="@/static/image/bg/no_data.png" class="empty_image" mode="aspectFit"></image>
-				<text class="empty_text">暂无待办事项</text>
-			</view>
+			<no-data v-if="todoList.length === 0" centered text="暂无待办事项"></no-data>
 		</view>
 		<common-tabbar></common-tabbar>
 	</view>
@@ -51,8 +48,12 @@
 	import { onShow } from '@dcloudio/uni-app';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 	import PageHeader from '@/components/page-header.vue';
+	import CommonTabbar from '@/components/commonTabbar.vue';
+	import NoData from '@/components/no-data.vue';
 	import workspace from '@/reqApi/workspace.js';
+	import { useSafeArea } from '@/common/safeArea';
 
+	const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 	const todoList = ref([]);
 
 	const getTagStyle = (status) => {
@@ -77,6 +78,10 @@
 		}
 	};
 
+	onMounted(() => {
+		initSafeArea();
+	});
+
 	onShow(() => {
 		fetchTodoList();
 	});
@@ -84,11 +89,10 @@
 
 <style lang="scss">
 .page_todo {
-	height: 100vh;
+	min-height: 100vh;
 	display: flex;
 	flex-direction: column;
 	background-color: #FFFFFF;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
 }
 
@@ -97,8 +101,6 @@
 	display: flex;
 	flex-direction: column;
 	overflow: hidden;
-	padding-top: calc(140rpx + env(safe-area-inset-top));
-	padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
 	padding-left: 0rpx;
 	padding-right: 0rpx;
 }
@@ -222,25 +224,4 @@
 		}
 	}
 }
-
-.schedule_empty {
-	display: flex;
-	flex-direction: column;
-	align-items: center;
-	font-size: 28rpx;
-	color: #999999;
-	position: relative;
-
-	.empty_image {
-		width: 400rpx;
-		height: 400rpx;
-	}
-
-	.empty_text {
-		position: absolute;
-		bottom: 15%;
-		left: 50%;
-		transform: translate(-50%, -50%);
-	}
-}
 </style>

+ 67 - 80
pages/workspace/index.vue

@@ -1,52 +1,39 @@
 <template>
-	<view class="page_workspace">
+	<view class="page_workspace"
+		:style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
 		<page-header></page-header>
 		<scroll-view scroll-y class="workspace_content">
 			<!-- 系统列表 -->
 			<view class="system_list">
-				<view class="section_title">系统列表</view>
-				
 				<template v-if="largeCard?.visible">
 					<view class="system_grid_with_large">
 						<view class="large_card" @click="handleSystemClick(largeCard)">
 							<image :src="largeCard.image" class="large_icon" mode="aspectFit"></image>
 							<text class="large_name">{{ largeCard.name }}</text>
 						</view>
-						
+
 						<view class="right_top_grid">
-							<view 
-								v-for="(item, index) in rightTopCards" 
-								:key="index" 
-								class="small_item"
-								@click="handleSystemClick(item)"
-							>
+							<view v-for="(item, index) in rightTopCards" :key="index" class="small_item"
+								@click="handleSystemClick(item)">
 								<image :src="item.image" class="small_icon" mode="aspectFit"></image>
 								<text class="small_name">{{ item.name }}</text>
 							</view>
 						</view>
 					</view>
-					
+
 					<view class="bottom_grid">
-						<view 
-							v-for="(item, index) in bottomCards" 
-							:key="index" 
-							class="small_item"
-							@click="handleSystemClick(item)"
-						>
+						<view v-for="(item, index) in bottomCards" :key="index" class="small_item"
+							@click="handleSystemClick(item)">
 							<image :src="item.image" class="small_icon" mode="aspectFit"></image>
 							<text class="small_name">{{ item.name }}</text>
 						</view>
 					</view>
 				</template>
-				
+
 				<template v-else>
 					<view class="system_grid_full">
-						<view 
-							v-for="(item, index) in allSmallCards" 
-							:key="index" 
-							class="small_item"
-							@click="handleSystemClick(item)"
-						>
+						<view v-for="(item, index) in allSmallCards" :key="index" class="small_item"
+							@click="handleSystemClick(item)">
 							<image :src="item.image" class="small_icon" mode="aspectFit"></image>
 							<text class="small_name">{{ item.name }}</text>
 						</view>
@@ -95,7 +82,8 @@
 					</view>
 					<view class="calendar_days">
 						<view v-for="day in calendarDays" :key="day.fullDate" class="day_item"
-							:class="{ active: day.isActive, weekend: day.isWeekend, today: day.isToday }" @click="handleDayClick(day)">
+							:class="{ active: day.isActive, weekend: day.isWeekend, today: day.isToday }"
+							@click="handleDayClick(day)">
 							<text class="day_text">{{ day.date }}</text>
 							<view class="schedule_dot" :class="{ visible: day.hasSchedule }"></view>
 						</view>
@@ -105,20 +93,11 @@
 				<view class="schedule_list">
 					<view class="schedule_title">当日日程</view>
 					<no-data v-if="scheduleList.length === 0" text="暂无日程安排" />
-					<view 
-						v-else 
-						v-for="item in scheduleList" 
-						:key="item.id" 
-						class="schedule_item"
-					>
+					<view v-else v-for="item in scheduleList" :key="item.id" class="schedule_item">
 						<view class="schedule_dot"></view>
 						<view class="schedule_text_container">
-							<text class="schedule_text" :class="{ expanded: expandedItems[item.id] }">{{ item.title }}</text>
-							<text 
-								v-if="item.title.length > 30" 
-								class="toggle_text" 
-								@click.stop="toggleExpand(item.id)"
-							>{{ expandedItems[item.id] ? '收起' : '更多' }}</text>
+							<text class="schedule_text" :class="{ expanded: expandedItems[item.id] }">{{ item.title
+								}}</text>
 						</view>
 					</view>
 				</view>
@@ -138,13 +117,10 @@
 					</view>
 				</view>
 				<view class="modal-body">
-					<text class="modal-date">{{ currentYear }}-{{ String(currentMonth).padStart(2, '0') }}-{{ String(currentDay).padStart(2, '0') }}日程</text>
-					<textarea 
-						class="schedule-textarea" 
-						v-model="scheduleContent" 
-						placeholder="请输入日程内容"
-						placeholder-class="textarea-placeholder"
-					></textarea>
+					<text class="modal-date">{{ currentYear }}-{{ String(currentMonth).padStart(2, '0') }}-{{
+						String(currentDay).padStart(2, '0') }}日程</text>
+					<textarea class="schedule-textarea" v-model="scheduleContent" placeholder="请输入日程内容"
+						placeholder-class="textarea-placeholder"></textarea>
 				</view>
 				<view class="modal-footer">
 					<view class="modal-btn cancel-btn" @click="handleCloseModal">
@@ -167,24 +143,27 @@ import { useStore } from 'vuex';
 import workspaceApi from '@/reqApi/workspace.js';
 import PageHeader from '@/components/page-header.vue';
 import NoData from '@/components/no-data.vue';
+import CommonTabbar from '@/components/commonTabbar.vue';
+import { useSafeArea } from '@/common/safeArea';
 
 const store = useStore();
 const schoolName = ref('');
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const systemItems = ref([
-	{ name: '大数据精准教学诊断平台', key:'', image: '/static/image/workspace/iconLeft.png', isLarge: true, visible: true },
-	{ name: '选择题判分', key:'', image: '/static/image/workspace/icon1.png', isLarge: false, visible: true },
-	{ name: '材料采集', key:'', image: '/static/image/workspace/icon2.png', isLarge: false, visible: true },
-	{ name: '教师研修', key:'teacherStudy', image: '/static/image/workspace/icon3.png', isLarge: false, visible: true },
-	{ name: '学生学习', key:'studentStudy', image: '/static/image/workspace/icon4.png', isLarge: false, visible: true },
-	{ name: '智能选课', key:'', image: '/static/image/workspace/icon5.png', isLarge: false, visible: true },
-	{ name: '教师发展', key:'teacherHonor', image: '/static/image/workspace/icon6.png', isLarge: false, visible: true },
-	{ name: '协同备课', key:'', image: '/static/image/workspace/icon7.png', isLarge: false, visible: true },
-	{ name: '校内通知', key:'notice', image: '/static/image/workspace/icon8.png', isLarge: false, visible: true },
-	{ name: '评教评学', key:'', image: '/static/image/workspace/icon9.png', isLarge: false, visible: true },
-	{ name: '考场编排', key:'', image: '/static/image/workspace/icon10.png', isLarge: false, visible: true },
-	{ name: '听课评课', key:'', image: '/static/image/workspace/icon11.png', isLarge: false, visible: true },
-	{ name: '基础数据', key:'', image: '/static/image/workspace/icon12.png', isLarge: false, visible: true }
+	{ name: '大数据精准教学诊断平台', key: '', image: '/static/image/workspace/iconLeft.png', isLarge: true, visible: true },
+	{ name: '选择题判分', key: '', image: '/static/image/workspace/icon1.png', isLarge: false, visible: true },
+	{ name: '材料采集', key: 'materialCollection', image: '/static/image/workspace/icon2.png', isLarge: false, visible: true },
+	{ name: '教师研修', key: 'teacherStudy', image: '/static/image/workspace/icon3.png', isLarge: false, visible: true },
+	{ name: '学生学习', key: 'studentStudy', image: '/static/image/workspace/icon4.png', isLarge: false, visible: true },
+	{ name: '智能选课', key: '', image: '/static/image/workspace/icon5.png', isLarge: false, visible: true },
+	{ name: '教师发展', key: '', image: '/static/image/workspace/icon6.png', isLarge: false, visible: true },
+	{ name: '协同备课', key: '', image: '/static/image/workspace/icon7.png', isLarge: false, visible: true },
+	{ name: '校内通知', key: 'notice', image: '/static/image/workspace/icon8.png', isLarge: false, visible: true },
+	{ name: '评教评学', key: '', image: '/static/image/workspace/icon9.png', isLarge: false, visible: true },
+	{ name: '考场编排', key: '', image: '/static/image/workspace/icon10.png', isLarge: false, visible: true },
+	{ name: '听课评课', key: '', image: '/static/image/workspace/icon11.png', isLarge: false, visible: true },
+	{ name: '基础数据', key: '', image: '/static/image/workspace/icon12.png', isLarge: false, visible: true }
 ]);
 
 const largeCard = computed(() => systemItems.value.find(item => item.isLarge));
@@ -262,12 +241,12 @@ const getWeekRange = (date) => {
 const updateCalendarDays = () => {
 	const days = [];
 	const { monday } = getWeekRange(new Date(currentYear.value, currentMonth.value - 1, currentDay.value));
-	
+
 	weekStartDate.value = monday;
 	const sunday = new Date(monday);
 	sunday.setDate(monday.getDate() + 6);
 	weekEndDate.value = sunday;
-	
+
 	for (let i = 0; i < 7; i++) {
 		const date = new Date(monday);
 		date.setDate(monday.getDate() + i);
@@ -291,7 +270,7 @@ const fetchScheduleList = async () => {
 	const { monday, sunday } = getWeekRange(new Date(currentYear.value, currentMonth.value - 1, currentDay.value));
 	const startDate = `${monday.getFullYear()}-${String(monday.getMonth() + 1).padStart(2, '0')}-${String(monday.getDate()).padStart(2, '0')}`;
 	const endDate = `${sunday.getFullYear()}-${String(sunday.getMonth() + 1).padStart(2, '0')}-${String(sunday.getDate()).padStart(2, '0')}`;
-	
+
 	isLoading.value = true;
 	try {
 		const res = await workspaceApi.getSchedule({ startDate, endDate });
@@ -324,6 +303,7 @@ const updateYearMonthIndex = () => {
 };
 
 onMounted(() => {
+	initSafeArea();
 	schoolName.value = store.state.userStore.userInfo.schoolName || '';
 	generateYearList();
 	generateMonthList();
@@ -340,7 +320,7 @@ const handleSystemClick = (item) => {
 		const userInfo = store.state.userStore.userInfo || uni.getStorageSync('userInfo') || {};
 		const roleCodes = userInfo.roleCodes || [];
 		const isSchoolManager = Array.isArray(roleCodes) && roleCodes.includes('SCHOOL_MANAGER');
-		
+
 		if (isSchoolManager) {
 			uni.navigateTo({
 				url: '/pages/notice/overview/index'
@@ -350,9 +330,17 @@ const handleSystemClick = (item) => {
 				url: '/pages/notice/mine/index?hideTabbar=true'
 			});
 		}
-	}else if(item.key == 'teacherStudy' || item.key == 'studentStudy' || item.key == 'teacherHonor'){//教师研修 学生学习 教师发展
+	} else if (item.key === 'materialCollection') {
 		uni.navigateTo({
-			url: `/pages/${item.key}/index`
+			url: '/pages/materialCollection/taskOverview/index'
+		});
+	} else if (item.key == 'teacherStudy') {//教师研修
+		uni.navigateTo({
+			url: '/pages/teacherStudy/index'
+		});
+	} else if (item.key == 'studentStudy') {//学生学习
+		uni.navigateTo({
+			url: '/pages/studentStudy/index'
 		});
 	} else {
 		uni.showToast({ title: `即将打开:${item.name}`, icon: 'none' });
@@ -360,7 +348,7 @@ const handleSystemClick = (item) => {
 };
 
 const handleViewSchedule = () => {
-	uni.navigateTo({	
+	uni.navigateTo({
 		url: '/pages/workspace/schedule'
 	});
 };
@@ -508,16 +496,13 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 .page_workspace {
 	min-height: 100vh;
 	background-color: #F8F9FD;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
 }
 
 .workspace_content {
-	padding-top: calc(140rpx + env(safe-area-inset-top));
-	padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
 	padding-left: 24rpx;
 	padding-right: 24rpx;
-	height: 100vh;
+	height: 100%;
 	box-sizing: border-box;
 	overflow: hidden;
 }
@@ -790,7 +775,7 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 					height: 8rpx;
 					background-color: transparent;
 					border-radius: 50%;
-					
+
 					&.visible {
 						background-color: #2E64FA;
 					}
@@ -804,6 +789,7 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 		background: #F9FAFF;
 		border-radius: 20rpx 20rpx 20rpx 20rpx;
 		padding: $spacing-lg;
+
 		.schedule_title {
 			font-size: 28rpx;
 			font-weight: 500;
@@ -841,24 +827,23 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 				color: #666666;
 				line-height: 1.6;
 				display: -webkit-box;
-				-webkit-box-orient: vertical;
-				-webkit-line-clamp: 2;
-				overflow: hidden;
-				text-overflow: ellipsis;
-
+				flex: 1;
+				text-align: justify;
 				&.expanded {
 					-webkit-line-clamp: unset;
 					overflow: visible;
 					text-overflow: unset;
+					padding-right: 0;
 				}
 			}
 
 			.toggle_text {
-				font-size: 24rpx;
+				font-size: 28rpx;
 				color: #2E64FA;
-				float: right;
-				margin-top: 14rpx;
-				display: inline;
+				position: absolute;
+				right: 0;
+				bottom: 0;
+				line-height: 1.6;
 			}
 		}
 	}
@@ -868,7 +853,8 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 		gap: 16rpx;
 		height: 56rpx;
 		background-color: #FFFFFF;
-		border-radius: 12rpx 12rpx 12rpx 12rpx;;
+		border-radius: 12rpx 12rpx 12rpx 12rpx;
+		;
 		border: 1px solid #2E64FA;
 
 		.btn_text {
@@ -964,6 +950,7 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 		padding: 28rpx 40rpx;
 		gap: 16rpx;
 		border-top: 2rpx solid #EBEEF5;
+
 		.modal-btn {
 			height: 72rpx;
 			// padding: 0 48rpx;
@@ -990,4 +977,4 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 		}
 	}
 }
-</style>
+</style>

+ 33 - 55
pages/workspace/schedule.vue

@@ -1,13 +1,14 @@
 <template>
-	<view class="page">
+	<view class="page" :style="{ paddingTop: statusBarHeight + 140 + 'rpx', paddingBottom: safeAreaBottom + 'rpx' }">
 		<!-- 导航栏 -->
-		<view class="nav_bar">
+		 <common-header :title="'校历安排'" @back="handleBack"></common-header>
+		<!-- <view class="nav_bar" :style="{ paddingTop: statusBarHeight + 24 + 'rpx' }">
 			<view class="nav_back" @click="handleBack">
 				<uni-icons type="back" size="26" color="#333333"></uni-icons>
 			</view>
 			<text class="nav_title">校历安排</text>
 			<view class="nav_right"></view>
-		</view>
+		</view> -->
 		<!-- 日历 -->
 		<view class="calendar_container">
 			<view class="calendar_header">
@@ -44,26 +45,19 @@
 			<view class="schedule_header">
 				<text class="schedule_date">{{ selectedDate }}</text>
 				<view class="add_btn" @click="handleAddSchedule">
+					<!-- <uni-icons type="‌plus-filled" size="24" color="#fff"></uni-icons> -->
 					<image src="/static/image/workspace/add.png" class="add_icon" mode="aspectFit"></image>
 					<text>新增日程</text>
 				</view>
 			</view>
 
-			<view v-if="currentScheduleList.length === 0" class="schedule_empty">
-				<image src="@/static/image/bg/no_data.png" class="empty_image" mode="aspectFit"></image>
-				<text class="empty_text">暂无日程安排</text>
-			</view>
+			<no-data v-if="currentScheduleList.length === 0" centered text="暂无日程安排"></no-data>
 
 			<view v-else class="schedule_list">
 				<view v-for="item in currentScheduleList" :key="item.id" class="schedule_item">
 					<view class="schedule_dot"></view>
 					<view class="schedule_text_container">
 						<text class="schedule_text" :class="{ expanded: expandedItems[item.id] }">{{ item.title }}</text>
-						<text 
-							v-if="item.title.length > 30" 
-							class="toggle_text" 
-							@click.stop="toggleExpand(item.id)"
-						>{{ expandedItems[item.id] ? '收起' : '更多' }}</text>
 					</view>
 					<view class="schedule_actions">
 						<text class="action_btn delete" @click="handleDelete(item.id)">删除</text>
@@ -101,6 +95,12 @@
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue';
 import workspaceApi from '@/reqApi/workspace';
+import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
+import { useSafeArea } from '@/common/safeArea';
+import commonHeader from '@/components/common-header.vue';
+import NoData from '@/components/no-data.vue';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 interface CalendarDay {
 	date: number;
@@ -259,7 +259,7 @@ const handleDateChange = (e: { detail: { value: string } }) => {
 };
 
 const handleBack = () => {
-	uni.switchTab({
+	uni.redirectTo({
 		url: '/pages/workspace/index'
 	});
 };
@@ -331,16 +331,18 @@ const handleConfirmSchedule = async () => {
 };
 
 onMounted(() => {
+	initSafeArea();
 	fetchScheduleList();
 });
 </script>
 
 <style lang="scss" scoped>
 .page {
-	height: 100vh;
+	min-height: 100vh;
 	background-color: #F5F7FA;
-	padding-bottom: env(safe-area-inset-bottom);
 	box-sizing: border-box;
+	display: flex;
+	flex-direction: column;
 }
 
 .nav_bar {
@@ -348,7 +350,6 @@ onMounted(() => {
 	display: flex;
 	align-items: center;
 	justify-content: space-between;
-	padding-top: calc(24rpx + env(safe-area-inset-top));
 	padding-bottom: 24rpx;
 	padding-left: 24rpx;
 	padding-right: 24rpx;
@@ -378,6 +379,7 @@ onMounted(() => {
 	margin: 16rpx;
 	border-radius: 20rpx;
 	padding: 16rpx;
+	flex-shrink: 0;
 
 	.calendar_header {
 		display: flex;
@@ -476,7 +478,7 @@ onMounted(() => {
 			}
 
 			&.today:not(.active) .day_num {
-				border-color: #2E64FA;
+				color: #2E64FA;
 			}
 		}
 	}
@@ -487,6 +489,10 @@ onMounted(() => {
 	margin: 0 16rpx 16rpx;
 	border-radius: 16rpx;
 	padding: 24rpx;
+	display: flex;
+	flex-direction: column;
+	flex: 1;
+	overflow: hidden;
 
 	.schedule_header {
 		display: flex;
@@ -495,6 +501,7 @@ onMounted(() => {
 		margin-bottom: 24rpx;
 		padding-bottom: 32rpx;
 		border-bottom: 2rpx solid #EBEEF5;
+		flex-shrink: 0;
 
 		.schedule_date {
 			font-size: 36rpx;
@@ -519,31 +526,12 @@ onMounted(() => {
 		}
 	}
 
-	.schedule_empty {
-		display: flex;
-		flex-direction: column;
-		align-items: center;
-		font-size: 28rpx;
-		color: #999999;
-		position: relative;
-
-		.empty_image {
-			width: 400rpx;
-			height: 400rpx;
-		}
-
-		.empty_text {
-			position: absolute;
-			bottom: 15%;
-			left: 50%;
-			transform: translate(-50%, -50%);
-		}
-	}
-
 	.schedule_list {
+		flex: 1;
+		overflow-y: auto;
 		.schedule_item {
 			display: flex;
-			align-items: center;
+			align-items: flex-start;
 			padding: 20rpx 0;
 			border-bottom: 2rpx solid #EBEEF5;
 
@@ -558,6 +546,7 @@ onMounted(() => {
 				border-radius: 50%;
 				margin-right: 16rpx;
 				flex-shrink: 0;
+				margin-top:16rpx
 			}
 
 			.schedule_text_container {
@@ -570,35 +559,24 @@ onMounted(() => {
 				color: #666666;
 				line-height: 1.6;
 				display: -webkit-box;
-				-webkit-box-orient: vertical;
-				-webkit-line-clamp: 2;
-				overflow: hidden;
-				text-overflow: ellipsis;
-
+				text-align: justify;
+				flex: 1;
 				&.expanded {
 					-webkit-line-clamp: unset;
 					overflow: visible;
 					text-overflow: unset;
+					padding-right: 0;
 				}
 			}
 
-			.toggle_text {
-				font-size: 24rpx;
-				color: #2E64FA;
-				position: absolute;
-				right: 0;
-				bottom: 0;
-				line-height: 1.6;
-				background-color: #FFFFFF;
-				padding-left: 10rpx;
-			}
 
 			.schedule_actions {
 				display: flex;
 				gap: 24rpx;
 				margin-left: 36rpx;
+				line-height: 1.6;
 				.action_btn {
-					font-size: 26rpx;
+					font-size: 28rpx;
 
 					&.delete {
 						color: #F56C6C;

+ 2 - 2
polyfill.js

@@ -1,5 +1,5 @@
-// #ifdef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
-// 小程序环境 Vue Devtools 兼容处理
+// #ifdef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-BAIDU || APP-PLUS
+// 小程序/App环境 Vue Devtools 兼容处理
 (function() {
   try {
     // Vue 内部 getDevtoolsGlobalHook 会获取全局对象

BIN
static/image/materialCollection/shenhe.png


BIN
static/image/materialCollection/shenheIcon.png


BIN
static/image/materialCollection/tijiao.png


BIN
static/image/materialCollection/tijiaoIcon.png


BIN
static/image/workspace/add.png


+ 13 - 7
store/modules/user.js

@@ -21,11 +21,12 @@ const userStore = {
 		//退出登录
 		LOGOUT_USERINFO(state)
 		{
-			uni.clearStorageSync();
-			// state.userInfo={};
-			// state.token="";
-			// uni.setStorageSync('token',"");
-			// uni.setStorageSync('userInfo', {});
+			state.token = '';
+			state.userInfo = {};
+			state.schoolsList = '';
+			uni.removeStorageSync('token');
+			uni.removeStorageSync('userInfo');
+			uni.removeStorageSync('schoolsList');
 		},
 		//保存学校
 		SET_SCHOOLSLIST(state,list){
@@ -47,8 +48,13 @@ const userStore = {
 		//注销退出登录
 		async logout({commit},isOut){
 			if(isOut){
-				const res = await userApi.signOut();
-				if (res.code === 200) {
+				try {
+					const res = await userApi.signOut();
+					if (res.code === 200) {
+						commit('LOGOUT_USERINFO');
+					}
+				} catch (error) {
+					// signOut 失败(如token无效),直接清除本地数据
 					commit('LOGOUT_USERINFO');
 				}
 			}else{

+ 4 - 1
tsconfig.json

@@ -4,7 +4,7 @@
 		"useDefineForClassFields": true,
 		"module": "ESNext",
 		"moduleResolution": "bundler",
-		"strict": true,
+		"strict": false,
 		"jsx": "preserve",
 		"resolveJsonModule": true,
 		"isolatedModules": true,
@@ -12,12 +12,15 @@
 		"lib": ["ESNext", "DOM"],
 		"skipLibCheck": true,
 		"noEmit": true,
+		"allowJs": true,
+		"checkJs": false,
 		"paths": {
 			"@/*": ["./*"]
 		},
 		"types": ["@dcloudio/types"]
 	},
 	"include": [
+		"env.d.ts",
 		"src/**/*.ts",
 		"src/**/*.d.ts",
 		"src/**/*.tsx",

+ 1 - 17
uni_modules/uni-icons/components/uni-icons/uni-icons.vue

@@ -1,12 +1,5 @@
 <template>
-	<!-- #ifdef APP-NVUE -->
 	<text :style="styleObj" class="uni-icons" @click="_onClick">{{unicode}}</text>
-	<!-- #endif -->
-	<!-- #ifndef APP-NVUE -->
-	<text :style="styleObj" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick">
-		<slot></slot>
-	</text>
-	<!-- #endif -->
 </template>
 
 <script>
@@ -17,15 +10,6 @@
 		return (typeof val === 'number' || reg.test(val)) ? val + 'px' : val;
 	}
 
-	// #ifdef APP-NVUE
-	var domModule = weex.requireModule('dom');
-	import iconUrl from './uniicons.ttf'
-	domModule.addRule('fontFace', {
-		'fontFamily': "uniicons",
-		'src': "url('" + iconUrl + "')"
-	});
-	// #endif
-
 	/**
 	 * Icons 图标
 	 * @description 用于展示 icons 图标
@@ -107,4 +91,4 @@
 		text-decoration: none;
 		text-align: center;
 	}
-</style>
+</style>

+ 0 - 4
uni_modules/uni-popup/components/uni-popup/uni-popup.vue

@@ -490,10 +490,6 @@
 			position: relative;
 
 			/* iphonex 等安全区设置,底部安全区适配 */
-			/* #ifndef APP-NVUE */
-			// padding-bottom: constant(safe-area-inset-bottom);
-			// padding-bottom: env(safe-area-inset-bottom);
-			/* #endif */
 			&.left,
 			&.right {
 				/* #ifdef H5 */

+ 1 - 1
unpackage/cache/certdata

@@ -1,4 +1,4 @@
-andrCertfile=C:/jdk-21/bin/workApp.keystore
+andrCertfile=D:/Hbuild X/HBuilderX/bin/workApp.keystore
 andrCertAlias=hjyWorkAppkey
 andrCertPass=l652FwqegF0KViqLH3Mk+w==
 storePassword=l652FwqegF0KViqLH3Mk+w==

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 1
unpackage/cache/wgt/__UNI__07352BD/app-config-service.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/app-service.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/app.css


BIN
unpackage/cache/wgt/__UNI__07352BD/assets/uvicons.04d281cc.ttf


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/manifest.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/login/agreement.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/login/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/materialCollection/reviewMaterial/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/materialCollection/submitMaterial/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/materialCollection/taskOverview/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/message/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/my/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/my/updatePassword.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/mine/detail.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/mine/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/mine/preview.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/overview/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/overview/noticeDetail.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/publish/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/publish/personList.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/notice/publish/selectObject.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/studentStudy/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/studentStudy/learningMonitor/taskDetails.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/studentStudy/videoDetails.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/teacherHonor/honorOverview/honorTypeDetail.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/teacherHonor/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/teacherStudy/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/teacherStudy/learningMonitor/taskDetails.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/teacherStudy/videoDetails.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/todo/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/workspace/index.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
unpackage/cache/wgt/__UNI__07352BD/pages/workspace/schedule.css


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/icon/delete.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/icon/edit.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/icon/honor_cup.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/icon/honor_seal.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorAudit.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorAudit_cur.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorOverview.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorOverview_cur.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorTeacher.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/honorTeacher_cur.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/myHonor.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/tabbar/myHonor_cur.png


BIN
unpackage/cache/wgt/__UNI__07352BD/static/image/workspace/add.png


+ 0 - 15
unpackage/dist/cache/.vite/deps/_metadata.json

@@ -1,15 +0,0 @@
-{
-  "hash": "af6e7a40",
-  "configHash": "89b37c30",
-  "lockfileHash": "65c58edb",
-  "browserHash": "c99899b1",
-  "optimized": {
-    "jsencrypt": {
-      "src": "../../../../../node_modules/jsencrypt/lib/index.js",
-      "file": "jsencrypt.js",
-      "fileHash": "d7735089",
-      "needsInterop": false
-    }
-  },
-  "chunks": {}
-}

+ 0 - 3
unpackage/dist/cache/.vite/deps/package.json

@@ -1,3 +0,0 @@
-{
-  "type": "module"
-}

BIN
unpackage/release/apk/__UNI__07352BD__20260727152326.apk → unpackage/release/apk/__UNI__07352BD__20260729160304.apk


+ 4 - 2
vite.config.js

@@ -3,9 +3,11 @@ import {
 } from 'vite'
 import uni from '@dcloudio/vite-plugin-uni'
 import path from 'path'
-const define = {}
+const define = {
+	__VUE_PROD_DEVTOOLS__: 'false'
+}
 if (!["mp-weixin", "h5", "web"].includes(process.env.UNI_PLATFORM)) {
-	define['global'] = null
+	define['global'] = '{}'
 	define['wx'] = 'uni'
 }
 export default defineConfig({

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.