liurongli vor 3 Wochen
Ursprung
Commit
dafc0285a1
100 geänderte Dateien mit 2250 neuen und 315 gelöschten Zeilen
  1. 59 0
      components/common-header.vue
  2. 177 0
      components/confirm-modal.vue
  3. 1 2
      components/notice-header.vue
  4. 101 0
      components/search-bar.vue
  5. 1 1
      manifest.json
  6. 715 2
      package-lock.json
  7. 3 1
      package.json
  8. 29 0
      pages.json
  9. 14 2
      pages/message/index.vue
  10. 305 0
      pages/notice/mine/detail.vue
  11. 106 76
      pages/notice/mine/index.vue
  12. 145 0
      pages/notice/mine/preview.vue
  13. 54 28
      pages/notice/overview/index.vue
  14. 69 39
      pages/notice/publish/index.vue
  15. 172 0
      pages/notice/publish/personList.vue
  16. 68 100
      pages/notice/publish/selectObject.vue
  17. 33 24
      pages/todo/index.vue
  18. 195 40
      pages/workspace/index.vue
  19. BIN
      static/image/workspace/icon/icon1.png
  20. BIN
      static/image/workspace/icon/icon10.png
  21. BIN
      static/image/workspace/icon/icon2.png
  22. BIN
      static/image/workspace/icon/icon3.png
  23. BIN
      static/image/workspace/icon/icon4.png
  24. BIN
      static/image/workspace/icon/icon5.png
  25. BIN
      static/image/workspace/icon/icon6.png
  26. BIN
      static/image/workspace/icon/icon7.png
  27. BIN
      static/image/workspace/icon/icon8.png
  28. BIN
      static/image/workspace/icon/icon9.png
  29. BIN
      static/image/workspace/icon1.png
  30. BIN
      static/image/workspace/icon10.png
  31. BIN
      static/image/workspace/icon11.png
  32. BIN
      static/image/workspace/icon12.png
  33. BIN
      static/image/workspace/icon2.png
  34. BIN
      static/image/workspace/icon3.png
  35. BIN
      static/image/workspace/icon4.png
  36. BIN
      static/image/workspace/icon5.png
  37. BIN
      static/image/workspace/icon6.png
  38. BIN
      static/image/workspace/icon7.png
  39. BIN
      static/image/workspace/icon8.png
  40. BIN
      static/image/workspace/icon9.png
  41. BIN
      static/image/workspace/iconLeft.png
  42. BIN
      static/pdfh5/cmaps/78-EUC-H.bcmap
  43. BIN
      static/pdfh5/cmaps/78-EUC-V.bcmap
  44. BIN
      static/pdfh5/cmaps/78-H.bcmap
  45. BIN
      static/pdfh5/cmaps/78-RKSJ-H.bcmap
  46. BIN
      static/pdfh5/cmaps/78-RKSJ-V.bcmap
  47. BIN
      static/pdfh5/cmaps/78-V.bcmap
  48. BIN
      static/pdfh5/cmaps/78ms-RKSJ-H.bcmap
  49. BIN
      static/pdfh5/cmaps/78ms-RKSJ-V.bcmap
  50. BIN
      static/pdfh5/cmaps/83pv-RKSJ-H.bcmap
  51. BIN
      static/pdfh5/cmaps/90ms-RKSJ-H.bcmap
  52. BIN
      static/pdfh5/cmaps/90ms-RKSJ-V.bcmap
  53. BIN
      static/pdfh5/cmaps/90msp-RKSJ-H.bcmap
  54. BIN
      static/pdfh5/cmaps/90msp-RKSJ-V.bcmap
  55. BIN
      static/pdfh5/cmaps/90pv-RKSJ-H.bcmap
  56. BIN
      static/pdfh5/cmaps/90pv-RKSJ-V.bcmap
  57. BIN
      static/pdfh5/cmaps/Add-H.bcmap
  58. BIN
      static/pdfh5/cmaps/Add-RKSJ-H.bcmap
  59. BIN
      static/pdfh5/cmaps/Add-RKSJ-V.bcmap
  60. BIN
      static/pdfh5/cmaps/Add-V.bcmap
  61. BIN
      static/pdfh5/cmaps/Adobe-CNS1-0.bcmap
  62. BIN
      static/pdfh5/cmaps/Adobe-CNS1-1.bcmap
  63. BIN
      static/pdfh5/cmaps/Adobe-CNS1-2.bcmap
  64. BIN
      static/pdfh5/cmaps/Adobe-CNS1-3.bcmap
  65. BIN
      static/pdfh5/cmaps/Adobe-CNS1-4.bcmap
  66. BIN
      static/pdfh5/cmaps/Adobe-CNS1-5.bcmap
  67. BIN
      static/pdfh5/cmaps/Adobe-CNS1-6.bcmap
  68. BIN
      static/pdfh5/cmaps/Adobe-CNS1-UCS2.bcmap
  69. BIN
      static/pdfh5/cmaps/Adobe-GB1-0.bcmap
  70. BIN
      static/pdfh5/cmaps/Adobe-GB1-1.bcmap
  71. BIN
      static/pdfh5/cmaps/Adobe-GB1-2.bcmap
  72. BIN
      static/pdfh5/cmaps/Adobe-GB1-3.bcmap
  73. BIN
      static/pdfh5/cmaps/Adobe-GB1-4.bcmap
  74. BIN
      static/pdfh5/cmaps/Adobe-GB1-5.bcmap
  75. BIN
      static/pdfh5/cmaps/Adobe-GB1-UCS2.bcmap
  76. BIN
      static/pdfh5/cmaps/Adobe-Japan1-0.bcmap
  77. BIN
      static/pdfh5/cmaps/Adobe-Japan1-1.bcmap
  78. BIN
      static/pdfh5/cmaps/Adobe-Japan1-2.bcmap
  79. BIN
      static/pdfh5/cmaps/Adobe-Japan1-3.bcmap
  80. BIN
      static/pdfh5/cmaps/Adobe-Japan1-4.bcmap
  81. BIN
      static/pdfh5/cmaps/Adobe-Japan1-5.bcmap
  82. BIN
      static/pdfh5/cmaps/Adobe-Japan1-6.bcmap
  83. BIN
      static/pdfh5/cmaps/Adobe-Japan1-UCS2.bcmap
  84. BIN
      static/pdfh5/cmaps/Adobe-Korea1-0.bcmap
  85. BIN
      static/pdfh5/cmaps/Adobe-Korea1-1.bcmap
  86. BIN
      static/pdfh5/cmaps/Adobe-Korea1-2.bcmap
  87. BIN
      static/pdfh5/cmaps/Adobe-Korea1-UCS2.bcmap
  88. BIN
      static/pdfh5/cmaps/B5-H.bcmap
  89. BIN
      static/pdfh5/cmaps/B5-V.bcmap
  90. BIN
      static/pdfh5/cmaps/B5pc-H.bcmap
  91. BIN
      static/pdfh5/cmaps/B5pc-V.bcmap
  92. BIN
      static/pdfh5/cmaps/CNS-EUC-H.bcmap
  93. BIN
      static/pdfh5/cmaps/CNS-EUC-V.bcmap
  94. BIN
      static/pdfh5/cmaps/CNS1-H.bcmap
  95. BIN
      static/pdfh5/cmaps/CNS1-V.bcmap
  96. BIN
      static/pdfh5/cmaps/CNS2-H.bcmap
  97. 3 0
      static/pdfh5/cmaps/CNS2-V.bcmap
  98. BIN
      static/pdfh5/cmaps/ETHK-B5-H.bcmap
  99. BIN
      static/pdfh5/cmaps/ETHK-B5-V.bcmap
  100. BIN
      static/pdfh5/cmaps/ETen-B5-H.bcmap

+ 59 - 0
components/common-header.vue

@@ -0,0 +1,59 @@
+<template>
+	<view class="common_header">
+		<view class="header_left" @click="handleBack">
+			<uni-icons type="back" size="24" color="#333333"></uni-icons>
+		</view>
+		<text class="header_title">{{ title }}</text>
+		<view class="header_right">
+			<slot name="right"></slot>
+		</view>
+	</view>
+</template>
+
+<script setup>
+const props = defineProps({
+	title: {
+		type: String,
+		default: ''
+	}
+});
+
+const emit = defineEmits(['back']);
+
+const handleBack = () => {
+	emit('back');
+};
+</script>
+
+<style lang="scss" scoped>
+.common_header {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	z-index: 100;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	height: 96rpx;
+	padding: 0 24rpx;
+	background-color: #FFFFFF;
+	border-bottom: 2rpx solid #F3F3F3;
+
+	.header_left {
+		width: 48rpx;
+	}
+
+	.header_title {
+		flex: 1;
+		text-align: center;
+		font-weight: 500;
+		font-size: 32rpx;
+		color: #333333;
+	}
+
+	.header_right {
+		width: 48rpx;
+	}
+}
+</style>

+ 177 - 0
components/confirm-modal.vue

@@ -0,0 +1,177 @@
+<template>
+	<view v-if="visible" class="modal_mask" >
+		<view class="modal_container" @click.stop>
+			<view class="modal_header">
+				<view class="header_left">
+					<image src="/static/image/icon/icon_warn.png" class="warning_icon" mode="aspectFit"></image>
+					<text class="header_title">{{ title }}</text>
+				</view>
+				<view class="close_btn" @click="handleClose">
+					<image src="/static/image/icon/close.png" class="close_icon" mode="aspectFit"></image>
+				</view>
+			</view>
+			<view class="modal_content">
+				<text class="content_text">{{ content }}</text>
+			</view>
+			<view class="modal_footer">
+				<view class="btn cancel_btn" @click="handleCancel">
+					<text class="btn_text">{{ cancelText }}</text>
+				</view>
+				<view class="btn confirm_btn" @click="handleConfirm">
+					<text class="btn_text">{{ confirmText }}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+const props = defineProps({
+	visible: {
+		type: Boolean,
+		default: false
+	},
+	title: {
+		type: String,
+		default: '提示'
+	},
+	content: {
+		type: String,
+		default: ''
+	},
+	cancelText: {
+		type: String,
+		default: '取消'
+	},
+	confirmText: {
+		type: String,
+		default: '确定'
+	}
+});
+
+const emit = defineEmits(['close', 'cancel', 'confirm']);
+
+const handleClose = () => {
+	emit('close');
+	emit('cancel');
+};
+
+const handleCancel = () => {
+	emit('cancel');
+};
+
+const handleConfirm = () => {
+	emit('confirm');
+};
+</script>
+
+<style lang="scss" scoped>
+.modal_mask {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: rgba(0, 0, 0, 0.5);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	z-index: 1000;
+}
+
+.modal_container {
+	width: 90%;
+	background-color: #FFFFFF;
+	border-radius: 16rpx;
+	overflow: hidden;
+}
+
+.modal_header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	height: 96rpx;
+	padding: 0 40rpx;
+	background-color: #F5F7FA;
+
+	.header_left {
+		display: flex;
+		align-items: center;
+	}
+
+	.warning_icon {
+		width: 40rpx;
+		height: 40rpx;
+		margin-right: 16rpx;
+	}
+
+	.header_title {
+		font-weight: 500;
+		font-size: 32rpx;
+		color: #333333;
+	}
+
+	.close_btn {
+		width: 48rpx;
+		height: 48rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		.close_icon {
+			width: 48rpx;
+			height: 48rpx;
+		}
+	}
+}
+
+.modal_content {
+	height: 198rpx;
+	padding: 0 32rpx;
+	border-bottom: 2rpx solid #DCDFE6;
+	line-height: 198rpx;
+	.content_text {
+		font-size: 28rpx;
+		color: #000000;
+	}
+}
+
+.modal_footer {
+	display: flex;
+	padding: 28rpx 40rpx;
+	justify-content: space-around;
+	gap: 32rpx;
+	.btn {
+		height: 80rpx;
+		width: 280rpx;
+		border-radius: 8rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-left: 0;
+
+		.btn_text {
+			font-size: 28rpx;
+			font-weight: 500;
+		}
+	}
+
+	.cancel_btn {
+		background-color: #FFFFFF;
+		border: 2rpx solid #DCDFE6;
+
+		.btn_text {
+			color: #666666;
+		}
+	}
+
+	.confirm_btn {
+		background-color: #2E64FA;
+		border: 2rpx solid #2E64FA;
+
+		.btn_text {
+			color: #FFFFFF;
+		}
+	}
+}
+</style>

+ 1 - 2
components/notice-header.vue

@@ -244,9 +244,8 @@ const handleSearchInput = () => {
 
 	.tabs_bar {
 		display: flex;
-		padding-top: 4rpx;
 		height: 56rpx;
-		padding-left: 24rpx;
+		padding:4rpx 24rpx 24rpx 24rpx;
 		.tab_item {
 			font-size: 32rpx;
 			color: #999999;

+ 101 - 0
components/search-bar.vue

@@ -0,0 +1,101 @@
+<template>
+	<!-- 搜索栏 -->
+	<view class="search_container" :style="containerStyle">
+			<view class="search_box">
+				<image src="/static/image/icon/search.png" class="search_icon" mode="aspectFit"></image>
+				<input type="text" class="search_input" :value="modelValue" @input="handleInput"
+					placeholder="请输入关键字搜索" />
+			</view>
+		</view>
+
+</template>
+
+<script setup>
+import { computed } from 'vue';
+
+const props = defineProps({
+	modelValue: {
+		type: String,
+		default: ''
+	},
+	top: {
+		type: [String, Number],
+		default: ''
+	},
+	isFixed: {
+		type: Boolean,
+		default: false
+	},
+	zIndex: {
+		type: [String, Number],
+		default: '98'
+	},
+	border: {
+		type: Boolean,
+		default: true
+	}
+});
+
+const emit = defineEmits(['update:modelValue', 'search']);
+
+const containerStyle = computed(() => {
+	const style = {};
+	if (props.isFixed) {
+		style.position = 'fixed';
+		style.left = '0';
+		style.right = '0';
+		style.zIndex = props.zIndex;
+	}
+	if (props.top) {
+		style.top = typeof props.top === 'number' ? `${props.top}rpx` : props.top;
+	}
+	if (props.border) {
+		style.borderBottom = '2rpx solid #F3F3F3';
+	} else {
+		style.borderBottom = 'none';
+	}
+	return style;
+});
+
+const handleInput = (e) => {
+	const value = e.detail.value;
+	emit('update:modelValue', value);
+	emit('search', value);
+};
+</script>
+
+<style lang="scss">
+
+.search_container {
+	padding-bottom: 24rpx;
+	margin: 0 24rpx;
+	border-bottom: 2rpx solid #F3F3F3;
+	display: flex;
+
+	.search_box {
+		flex: 1;
+		display: flex;
+		align-items: center;
+		padding: 0rpx 24rpx;
+		height: 72rpx;
+		line-height: 72rpx;
+		border-radius: 8rpx;
+		border: 2rpx solid #DCDFE6;
+
+		.search_icon {
+			width: 32rpx;
+			height: 32rpx;
+			margin-right: 8rpx;
+		}
+
+		.search_input {
+			flex: 1;
+			height: 72rpx;
+			font-size: 28rpx;
+			color: #333333;
+			padding-right: 24rpx;
+			box-sizing: border-box;
+		}
+	}
+}
+</style>

+ 1 - 1
manifest.json

@@ -1,6 +1,6 @@
 {
     "name" : "教师端",
-    "appid" : "__UNI__C45CD76",
+    "appid" : "__UNI__07352BD",
     "description" : "",
     "versionName" : "1.2.0",
     "versionCode" : 20260602,

Datei-Diff unterdrückt, da er zu groß ist
+ 715 - 2
package-lock.json


+ 3 - 1
package.json

@@ -15,7 +15,9 @@
     "@vueup/vue-quill": "^1.5.5",
     "echarts": "^5.6.0",
     "jsencrypt": "^3.3.2",
-    "lodash-es": "^4.17.21"
+    "lodash-es": "^4.17.21",
+    "pdfh5": "^3.0.0",
+    "pdfjs-dist": "^3.8.162"
   },
   "devDependencies": {
     "@dcloudio/uni-cli-i18n": "^2.0.2-4060620250520001",

+ 29 - 0
pages.json

@@ -84,6 +84,13 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/notice/publish/personList",
+			"style": {
+				"navigationBarTitleText": "人员列表",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/notice/overview/noticeDetail",
 			"style": {
@@ -98,6 +105,28 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/notice/mine/detail",
+			"style": {
+				"navigationBarTitleText": "消息通知",
+				"navigationStyle": "custom",
+				"tabBar": {
+					"custom": true,
+					"hidden": true
+				}
+			}
+		},
+		{
+			"path": "pages/notice/mine/preview",
+			"style": {
+				"navigationBarTitleText": "文件预览",
+				"navigationStyle": "custom",
+				"tabBar": {
+					"custom": true,
+					"hidden": true
+				}
+			}
+		},
 		{
 			"path": "pages/teacherStudy/index",
 			"style": {

+ 14 - 2
pages/message/index.vue

@@ -109,10 +109,18 @@
 
 <style lang="scss">
 .page_message {
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
 }
 
 .message_content {
-	padding: 140rpx 24rpx 24rpx 24rpx;
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding: 140rpx 0rpx 160rpx 0rpx;
 }
 
 .message_header {
@@ -120,7 +128,8 @@
 	align-items: center;
 	justify-content: space-between;
 	margin-bottom: 24rpx;
-
+	flex-shrink: 0;
+	padding:0 24rpx;
 	.message_title {
 		font-weight: 500;
 		font-size: 32rpx;
@@ -143,6 +152,9 @@
 }
 
 .message_list {
+	flex: 1;
+	overflow-y: auto;
+	padding:0 24rpx;
 	.message_item {
 		display: flex;
 		align-items: center;

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

@@ -0,0 +1,305 @@
+<template>
+	<view class="page_detail">
+		<common-header title="消息通知" @back="goBack"></common-header>
+
+		<view class="detail_content">
+			<view class="detail_header">
+				<text class="detail_title">{{ noticeDetail.noticeName }}</text>
+				<view class="detail_meta">
+					<view class="meta_left">
+						<text class="meta_text">{{ noticeDetail.typeName }}</text>
+						<text class="meta_text">{{ noticeDetail.createTeacherName }}</text>
+						<text class="meta_text">{{ noticeDetail.releaseTime }}</text>
+					</view>
+					<view class="attachment_tag" v-if="noticeDetail.noticeFiles.length > 0" @click="showAttachmentModal = true">
+						<text>附件: {{ noticeDetail.noticeFiles.length }}</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="detail_section">
+				<text class="section_title">通知内容</text>
+				<view class="section_scroll_wrapper">
+					<scroll-view scroll-y class="section_content">
+						<rich-text :nodes="noticeDetail.noticeContent"></rich-text>
+					</scroll-view>
+				</view>
+			</view>
+		</view>
+
+		<view class="attachment_modal" v-if="showAttachmentModal" @click="showAttachmentModal = false">
+			<view class="modal_content" @click.stop>
+				<view class="modal_header">
+					<text class="modal_title">查看附件</text>
+					<view class="modal_close" @click="showAttachmentModal = false">
+						<text>×</text>
+					</view>
+				</view>
+				<view class="modal_list_wrapper">
+					<scroll-view scroll-y class="modal_list">
+						<view class="attachment_item" v-for="(file, index) in noticeDetail.noticeFiles" :key="index">
+							<image class="file_icon" src="/static/image/icon/file.png" mode="aspectFit"></image>
+							<view class="file_info">
+								<text class="file_name">{{ file.attachmentName }}</text>
+							</view>
+							<text class="preview_btn" @click="previewFile(file)">预览</text>
+						</view>
+					</scroll-view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { onLoad } from '@dcloudio/uni-app';
+import CommonHeader from '@/components/common-header.vue';
+import overviewApi from '@/reqApi/overview.js';
+
+const noticeDetail = ref({
+	noticeName: '',
+	typeName: '',
+	createTeacherName: '',
+	releaseTime: '',
+	noticeFiles: [],
+	noticeContent: ''
+});
+
+const showAttachmentModal = ref(false);
+
+const previewFile = (file) => {
+	if (file.attachmentUrl) {
+		uni.navigateTo({
+			url: `/pages/notice/mine/preview?url=${encodeURIComponent(file.attachmentUrl)}&name=${encodeURIComponent(file.attachmentName || '文件预览')}`
+		});
+	} else {
+		uni.showToast({
+			title: '附件链接为空',
+			icon: 'none'
+		});
+	}
+};
+
+const fetchDetail = async (id) => {
+	try {
+		const res = await overviewApi.notices_detail({ id });
+		if (res.data) {
+			noticeDetail.value = {
+				...res.data,
+				noticeFiles: Array.isArray(res.data.noticeFiles) ? res.data.noticeFiles : []
+			};
+		}
+	} catch (error) {
+		console.error('获取通知详情失败:', error);
+	}
+};
+
+onLoad((options) => {
+	if (options && options.id) {
+		fetchDetail(options.id);
+	}
+});
+
+const goBack = () => {
+	uni.navigateBack();
+};
+</script>
+
+<style lang="scss">
+.page_detail {
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
+}
+
+.detail_content {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding-top: 140rpx;
+}
+
+.detail_header {
+	padding: 24rpx;
+	border-bottom: 2rpx solid #F3F3F3;
+	flex-shrink: 0;
+
+	.detail_title {
+		font-weight: 500;
+		font-size: 32rpx;
+		color: #333333;
+		line-height: 1.5;
+		margin-bottom: 24rpx;
+		display: block;
+		word-wrap: break-word;
+		white-space: normal;
+	}
+
+	.detail_meta {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+
+		.meta_left {
+			display: flex;
+			align-items: center;
+			flex-wrap: wrap;
+			gap: 16rpx;
+		}
+
+		.meta_text {
+			font-size: 24rpx;
+			color: #999999;
+		}
+
+		.attachment_tag {
+			font-size: 24rpx;
+			color: #2E64FA;
+			padding: 8rpx 16rpx;
+			background-color: rgba(46, 100, 250, 0.1);
+			border-radius: 8rpx;
+			flex-shrink: 0;
+		}
+	}
+}
+
+.detail_section {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+
+	.section_title {
+		font-weight: 500;
+		font-size: 28rpx;
+		color: #333333;
+		padding: 24rpx;
+		display: block;
+		flex-shrink: 0;
+	}
+
+	.section_scroll_wrapper {
+		flex: 1;
+		overflow: hidden;
+		padding: 0 24rpx 90rpx 24rpx;
+		box-sizing: border-box;
+
+		.section_content {
+			height: 100%;
+			font-size: 28rpx;
+			color: #333333;
+			line-height: 1.8;
+
+			:deep(p) {
+				margin-bottom: 16rpx;
+			}
+
+			:deep(ol),
+			:deep(ul) {
+				padding-left: 32rpx;
+				margin-bottom: 16rpx;
+			}
+
+			:deep(li) {
+				margin-bottom: 8rpx;
+			}
+		}
+	}
+}
+
+.attachment_modal {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: rgba(0, 0, 0, 0.5);
+	display: flex;
+	align-items: flex-end;
+	z-index: 1000;
+
+	.modal_content {
+		width: 100%;
+		background-color: #FFFFFF;
+		border-radius: 24rpx 24rpx 0 0;
+		max-height: 70vh;
+		display: flex;
+		flex-direction: column;
+
+		.modal_header {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 32rpx;
+			border-bottom: 2rpx solid #F3F3F3;
+
+			.modal_title {
+				font-weight: 500;
+				font-size: 32rpx;
+				color: #333333;
+			}
+
+			.modal_close {
+				width: 64rpx;
+				height: 64rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				font-size: 48rpx;
+				color: #999999;
+			}
+		}
+
+		.modal_list_wrapper {
+			flex: 1;
+			overflow: hidden;
+			padding: 24rpx;
+			box-sizing: border-box;
+
+			.modal_list {
+				height: 100%;
+
+				.attachment_item {
+					display: flex;
+					align-items: center;
+					padding: 24rpx;
+					background-color: #F5F7FA;
+					border-radius: 12rpx;
+					margin-bottom: 16rpx;
+
+					.file_icon {
+						width: 80rpx;
+						height: 80rpx;
+						margin-right: 20rpx;
+						flex-shrink: 0;
+					}
+
+					.file_info {
+						flex: 1;
+						overflow: hidden;
+
+						.file_name {
+							font-size: 28rpx;
+							color: #333333;
+							display: block;
+							white-space: nowrap;
+							overflow: hidden;
+							text-overflow: ellipsis;
+						}
+					}
+
+					.preview_btn {
+						font-size: 28rpx;
+						color: #2E64FA;
+						margin-left: 20rpx;
+						flex-shrink: 0;
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 106 - 76
pages/notice/mine/index.vue

@@ -6,35 +6,21 @@
 		<view class="notice_content">
 			<view class="notice_list">
 				<scroll-view scroll-y v-for="item in noticeList" :key="item.id" class="notice_item">
-					<view class="item_content">
+					<view class="item_content" @click="handleDetail(item.id)">
 						<view class="notice_title_row">
 							<image class="notice_icon" :src="item.icon" mode="aspectFit"></image>
 							<text class="notice_title">{{ item.title }}</text>
+							<image src="/static/image/icon/xiala.png" class="arrow_icon"></image>
 						</view>
 						<view class="notice_meta">
+							<view class="meta_left">
+								<text class="meta_text">{{ item.category }}</text>
+								<text class="meta_text">{{ item.department }}</text>
+								<text class="meta_text">{{ item.time }}</text>
+							</view>
 							<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">{{ item.time }}</text>
-						</view>
-					</view>
-					<view class="item_bottom">
-						<view class="notice_stats">
-							<text class="stats_text">已读: {{ item.readCount }}人</text>
-							<text class="stats_text">未读: {{ item.unreadCount }}人</text>
-						</view>
-						<view class="notice_actions">
-							<view class="action_btn" :class="getDeleteClass(item)" @click="handleDelete(item.id)">
-								<text>删除</text>
-							</view>
-							<view class="action_btn edit" @click="handleEdit(item.id)">
-								<text>编辑</text>
-							</view>
-							<view class="action_btn detail" @click="handleDetail(item.id)">
-								<text>详情</text>
-							</view>
 						</view>
 					</view>
 				</scroll-view>
@@ -43,56 +29,26 @@
 			</view>
 		</view>
 
-		<common-tabbar></common-tabbar>
+		<common-tabbar v-if="showTabbar"></common-tabbar>
 	</view>
 </template>
 
 <script setup>
 import { ref } from 'vue';
+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';
 
-const tabs = ['未读', '已读', '全部'];
+const tabs = ['全部', '未读', '已读'];
 const activeTab = ref(0);
+const readStatusMap = [-1, 0, 1];
+const typeId = ref('0');
+const timeType = ref(0);
+const word = ref('');
+const showTabbar = ref(true);
 
-const noticeList = ref([
-	{
-		id: '1',
-		title: '25-26学年第一学期初一期中考试安排通知',
-		status: '已发布',
-		statusClass: 'status-published',
-		category: '教学教务',
-		department: '教务处',
-		time: '2024-06-03 18:00:00',
-		readCount: 1500,
-		unreadCount: 50,
-		icon: '/static/image/todo/xiaoxi.png'
-	},
-	{
-		id: '2',
-		title: '关于开展教师培训的通知',
-		status: '已发布',
-		statusClass: 'status-published',
-		category: '教师培训',
-		department: '人事处',
-		time: '2024-06-02 10:00:00',
-		readCount: 280,
-		unreadCount: 15,
-		icon: '/static/image/todo/xiaoxi.png'
-	},
-	{
-		id: '3',
-		title: '暑期放假安排通知',
-		status: '待发布',
-		statusClass: 'status-pending',
-		category: '学校通知',
-		department: '校长办公室',
-		time: '2024-06-01 09:00:00',
-		readCount: 0,
-		unreadCount: 0,
-		icon: '/static/image/todo/xiaoxi.png'
-	}
-]);
+const noticeList = ref([]);
 
 const getDeleteClass = (item) => {
 	if (item.noticeStatus !== 2) {
@@ -101,17 +57,74 @@ const getDeleteClass = (item) => {
 	return 'delete';
 };
 
+const fetchNoticeList = async () => {
+	try {
+		const params = {
+			readStatus: readStatusMap[activeTab.value],
+			pageParam: {
+				pageNum: 1,
+				pageSize: 9999
+			},
+			typeId: typeId.value,
+			timeType: timeType.value,
+			word: word.value
+		};
+		const res = await overviewApi.teacher_notices_page(params);
+		if (res.data.records && res.data.records[0] && res.data.records[0].noticePageVOList) {
+			noticeList.value = res.data.records[0].noticePageVOList.map(item => ({
+				id: item.id,
+				title: item.noticeName,
+				status: item.readStatus === 1 ? '已读' : '未读',
+				noticeStatus: item.readStatus,
+				statusClass: item.readStatus === 1 ? 'status-published' : 'status-closed',
+				category: item.typeName,
+				department: item.createTeacherName,
+				time: item.releaseTime,
+				readCount: item.readNum,
+				unreadCount: item.unReadNum,
+				icon: '/static/image/todo/xiaoxi.png'
+			}));
+		} else {
+			noticeList.value = [];
+		}
+	} catch (error) {
+		console.error('获取通知列表失败:', error);
+	}
+};
+
+onLoad((options) => {
+	if (options && options.hideTabbar === 'true') {
+		showTabbar.value = false;
+	}
+});
+
+onShow(() => {
+	fetchNoticeList();
+});
+
 const goBack = () => {
 	uni.navigateBack();
 };
 
 const handleTabChange = (idx) => {
 	activeTab.value = idx;
+	fetchNoticeList();
+};
+
+const handleTypeChange = (type) => {
+	typeId.value = type;
+	fetchNoticeList();
+};
+
+const handleTimeChange = (time) => {
+	timeType.value = time;
+	fetchNoticeList();
 };
 
-const handleTypeChange = () => {};
-const handleTimeChange = () => {};
-const handleSearchChange = () => {};
+const handleSearchChange = (keyword) => {
+	word.value = keyword;
+	fetchNoticeList();
+};
 
 const handleDelete = (id) => {
 	uni.showModal({
@@ -131,27 +144,31 @@ const handleEdit = (id) => {
 };
 
 const handleDetail = (id) => {
-	const item = noticeList.value.find(item => item.id === id);
-	if (item && item.status === '待发布') {
-		uni.showToast({ title: '待发布通知暂无法查看详情', icon: 'none' });
-		return;
-	}
-	uni.showToast({ title: `查看详情: ${id}`, icon: 'none' });
+	uni.navigateTo({ url: `/pages/notice/mine/detail?id=${id}` });
 };
 </script>
 
 <style lang="scss">
 .page_mine {
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
 }
 
 .notice_content {
-	padding-top: 325rpx;
-	padding-bottom: 104px;
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding: 325rpx 0rpx 104px 0rpx;
 }
 
 .notice_list {
-	padding: 0rpx 24rpx;
-	box-sizing: border-box;
+	flex: 1;
+	overflow-y: auto;
+	overflow-x: hidden;
+	padding: 0 24rpx;
 }
 
 .notice_item {
@@ -173,6 +190,11 @@ const handleDetail = (id) => {
 				height: 32rpx;
 				margin-right: 16rpx;
 			}
+			.arrow_icon {
+				width: 24rpx;
+				height: 24rpx;
+				transform: rotate(-90deg);
+			}
 
 			.notice_title {
 				flex: 1;
@@ -188,8 +210,15 @@ const handleDetail = (id) => {
 		.notice_meta {
 			display: flex;
 			align-items: center;
-			flex-wrap: wrap;
-			gap: 16rpx;
+			justify-content: space-between;
+
+			.meta_left {
+				display: flex;
+				align-items: center;
+				flex-wrap: wrap;
+				gap: 16rpx;
+				
+			}
 
 			.status_tag {
 				font-size: 24rpx;
@@ -219,6 +248,7 @@ const handleDetail = (id) => {
 			.meta_text {
 				font-size: 24rpx;
 				color: #999999;
+				height: 48rpx;
 				line-height: 48rpx;
 			}
 		}

+ 145 - 0
pages/notice/mine/preview.vue

@@ -0,0 +1,145 @@
+<template>
+	<view class="page_preview">
+		<common-header :title="fileName" @back="goBack"></common-header>
+		
+		<web-view 
+			v-if="previewUrl" 
+			:src="previewUrl" 
+			class="preview_webview"
+			@error="handleWebViewError"
+			@load="handleWebViewLoad"
+		></web-view>
+		
+		<view v-if="showRetry" class="retry_container">
+			<text class="retry_text">预览加载失败,正在尝试其他服务...</text>
+		</view>
+		
+		<view v-if="showLoading" class="loading_container">
+			<text class="loading_text">加载中...</text>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { onLoad } from '@dcloudio/uni-app';
+import CommonHeader from '@/components/common-header.vue';
+
+const fileUrl = ref('');
+const fileName = ref('文件预览');
+const currentServiceIndex = ref(0);
+const showRetry = ref(false);
+const showLoading = ref(true);
+
+const isPdf = computed(() => {
+	const ext = fileUrl.value.split('.').pop()?.toLowerCase();
+	return ext === 'pdf';
+});
+
+const previewServices = [
+	(fileUrl) => `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(fileUrl)}`,
+	(fileUrl) => `http://api.idocv.com/view/url?url=${encodeURIComponent(fileUrl)}&name=${encodeURIComponent(fileName.value)}`,
+	(fileUrl) => fileUrl
+];
+
+const previewUrl = computed(() => {
+	const ext = fileUrl.value.split('.').pop()?.toLowerCase();
+	if (!ext) return fileUrl.value;
+	
+	if (ext === 'pdf') {
+		return `/static/pdfh5/index.html?url=${encodeURIComponent(fileUrl.value)}`;
+	}
+	
+	if (['docx', 'doc', 'xlsx', 'xls', 'ppt', 'pptx', 'txt', 'csv', 'wps', 'dps', 'et'].includes(ext)) {
+		return previewServices[currentServiceIndex.value](fileUrl.value);
+	}
+	
+	return fileUrl.value;
+});
+
+const handleWebViewError = () => {
+	if (!isPdf.value && currentServiceIndex.value < previewServices.length - 1) {
+		showRetry.value = true;
+		currentServiceIndex.value++;
+		setTimeout(() => {
+			showRetry.value = false;
+		}, 2000);
+	} else {
+		showLoading.value = false;
+		uni.showToast({
+			title: '预览失败,请重试',
+			icon: 'none'
+		});
+	}
+};
+
+const handleWebViewLoad = () => {
+	showLoading.value = false;
+	showRetry.value = false;
+};
+
+onLoad((options) => {
+	if (options && options.url) {
+		fileUrl.value = decodeURIComponent(options.url);
+	}
+	if (options && options.name) {
+		fileName.value = decodeURIComponent(options.name);
+	}
+});
+
+onMounted(() => {
+	if (!previewUrl.value) {
+		showLoading.value = false;
+	}
+});
+
+const goBack = () => {
+	uni.navigateBack();
+};
+</script>
+
+<style lang="scss">
+.page_preview {
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
+}
+
+.preview_webview {
+	flex: 1;
+	margin-top: 96rpx;
+}
+
+.retry_container {
+	position: fixed;
+	top: 120rpx;
+	left: 50%;
+	transform: translateX(-50%);
+	background-color: rgba(0, 0, 0, 0.7);
+	padding: 20rpx 40rpx;
+	border-radius: 12rpx;
+	z-index: 100;
+	
+	.retry_text {
+		font-size: 26rpx;
+		color: #FFFFFF;
+	}
+}
+
+.loading_container {
+	position: fixed;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%, -50%);
+	background-color: rgba(0, 0, 0, 0.5);
+	padding: 40rpx 60rpx;
+	border-radius: 12rpx;
+	z-index: 1000;
+	
+	.loading_text {
+		font-size: 28rpx;
+		color: #FFFFFF;
+	}
+}
+</style>

+ 54 - 28
pages/notice/overview/index.vue

@@ -1,10 +1,11 @@
 <template>
 	<view class="page_notice">
 		<notice-header title="校内通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
-			:activeTab="activeTab" @tabChange="handleTabChange" @typeChange="handleTypeChange" @timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
+			:activeTab="activeTab" @tabChange="handleTabChange" @typeChange="handleTypeChange"
+			@timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
 
 		<view class="notice_content">
-			<view  class="notice_list">
+			<view class="notice_list">
 				<scroll-view scroll-y v-for="item in noticeList" :key="item.id" class="notice_item">
 					<!-- 通知内容 -->
 					<view class="item_content">
@@ -31,12 +32,10 @@
 							<view class="action_btn " :class="getDeleteClass(item)" @click="handleDelete(item.id)">
 								<text>删除</text>
 							</view>
-							<view class="action_btn edit" 
-								@click="handleEdit(item.id)">
+							<view class="action_btn edit" @click="handleEdit(item.id)">
 								<text>编辑</text>
 							</view>
-							<view class="action_btn detail" 
-								@click="handleDetail(item.id)">
+							<view class="action_btn detail" @click="handleDetail(item.id)">
 								<text>详情</text>
 							</view>
 						</view>
@@ -50,6 +49,9 @@
 
 		<common-tabbar></common-tabbar>
 
+		<ConfirmModal :visible="showDeleteModal" title="提示" content="确定要删除该通知吗?" cancelText="取消" confirmText="确定"
+			@cancel="closeDeleteModal" @confirm="confirmDelete"></ConfirmModal>
+
 	</view>
 </template>
 
@@ -57,6 +59,7 @@
 import { ref } 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';
@@ -67,6 +70,8 @@ const typeId = ref('0');
 const timeType = ref(0);
 const word = ref('');
 const noticeList = ref([]);
+const showDeleteModal = ref(false);
+const deleteId = ref(null);
 
 const statusMap = {
 	0: '待发布',
@@ -107,7 +112,7 @@ const fetchNoticeList = async () => {
 				unreadCount: item.unReadNum,
 				icon: '/static/image/todo/xiaoxi.png'
 			}));
-		}else{
+		} else {
 			noticeList.value = [];
 		}
 	} catch (error) {
@@ -151,16 +156,28 @@ const handleDelete = (id) => {
 	if (item && item.noticeStatus !== 2) {
 		return;
 	}
-	uni.showModal({
-		title: '确认删除',
-		content: '确定要删除这条通知吗?',
-		success: (res) => {
-			if (res.confirm) {
-				noticeList.value = noticeList.value.filter(item => item.id !== id);
-				uni.showToast({ title: '删除成功', icon: 'success' });
-			}
-		}
-	});
+	deleteId.value = id;
+	showDeleteModal.value = true;
+};
+
+const closeDeleteModal = () => {
+	showDeleteModal.value = false;
+	deleteId.value = null;
+};
+
+const confirmDelete = async () => {
+	if (!deleteId.value) {
+		closeDeleteModal();
+		return;
+	}
+	try {
+		await overviewApi.delete_notices({ id: deleteId.value });
+		noticeList.value = noticeList.value.filter(item => item.id !== deleteId.value);
+		uni.showToast({ title: '删除成功', icon: 'success' });
+	} catch (error) {
+		uni.showToast({ title: '删除失败', icon: 'none' });
+	}
+	closeDeleteModal();
 };
 
 const handleEdit = (id) => {
@@ -181,30 +198,35 @@ const handleDetail = (id) => {
 
 <style lang="scss">
 .page_notice {
-	// min-height: 100vh;
-	// background-color: #F5F5F5;
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
 }
 
 .notice_content {
-	padding-top: 325rpx;
-	// background-color: #000;
-	padding-bottom: 104px;
+
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding: 325rpx 0rpx 104px 0rpx;
+
 }
 
 .notice_list {
-	padding: 0rpx 24rpx;
-	box-sizing: border-box;
-	// flex: 1;
-	// 		overflow-y: auto;
+	flex: 1;
+	overflow-y: auto;
+	overflow-x: hidden;
+	padding: 0 24rpx;
 }
 
 .notice_item {
 	background-color: #F9FAFF;
 	border-radius: 20rpx;
-	// padding: 24rpx;
 	margin-bottom: 24rpx;
 	border: 2rpx solid #E4E7ED;
-	
+
 	.item_content {
 		padding: 24rpx;
 
@@ -277,8 +299,10 @@ const handleDetail = (id) => {
 		justify-content: space-between;
 		align-items: center;
 		padding: 24rpx 16rpx;
+
 		.notice_stats {
 			display: flex;
+
 			.stats_text {
 				font-size: 24rpx;
 				color: #999999;
@@ -293,6 +317,7 @@ const handleDetail = (id) => {
 			display: flex;
 			justify-content: flex-end;
 			gap: 16rpx;
+
 			.action_btn {
 				font-size: 24rpx;
 				padding: 12rpx 24rpx;
@@ -336,6 +361,7 @@ const handleDetail = (id) => {
 			}
 		}
 	}
+
 	&:last-child {
 		margin-bottom: 0;
 	}

+ 69 - 39
pages/notice/publish/index.vue

@@ -1,10 +1,6 @@
 <template>
 	<view class="page_publish">
-		<view class="publish_header">
-			<uni-icons type="back" size="24" color="#333333" @click="goBack"></uni-icons>
-			<text class="header_title">发布通知</text>
-			<text class="header_placeholder"></text>
-		</view>
+		<common-header :title="isEdit ? '编辑通知' : '发布通知'" @back="goBack"></common-header>
 
 		<scroll-view scroll-y class="publish_content">
 			<view class="form_item">
@@ -65,7 +61,7 @@
 					<text class="form_label">通知对象</text>
 					<view class="form_content">
 						<view class="object_row">
-							<view class="object_info">
+							<view class="object_info" @click="goPersonList">
 								<text class="object_count">{{ objectTypeLabel || '请选择' }}</text>
 								<text class="object_count">已选{{ selectedCount }}人</text>
 							</view>
@@ -118,6 +114,7 @@ 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';
 
 const showTypePicker = ref(false);
 const publishType = ref(0);
@@ -199,7 +196,8 @@ const fetchNoticeDetail = async (id) => {
 		const res = await overviewApi.notices_edit({ id });
 		if (res.data) {
 			const data = res.data;
-				formData.title = data.noticeName || '';
+			console.log('userList原始数据:', data.userList);
+			formData.title = data.noticeName || '';
 			const content = data.noticeContent || '';
 			formData.content = content.replace(/<[^>]+>/g, '');
 			formData.status = data.isClose !== true;
@@ -226,10 +224,15 @@ const fetchNoticeDetail = async (id) => {
 			}
 			
 			if (data.userList && Array.isArray(data.userList)) {
-				selectedUsers.value = data.userList.map(user => ({
-					id: user.userId,
-					name: user.thirdName
-				}));
+				selectedUsers.value = data.userList.map(user => {
+					const thirdCode = user.thirdCode || user.userAccount || user.teacherId || user.studentCode || '';
+					return {
+						id: user.userId,
+						name: user.thirdName,
+						thirdCode: thirdCode,
+						userAccount: user.userAccount || thirdCode
+					};
+				});
 				selectedCount.value = selectedUsers.value.length;
 			}
 		}
@@ -253,6 +256,20 @@ onShow(() => {
 		}
 		uni.removeStorageSync('selectObjectResult');
 	}
+
+	const personResult = uni.getStorageSync('personListResult');
+	if (personResult) {
+		try {
+			const data = JSON.parse(personResult);
+			if (data && data.users) {
+				selectedUsers.value = data.users;
+				selectedCount.value = data.users.length;
+			}
+		} catch (e) {
+			console.error('解析人员列表结果失败:', e);
+		}
+		uni.removeStorageSync('personListResult');
+	}
 });
 
 const handleTypeChange = (e) => {
@@ -286,6 +303,18 @@ const selectObject = () => {
 	uni.navigateTo({ url: '/pages/notice/publish/selectObject' });
 };
 
+const goPersonList = () => {
+	if (selectedUsers.value.length === 0) {
+		return;
+	}
+	const data = {
+		tab: objectTypeLabel.value,
+		users: selectedUsers.value
+	};
+	uni.setStorageSync('personListData', JSON.stringify(data));
+	uni.navigateTo({ url: '/pages/notice/publish/personList' });
+};
+
 const handleObjectSelect = (data) => {
 	if (data) {
 		objectTypeLabel.value = data.tab;
@@ -371,10 +400,36 @@ const handleSave = async () => {
 			const userInfo = {
 				userType: objectType === 5 ? 2 : 1,
 				userId: user.id,
-				thirdName: user.name.split('(')[0].trim(),
-				thirdCode: user.name.match(/\((.*?)\)/)?.[1] || ''
-				
+				thirdName: user.name,
+				thirdCode: user.thirdCode || user.userAccount || '',
+				gradeName: '',
+				classTypeName: '',
+				className: '',
+				departmentName: '',
+				groupName: '',
+				subjectName: '',
+				roleName: ''
 			};
+
+			switch (objectType) {
+				case 1:
+					userInfo.departmentName = user.departmentName || '';
+					userInfo.groupName = user.groupName || '';
+					break;
+				case 2:
+					userInfo.subjectName = user.subjectName || '';
+					userInfo.gradeName = user.gradeName || '';
+					break;
+				case 3:
+					userInfo.gradeName = user.gradeName || '';
+					userInfo.classTypeName = user.classTypeName || '';
+					userInfo.className = user.className || '';
+					break;
+				case 4:
+					userInfo.roleName = user.roleName || '';
+					break;
+			}
+
 			return userInfo;
 		});
 		
@@ -408,31 +463,6 @@ const handleSave = async () => {
 	flex-direction: column;
 }
 
-.publish_header {
-	position: fixed;
-	top: 0;
-	left: 0;
-	right: 0;
-	z-index: 100;
-	display: flex;
-	align-items: center;
-	justify-content: space-between;
-	height: 96rpx;
-	padding: 0 24rpx;
-	background-color: #FFFFFF;
-	border-bottom: 2rpx solid #F3F3F3;
-
-	.header_title {
-		font-weight: 500;
-		font-size: 32rpx;
-		color: #333333;
-	}
-
-	.header_placeholder {
-		width: 48rpx;
-	}
-}
-
 .publish_content {
 	flex: 1;
 	box-sizing: border-box;

+ 172 - 0
pages/notice/publish/personList.vue

@@ -0,0 +1,172 @@
+<template>
+	<view class="page_person_list">
+		<CommonHeader :title="headerTitle" @back="goBack"></CommonHeader>
+
+		<SearchBar v-model="searchWord" @search="handleSearch" :isFixed="true" :top="114" :border="false"></SearchBar>
+
+		<scroll-view scroll-y class="person_list">
+			<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>
+					<text class="person_code">({{ person.thirdCode || person.userAccount || '' }})</text>
+				</view>
+				<view class="person_action" @click="openDeleteModal(index)">
+					<text class="delete_text">删除</text>
+				</view>
+			</view>
+
+			<view v-if="filteredList.length === 0" class="empty_state">
+				<text class="empty_text">暂无人员</text>
+			</view>
+		</scroll-view>
+
+		<ConfirmModal
+			:visible="showDeleteModal"
+			title="提示"
+			:content="`确定要删除【${deletePersonName}】吗?`"
+			cancelText="取消"
+			confirmText="确定"
+			@cancel="closeDeleteModal"
+			@confirm="confirmDelete"
+		></ConfirmModal>
+	</view>
+</template>
+
+<script setup>
+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';
+
+const searchWord = ref('');
+
+const personList = ref([]);
+const objectTypeLabel = ref('');
+
+const showDeleteModal = ref(false);
+const deleteIndex = ref(-1);
+const deletePersonName = ref('');
+
+const filteredList = computed(() => {
+	console.log(personList.value)
+	if (!searchWord.value) return personList.value;
+	const keyword = searchWord.value.toLowerCase();
+	return personList.value.filter(person => {
+		const name = (person.thirdName || person.name || '').toLowerCase();
+		const code = (person.thirdCode || person.userAccount || '').toLowerCase();
+		return name.includes(keyword) || code.includes(keyword);
+	});
+	
+});
+
+const headerTitle = computed(() => {
+	return `${objectTypeLabel.value || ''} 已选${personList.value.length}人`;
+});
+
+const handleSearch = (keyword) => {
+	searchWord.value = keyword;
+};
+
+const openDeleteModal = (index) => {
+	const person = personList.value[index];
+	deletePersonName.value = person.thirdName || person.name || '';
+	deleteIndex.value = index;
+	showDeleteModal.value = true;
+};
+
+const closeDeleteModal = () => {
+	showDeleteModal.value = false;
+	deleteIndex.value = -1;
+	deletePersonName.value = '';
+};
+
+const confirmDelete = () => {
+	if (deleteIndex.value >= 0) {
+		personList.value.splice(deleteIndex.value, 1);
+	}
+	closeDeleteModal();
+};
+
+const goBack = () => {
+	uni.setStorageSync('personListResult', JSON.stringify({
+		users: personList.value
+	}));
+	uni.navigateBack();
+};
+
+onMounted(() => {
+	const data = uni.getStorageSync('personListData');
+	if (data) {
+		try {
+			const parsed = JSON.parse(data);
+			const users = parsed.users || [];
+			const seen = new Set();
+			personList.value = users.filter(user => {
+				const userId = user.userId || user.id;
+				if (!userId || seen.has(userId)) return false;
+				seen.add(userId);
+				return true;
+			});
+			objectTypeLabel.value = parsed.tab || '';
+		} catch (e) {
+			console.error('解析人员列表数据失败:', e);
+		}
+	}
+});
+</script>
+
+<style lang="scss">
+.page_person_list {
+	display: flex;
+	flex-direction: column;
+	min-height: 100vh;
+}
+
+.person_list {
+	flex: 1;
+	padding: 222rpx 24rpx 24rpx 24rpx;
+	box-sizing: border-box;
+}
+
+.person_item {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	height: 86rpx;
+	border-bottom: 2rpx solid #F3F3F3;
+	.person_info {
+		display: flex;
+		align-items: center;
+
+		.person_name {
+			font-size: 28rpx;
+			color: #666;
+		}
+
+		.person_code {
+			font-size: 28rpx;
+			color: #666;
+			margin-left: 16rpx;
+		}
+	}
+
+	.person_action {
+		.delete_text {
+			font-size: 28rpx;
+			color: #F56C6C;
+		}
+	}
+}
+
+.empty_state {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	padding: 100rpx 0;
+
+	.empty_text {
+		font-size: 28rpx;
+		color: #999999;
+	}
+}
+</style>

+ 68 - 100
pages/notice/publish/selectObject.vue

@@ -1,10 +1,6 @@
 <template>
 	<view class="page_select_object">
-		<view class="select_header">
-			<uni-icons type="back" size="24" color="#333333" @click="goBack"></uni-icons>
-			<text class="header_title">选择通知对象</text>
-			<text class="header_placeholder"></text>
-		</view>
+		<CommonHeader title="选择通知对象" @back="goBack"></CommonHeader>
 
 		<view class="tabs_container">
 			<view v-for="tab in tabs" :key="tab.key" class="tab_item" :class="{ active: activeTab === tab.key, disabled: editMode }"
@@ -13,12 +9,7 @@
 			</view>
 		</view>
 
-		<view class="search_container">
-			<view class="search_box">
-				<image src="/static/image/icon/search.png" class="search_icon" mode="aspectFit"></image>
-				<input type="text" class="search_input" v-model="searchKeyword" @input="searchTree(searchKeyword)" placeholder="请输入关键字搜索" />
-			</view>
-		</view>
+		<SearchBar v-model="searchKeyword" @search="searchTree" :border="true" :isFixed="true" :top="212" :zIndex="100"></SearchBar>
 
 		<scroll-view scroll-y class="content_list">
 			<TreeNode :tree-data="treeData" :expanded-ids="expandedIds" @toggle-expand="toggleExpand"
@@ -37,6 +28,8 @@
 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';
 
 const activeTab = ref('department');
@@ -63,11 +56,12 @@ const deduplicateUsers = (users) => {
 	if (!users || !Array.isArray(users)) return [];
 	const seen = new Set();
 	return users.filter(user => {
-		if (user.teacherId && seen.has(user.teacherId)) {
+		const userId = user.userId;
+		if (userId && seen.has(userId)) {
 			return false;
 		}
-		if (user.teacherId) {
-			seen.add(user.teacherId);
+		if (userId) {
+			seen.add(userId);
 		}
 		return true;
 	});
@@ -79,14 +73,15 @@ const transformDepartmentData = (data) => {
 	
 	return data.map(dept => {
 		const groups = (dept.groupVOS || []).map(group => ({
-			id: group.groupId,
+			id: `${dept.id}_${group.groupId}`,
 			name: group.groupName,
 			checked: false,
 			halfChecked: false,
 			children: deduplicateUsers(group.groupUserVOS || []).map(user => ({
 				id: user.userId,
-				name: `${user.teacherName} (${user.userAccount})`,
+				name: user.teacherName,
 				userId: user.userId,
+				userAccount: user.userAccount,
 				checked: false,
 				halfChecked: false
 			}))
@@ -124,21 +119,22 @@ const transformSubjectData = (data) => {
 	
 	return data.map(subject => {
 		const grades = (subject.gradePersonVos || []).map(grade => ({
-			id: grade.schoolYearGradeId || grade.gradeCode,
+			id: `${subject.subjectCode}_${grade.schoolYearGradeId || grade.gradeCode}`,
 			name: grade.gradeName,
 			checked: false,
 			halfChecked: false,
 			children: deduplicateUsers(grade.personVoList || []).map(user => ({
 				id: user.userId,
-				name: `${user.teacherName} (${user.userAccount})`,
+				name: user.teacherName,
 				userId: user.userId,
+				userAccount: user.userAccount,
 				checked: false,
 				halfChecked: false
 			}))
 		}));
 		
 		return {
-			id: subject.subjectCode,
+			id: `subject_${subject.subjectCode}`,
 			name: subject.subjectName,
 			checked: false,
 			halfChecked: false,
@@ -154,21 +150,22 @@ const transformGradeData = (data) => {
 	return data.map(grade => {
 		const classTypes = (grade.classTypePersonVoList || []).map(classType => {
 			const classes = (classType.classInfoPersonVoList || []).map(cls => ({
-				id: cls.classCode,
+				id: `${classType.classType}_${cls.classCode}`,
 				name: cls.className,
 				checked: false,
 				halfChecked: false,
 				children: deduplicateUsers(cls.personVoList || []).map(user => ({
 					id: user.userId,
-					name: `${user.teacherName} (${user.userAccount})`,
+					name: user.teacherName,
 					userId: user.userId,
+					userAccount: user.userAccount,
 					checked: false,
 					halfChecked: false
 				}))
 			}));
 			
 			return {
-				id: classType.classType,
+				id: `${grade.schoolYearGradeId || grade.gradeCode}_${classType.classType}`,
 				name: classType.classTypeName,
 				checked: false,
 				halfChecked: false,
@@ -198,8 +195,9 @@ const transformPersonalData = (data) => {
 			halfChecked: false,
 			children: deduplicateUsers(item.personVoList || []).map(user => ({
 				id: user.userId,
-				name: `${user.teacherName} (${user.userAccount})`,
+				name: user.teacherName,
 				userId: user.userId,
+				userAccount: user.userAccount,
 				checked: false,
 				halfChecked: false
 			}))
@@ -213,21 +211,22 @@ const transformStudentData = (data) => {
 	
 	return data.map(grade => {
 		const classes = (grade.clsVOS || []).map(cls => ({
-			id: cls.classId,
+			id: `${grade.schoolYearGradeId || grade.gradeCode}_${cls.classId}`,
 			name: cls.className,
 			checked: false,
 			halfChecked: false,
-			children: (cls.studentTblVOS || []).map(student => ({
+			children: deduplicateUsers(cls.studentTblVOS || []).map(student => ({
 				id: student.userId,
-				name: `${student.studentName} (${student.studentCode})`,
+				name: student.studentName,
 				userId: student.userId,
+				userAccount: student.studentCode,
 				checked: false,
 				halfChecked: false
 			}))
 		}));
 		
 		return {
-			id: grade.schoolYearGradeId || grade.gradeCode,
+			id: `student_${grade.schoolYearGradeId || grade.gradeCode}`,
 			name: grade.gradeName,
 			checked: false,
 			halfChecked: false,
@@ -374,14 +373,10 @@ const toggleExpand = ({ itemId, siblingIds, isExpanded }) => {
 	if (isExpanded) {
 		expandedIds.value = expandedIds.value.filter(id => id !== itemId);
 	} else {
-		const descendantIds = getDescendantIds(treeData.value, itemId);
 		expandedIds.value = expandedIds.value.filter(id => {
 			if (siblingIds.includes(id)) {
 				return false;
 			}
-			if (descendantIds.includes(id)) {
-				return false;
-			}
 			return true;
 		});
 		expandedIds.value = [...expandedIds.value, itemId];
@@ -486,15 +481,51 @@ const goBack = () => {
 
 const collectSelectedUsers = () => {
 	const selectedUsers = [];
-	const collect = (nodes) => {
+	const seen = new Set();
+	const collect = (nodes, parentInfo = {}) => {
 		nodes.forEach(node => {
 			if (node.children && node.children.length > 0) {
-				collect(node.children);
-			} else if (node.checked && node.userId) {
+				const newParentInfo = { ...parentInfo };
+				if (node.name && !node.userId) {
+					if (!newParentInfo.departmentName && !newParentInfo.subjectName && !newParentInfo.gradeName && !newParentInfo.roleName) {
+						if (activeTab.value === 1) {
+							newParentInfo.departmentName = node.name;
+						} else if (activeTab.value === 2) {
+							newParentInfo.subjectName = node.name;
+						} else if (activeTab.value === 3) {
+							newParentInfo.gradeName = node.name;
+						} else if (activeTab.value === 4) {
+							newParentInfo.roleName = node.name;
+						} else if (activeTab.value === 5) {
+							newParentInfo.gradeName = node.name;
+						}
+					} else if (!newParentInfo.groupName && !newParentInfo.classTypeName && !newParentInfo.className) {
+						if (activeTab.value === 1) {
+							newParentInfo.groupName = node.name;
+						} else if (activeTab.value === 3) {
+							newParentInfo.classTypeName = node.name;
+						} else if (activeTab.value === 5) {
+							newParentInfo.className = node.name;
+						}
+					} else if (!newParentInfo.className && activeTab.value === 3) {
+						newParentInfo.className = node.name;
+					}
+				}
+				collect(node.children, newParentInfo);
+			} else if (node.checked && node.userId && !seen.has(node.userId)) {
+				seen.add(node.userId);
 				selectedUsers.push({
 					id: node.id,
 					name: node.name,
-					userId: node.userId
+					userId: node.userId,
+					thirdCode: node.userAccount,
+					departmentName: parentInfo.departmentName || '',
+					groupName: parentInfo.groupName || '',
+					subjectName: parentInfo.subjectName || '',
+					gradeName: parentInfo.gradeName || '',
+					classTypeName: parentInfo.classTypeName || '',
+					className: parentInfo.className || '',
+					roleName: parentInfo.roleName || ''
 				});
 			}
 		});
@@ -592,7 +623,7 @@ onMounted(async () => {
 			}
 			
 			if (data.users) {
-				selectedUserIds.value = data.users.map(u => u.id);
+				selectedUserIds.value = data.users.map(u => u.userId || u.id);
 			}
 		} catch (e) {
 			console.error('解析上次选择失败:', e);
@@ -631,31 +662,6 @@ onMounted(async () => {
 	min-height: 100vh;
 }
 
-.select_header {
-	position: fixed;
-	top: 0;
-	left: 0;
-	right: 0;
-	z-index: 100;
-	display: flex;
-	align-items: center;
-	justify-content: space-between;
-	height: 96rpx;
-	padding: 0 24rpx;
-	background-color: #FFFFFF;
-	border-bottom: 2rpx solid #F3F3F3;
-
-	.header_title {
-		font-weight: 500;
-		font-size: 32rpx;
-		color: #333333;
-	}
-
-	.header_placeholder {
-		width: 48rpx;
-	}
-}
-
 .tabs_container {
 	position: fixed;
 	top: 96rpx;
@@ -663,7 +669,7 @@ onMounted(async () => {
 	right: 0;
 	z-index: 99;
 	display: flex;
-	padding: 24rpx;
+	padding: 24rpx ;
 	background-color: #FFFFFF;
 	justify-content: space-between;
 
@@ -700,44 +706,6 @@ onMounted(async () => {
 	}
 }
 
-.search_container {
-	position: fixed;
-	top: 192rpx;
-	left: 0;
-	right: 0;
-	z-index: 98;
-	padding: 32rpx 0rpx;
-	margin: 0 24rpx;
-	background-color: #FFFFFF;
-	border-bottom: 2rpx solid #F3F3F3;
-
-	.search_box {
-		flex: 1;
-		display: flex;
-		align-items: center;
-		padding: 0rpx 24rpx;
-		height: 72rpx;
-		line-height: 72rpx;
-		border-radius: 8rpx;
-		border: 2rpx solid #DCDFE6;
-
-		.search_icon {
-			width: 32rpx;
-			height: 32rpx;
-			margin-right: 8rpx;
-		}
-
-		.search_input {
-			flex: 1;
-			height: 72rpx;
-			font-size: 28rpx;
-			color: #333333;
-			padding-right:24rpx;
-			box-sizing: border-box;
-		}
-	}
-}
-
 .content_list {
 	flex: 1;
 	padding: 330rpx 0 150rpx 0;

+ 33 - 24
pages/todo/index.vue

@@ -12,14 +12,26 @@
 					<view class="todo_left">
 						<view class="todo_info">
 							<view class="todo_title_row">
-								<image src="/static/image/todo/xiaoxi.png" class="todo_icon" mode="aspectFit"></image>
+								<image :src="item.source === 1 ? '/static/image/todo/xiaoxi.png' : (item.source === 2 ? '/static/image/todo/daiban.png' : '/static/image/todo/shipin.png')" class="todo_icon" mode="aspectFit"></image>
 								<text class="todo_name">{{ item.title }}</text>
 								<uni-icons type="right" size="18" color="#999999"></uni-icons>
 							</view>
-							<view class="todo_meta">
-								<text class="meta_tag" :class="getTagStyle(item.status)">{{ item.status }}</text>
-								<text v-for="(tag, idx) in item.categoryTags" :key="idx" class="meta_type">{{ tag }}</text>
-								<text class="meta_time">{{ item.time }}</text>
+							<view class="todo_meta" v-if="item.source === 1">
+								<text class="meta_tag" >未读</text>
+								<text class="meta_type" >{{ item.noticeType }}</text>
+								<text class="meta_type" >{{ item.createdBy }}</text>
+								<text class="meta_type" >{{ item.releaseTime }}</text>
+							</view>
+							<view class="todo_meta" v-if="item.source === 2">
+								<text class="meta_tag" :class="getTagStyle('进行中')" >{{ '进行中' }}</text>
+								<text class="meta_type" >{{ item.departmentName }}</text>
+								<text class="meta_type" >{{ item.materialCategoryName }}</text>
+								<text class="meta_type" >{{"剩余" + item.daysRemaining + "天"}}</text>
+							</view>
+							<view class="todo_meta" v-if="item.source === 3">
+								<text class="meta_tag" >{{ item.taskTyp }}</text>
+								<text class="meta_type" >{{ item.workspaceTheme }}</text>
+								<text class="meta_type" >{{ item.contentDomain }}</text>
 							</view>
 						</view>
 					</view>
@@ -56,21 +68,9 @@
 	const fetchTodoList = async () => {
 		try {
 			const res = await workspace.getTodoList();
+			todoList.value = []
 			if (res && res.data) {
-				todoList.value = res.data.map(item => {
-					const categoryTags = [];
-					if (item.departmentName) categoryTags.push(item.departmentName);
-					if (item.materialCategoryName) categoryTags.push(item.materialCategoryName);
-					return {
-						id: item.id,
-						title: item.title,
-						status: item.source === 2 ? '进行中' : (item.notSubmitNum > 0 ? `待提交:${item.notSubmitNum}` : (item.rejectNum > 0 ? `已驳回:${item.rejectNum}` : '')),
-        				btnText: item.source === 1 ? '查看通知' : '查看任务',
-						categoryTags,
-						time: item.daysRemaining !== undefined ? `剩余${item.daysRemaining}天` : '',
-						tag: '',
-					};
-				});
+				todoList.value = res.data
 			}
 			} catch (error) {
 			console.error('获取待办事项列表失败:', error);
@@ -84,20 +84,26 @@
 
 <style lang="scss">
 .page_todo {
-	// min-height: 100vh;
-	// background-color: #F5F7FA;
-	// padding-bottom: 120rpx;
+	height: 100vh;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
 }
 
 .todo_content {
-	padding: 140rpx 24rpx 24rpx 24rpx;
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding: 140rpx 0rpx 160rpx 0rpx;
 }
 // 待办事项标题
 .todo_header {
 	display: flex;
 	align-items: baseline;
 	margin-bottom: 24rpx;
-
+	padding:0 24rpx;
+	
 	.todo_title {
 		font-weight: 500;
 		font-size: 32rpx;
@@ -112,6 +118,9 @@
 }
 
 .todo_list {
+	flex: 1;
+	overflow-y: auto;
+	padding:0 24rpx;
 	.todo_item {
 		display: flex;
 		align-items: center;

+ 195 - 40
pages/workspace/index.vue

@@ -1,21 +1,57 @@
 <template>
 	<view class="page_workspace">
 		<page-header></page-header>
-		<view class="workspace_content">
+		<scroll-view scroll-y class="workspace_content">
 			<!-- 系统列表 -->
 			<view class="system_list">
 				<view class="section_title">系统列表</view>
-				<view class="system_grid">
-					<view 
-						v-for="(item, index) in systemItems" 
-						:key="index" 
-						class="system_item"
-						@click="handleSystemClick(item)"
-					>
-						<image :src="item.image" class="system_icon" mode="aspectFit"></image>
-						<text class="system_name">{{ item.name }}</text>
+				
+				<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)"
+							>
+								<image :src="item.image" class="small_icon" mode="aspectFit"></image>
+								<text class="small_name">{{ item.name }}</text>
+							</view>
+						</view>
 					</view>
-				</view>
+					
+					<view class="bottom_grid">
+						<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)"
+						>
+							<image :src="item.image" class="small_icon" mode="aspectFit"></image>
+							<text class="small_name">{{ item.name }}</text>
+						</view>
+					</view>
+				</template>
 			</view>
 			<!-- 校历安排 -->
 			<view class="calendar_section">
@@ -91,7 +127,7 @@
 					<text class="btn_text">新增日程</text>
 				</view>
 			</view>
-		</view>
+		</scroll-view>
 
 		<view v-if="showAddScheduleModal" class="modal-overlay" @click="handleCloseModal">
 			<view class="modal-content" @click.stop>
@@ -136,18 +172,27 @@ const store = useStore();
 const schoolName = ref('');
 
 const systemItems = ref([
-	{ name: '精准教学', key:'', image: '/static/image/workspace/icon1.png' },
-	{ name: '教师研修', key:'teacherStudy', image: '/static/image/workspace/icon2.png' },
-	{ name: '协同备课', key:'', image: '/static/image/workspace/icon3.png' },
-	{ name: '材料采集', key:'', image: '/static/image/workspace/icon4.png' },
-	{ name: '教师发展', key:'', image: '/static/image/workspace/icon5.png' },
-	{ name: '校内通知', key:'notice', image: '/static/image/workspace/icon6.png' },
-	{ name: '选择题判分', key:'', image: '/static/image/workspace/icon7.png' },
-	{ name: '智能选课', key:'', image: '/static/image/workspace/icon8.png' },
-	{ name: '学生学习', key:'', image: '/static/image/workspace/icon9.png' },
-	{ name: '基础数据', key:'', image: '/static/image/workspace/icon10.png' }
+	{ 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:'', 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));
+const smallCards = computed(() => systemItems.value.filter(item => !item.isLarge));
+const rightTopCards = computed(() => smallCards.value.slice(0, 4));
+const bottomCards = computed(() => smallCards.value.slice(4));
+const allSmallCards = computed(() => smallCards.value);
+
 const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
 
 const calendarDays = ref([]);
@@ -292,9 +337,19 @@ onShow(() => {
 
 const handleSystemClick = (item) => {
 	if (item.key === 'notice') {
-		uni.navigateTo({
-			url: '/pages/notice/overview/index'
-		});
+		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'
+			});
+		} else {
+			uni.navigateTo({
+				url: '/pages/notice/mine/index?hideTabbar=true'
+			});
+		}
 	}else if(item.key == 'teacherStudy'){
 		uni.navigateTo({
 			url: '/pages/teacherStudy/index'
@@ -458,7 +513,10 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 }
 
 .workspace_content {
-	padding: 140rpx 24rpx 30rpx 24rpx;
+	padding: 140rpx 24rpx 140rpx 24rpx;
+	height: 100vh;
+	box-sizing: border-box;
+	overflow: hidden;
 }
 
 // 系统列表
@@ -473,25 +531,122 @@ $shadow-sm: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 		margin-bottom: $spacing-sm;
 	}
 
-	.system_grid {
-		display: grid;
-		grid-template-columns: repeat(4, 130rpx);
-		justify-content: space-between;
+	.system_grid_with_large {
+		display: flex;
+		gap: 20rpx;
+		margin-bottom: 20rpx;
+
+		.large_card {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+			background-color: #EAF0FF;
+			border-radius: 20rpx;
+			width: 324rpx;
+			height: 352rpx;
+			// padding: 30rpx 0;
+
+			.large_icon {
+				width: 160rpx;
+				height: 160rpx;
+				margin-bottom: 32rpx;
+			}
+
+			.large_name {
+				font-size: 32rpx;
+				color: #333333;
+				font-weight: 500;
+				text-align: center;
+				line-height: 48rpx;
+				width: 276rpx;
+			}
+		}
+
+		.right_top_grid {
+			flex: 1;
+			display: grid;
+			grid-template-columns: repeat(2, 1fr);
+			gap: 16rpx;
+
+			.small_item {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+				background-color: #FFFFFF;
+				// border-radius: 12rpx;
+				// padding: 20rpx 0;
+
+				.small_icon {
+					width: 96rpx;
+					height: 96rpx;
+					margin-bottom: 20rpx;
+				}
+
+				.small_name {
+					font-size: 28rpx;
+					color: $text-secondary;
+					text-align: center;
+				}
+			}
+		}
 	}
 
-	.system_item {
-		@include flex-column;
-		padding: 20rpx 0;
+	.bottom_grid {
+		display: grid;
+		grid-template-columns: repeat(4, 1fr);
+		gap: 16rpx;
+
+		.small_item {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+			background-color: #FFFFFF;
+			border-radius: 12rpx;
+			padding: 20rpx 0;
+
+			.small_icon {
+				width: 96rpx;
+				height: 96rpx;
+				margin-bottom: 20rpx;
+			}
 
-		.system_icon {
-			width: 96rpx;
-			height: 96rpx;
-			margin-bottom: 20rpx;
+			.small_name {
+				font-size: 28rpx;
+				color: $text-secondary;
+				text-align: center;
+			}
 		}
+	}
 
-		.system_name {
-			font-size: $font-xxs;
-			color: $text-secondary;
+	.system_grid_full {
+		display: grid;
+		grid-template-columns: repeat(4, 1fr);
+		gap: 16rpx;
+
+		.small_item {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+			background-color: #FFFFFF;
+			border-radius: 12rpx;
+			padding: 20rpx 0;
+
+			.small_icon {
+				width: 64rpx;
+				height: 64rpx;
+				margin-bottom: 12rpx;
+			}
+
+			.small_name {
+				font-size: 22rpx;
+				color: $text-secondary;
+				text-align: center;
+			}
 		}
 	}
 }

BIN
static/image/workspace/icon/icon1.png


BIN
static/image/workspace/icon/icon10.png


BIN
static/image/workspace/icon/icon2.png


BIN
static/image/workspace/icon/icon3.png


BIN
static/image/workspace/icon/icon4.png


BIN
static/image/workspace/icon/icon5.png


BIN
static/image/workspace/icon/icon6.png


BIN
static/image/workspace/icon/icon7.png


BIN
static/image/workspace/icon/icon8.png


BIN
static/image/workspace/icon/icon9.png


BIN
static/image/workspace/icon1.png


BIN
static/image/workspace/icon10.png


BIN
static/image/workspace/icon11.png


BIN
static/image/workspace/icon12.png


BIN
static/image/workspace/icon2.png


BIN
static/image/workspace/icon3.png


BIN
static/image/workspace/icon4.png


BIN
static/image/workspace/icon5.png


BIN
static/image/workspace/icon6.png


BIN
static/image/workspace/icon7.png


BIN
static/image/workspace/icon8.png


BIN
static/image/workspace/icon9.png


BIN
static/image/workspace/iconLeft.png


BIN
static/pdfh5/cmaps/78-EUC-H.bcmap


BIN
static/pdfh5/cmaps/78-EUC-V.bcmap


BIN
static/pdfh5/cmaps/78-H.bcmap


BIN
static/pdfh5/cmaps/78-RKSJ-H.bcmap


BIN
static/pdfh5/cmaps/78-RKSJ-V.bcmap


BIN
static/pdfh5/cmaps/78-V.bcmap


BIN
static/pdfh5/cmaps/78ms-RKSJ-H.bcmap


BIN
static/pdfh5/cmaps/78ms-RKSJ-V.bcmap


BIN
static/pdfh5/cmaps/83pv-RKSJ-H.bcmap


BIN
static/pdfh5/cmaps/90ms-RKSJ-H.bcmap


BIN
static/pdfh5/cmaps/90ms-RKSJ-V.bcmap


BIN
static/pdfh5/cmaps/90msp-RKSJ-H.bcmap


BIN
static/pdfh5/cmaps/90msp-RKSJ-V.bcmap


BIN
static/pdfh5/cmaps/90pv-RKSJ-H.bcmap


BIN
static/pdfh5/cmaps/90pv-RKSJ-V.bcmap


BIN
static/pdfh5/cmaps/Add-H.bcmap


BIN
static/pdfh5/cmaps/Add-RKSJ-H.bcmap


BIN
static/pdfh5/cmaps/Add-RKSJ-V.bcmap


BIN
static/pdfh5/cmaps/Add-V.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-0.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-1.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-2.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-3.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-4.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-5.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-6.bcmap


BIN
static/pdfh5/cmaps/Adobe-CNS1-UCS2.bcmap


BIN
static/pdfh5/cmaps/Adobe-GB1-0.bcmap


BIN
static/pdfh5/cmaps/Adobe-GB1-1.bcmap


BIN
static/pdfh5/cmaps/Adobe-GB1-2.bcmap


BIN
static/pdfh5/cmaps/Adobe-GB1-3.bcmap


BIN
static/pdfh5/cmaps/Adobe-GB1-4.bcmap


BIN
static/pdfh5/cmaps/Adobe-GB1-5.bcmap


BIN
static/pdfh5/cmaps/Adobe-GB1-UCS2.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-0.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-1.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-2.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-3.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-4.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-5.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-6.bcmap


BIN
static/pdfh5/cmaps/Adobe-Japan1-UCS2.bcmap


BIN
static/pdfh5/cmaps/Adobe-Korea1-0.bcmap


BIN
static/pdfh5/cmaps/Adobe-Korea1-1.bcmap


BIN
static/pdfh5/cmaps/Adobe-Korea1-2.bcmap


BIN
static/pdfh5/cmaps/Adobe-Korea1-UCS2.bcmap


BIN
static/pdfh5/cmaps/B5-H.bcmap


BIN
static/pdfh5/cmaps/B5-V.bcmap


BIN
static/pdfh5/cmaps/B5pc-H.bcmap


BIN
static/pdfh5/cmaps/B5pc-V.bcmap


BIN
static/pdfh5/cmaps/CNS-EUC-H.bcmap


BIN
static/pdfh5/cmaps/CNS-EUC-V.bcmap


BIN
static/pdfh5/cmaps/CNS1-H.bcmap


BIN
static/pdfh5/cmaps/CNS1-V.bcmap


BIN
static/pdfh5/cmaps/CNS2-H.bcmap


+ 3 - 0
static/pdfh5/cmaps/CNS2-V.bcmap

@@ -0,0 +1,3 @@
+àRCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSEáCNS2-H

BIN
static/pdfh5/cmaps/ETHK-B5-H.bcmap


BIN
static/pdfh5/cmaps/ETHK-B5-V.bcmap


BIN
static/pdfh5/cmaps/ETen-B5-H.bcmap


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