Bladeren bron

新增:我的通知、发布通知、编辑通知、新增通知功能

吴朋磊 3 weken geleden
bovenliggende
commit
aefa92e436

+ 14 - 2
components/notice-header.vue

@@ -79,10 +79,18 @@ const props = defineProps({
 	activeTab: {
 		type: Number,
 		default: -1
+	},
+	tabMode: {
+		type: String,
+		default: 'status',
+		validator: (value) => ['status', 'index'].includes(value)
 	}
 });
 
 const activeTabIndex = computed(() => {
+	if (props.tabMode === 'index') {
+		return props.activeTab;
+	}
 	const statusMap = [-1, 0, 1, 2];
 	return statusMap.indexOf(props.activeTab);
 });
@@ -110,8 +118,12 @@ onMounted(() => {
 });
 
 const handleTabChange = (idx) => {
-	const statusMap = [-1, 0, 1, 2];
-	emit('tabChange', statusMap[idx]);
+	if (props.tabMode === 'index') {
+		emit('tabChange', idx);
+	} else {
+		const statusMap = [-1, 0, 1, 2];
+		emit('tabChange', statusMap[idx]);
+	}
 };
 
 const handleTypeChange = (e) => {

+ 168 - 0
components/tree-node.vue

@@ -0,0 +1,168 @@
+<template>
+	<view class="tree-node">
+		<view v-for="item in treeData" :key="item.id" class="node-item">
+			<view class="tree_row" :style="getRowStyle()" @click="handleExpand(item)">
+				<view class="checkbox_wrap" @click.stop="handleSelect(item)">
+					<view class="checkbox" :class="{ checked: item.checked, 'half-checked': item.halfChecked }">
+						<view v-if="(item.checked || item.halfChecked) && item.children && item.children.length > 0" class="checkbox_minus"></view>
+						<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>
+				<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>
+			</view>
+
+			<view v-if="expandedIds.includes(item.id) && item.children && item.children.length > 0" class="children-container">
+				<TreeNode :tree-data="item.children" :expanded-ids="expandedIds" :level="level + 1"
+					@toggle-expand="(ids) => $emit('toggle-expand', ids)"
+					@toggle-select="(item) => $emit('toggle-select', item)" />
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'TreeNode',
+	props: {
+		treeData: {
+			type: Array,
+			default: () => []
+		},
+		expandedIds: {
+			type: Array,
+			default: () => []
+		},
+		level: {
+			type: Number,
+			default: 0
+		}
+	},
+	methods: {
+		handleExpand(item) {
+			if (item.children && item.children.length > 0) {
+				const siblingIds = this.treeData.map(n => n.id);
+				const isExpanded = this.expandedIds.includes(item.id);
+				this.$emit('toggle-expand', { itemId: item.id, siblingIds, isExpanded });
+			}
+		},
+		handleSelect(item) {
+			this.$emit('toggle-select', item);
+		},
+		getRowStyle() {
+			return {
+				paddingLeft: `${24 + this.level * 48}rpx`
+			};
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.tree-node {
+	.node-item {
+		background-color: #FFFFFF;
+		border-bottom: 2rpx solid #F3F3F3;
+
+		&:last-child {
+			border-bottom: none;
+		}
+	}
+
+	.tree_row {
+		display: flex;
+		align-items: center;
+		padding: 24rpx;
+		padding-right: 24rpx;
+
+		.checkbox_wrap {
+			margin-right: 16rpx;
+			flex-shrink: 0;
+
+			.checkbox {
+				width: 32rpx;
+				height: 32rpx;
+				border: 2rpx solid #DCDFE6;
+				border-radius: 6rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+
+				&.checked {
+					background-color: #2E64FA;
+					border-color: #2E64FA;
+
+					.checkbox_minus {
+						width: 16rpx;
+						height: 0;
+						border-top: 2rpx solid #FFFFFF;
+						border-bottom: 2rpx solid #FFFFFF;
+					}
+
+					.checkbox_check_image {
+						width: 32rpx;
+						height: 32rpx;
+					}
+				}
+
+				&.half-checked {
+					background-color: #2E64FA;
+					border-color: #2E64FA;
+
+					.checkbox_minus {
+						width: 16rpx;
+						height: 0;
+						border-top: 2rpx solid #FFFFFF;
+						border-bottom: 2rpx solid #FFFFFF;
+					}
+				}
+			}
+		}
+
+		.row_text {
+			flex: 1;
+			font-size: 28rpx;
+			color: #333333;
+
+			&.leaf-node {
+				color: #666666;
+			}
+		}
+
+		.expand_icon {
+				flex-shrink: 0;
+				margin-left: 8rpx;
+				width: 24rpx;
+				height: 24rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+
+				.expand-arrow {
+					width: 32rpx;
+					height: 32rpx;
+					display: inline-block;
+					transform: rotate(-90deg);
+					transition: transform 0.3s ease;
+
+					&.expanded {
+						transform: rotate(0deg);
+					}
+				}
+			}
+
+		.arrow_right {
+			font-size: 24rpx;
+			color: #999999;
+			flex-shrink: 0;
+			margin-left: 8rpx;
+		}
+	}
+
+	.children-container {
+		border-top: 2rpx solid #F3F3F3;
+	}
+}
+</style>

+ 7 - 0
pages.json

@@ -77,6 +77,13 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/notice/publish/selectObject",
+			"style": {
+				"navigationBarTitleText": "选择通知对象",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/notice/overview/noticeDetail",
 			"style": {

+ 143 - 164
pages/notice/mine/index.vue

@@ -1,41 +1,46 @@
 <template>
 	<view class="page_mine">
-		<notice-header title="我的通知"></notice-header>
+		<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>
 
-		<view class="mine_content">
-			<view class="tabs_bar">
-				<text v-for="(tab, idx) in tabs" :key="idx" class="tab_item" :class="{ active: activeTab === idx }" @click="activeTab = idx">{{ tab }}</text>
-			</view>
-
-			<scroll-view scroll-y class="notice_list">
-				<view v-for="item in noticeList" :key="item.id" class="notice_item">
-					<view class="notice_content">
+		<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="notice_title_row">
-							<uni-icons type="info" size="20" color="#4A6CF7"></uni-icons>
+							<image class="notice_icon" :src="item.icon" mode="aspectFit"></image>
 							<text class="notice_title">{{ item.title }}</text>
 						</view>
 						<view class="notice_meta">
-							<text class="status_tag" :class="item.statusClass">{{ item.status }}</text>
+							<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="notice_stats">
-						<text class="stats_text">已读: {{ item.readCount }}人</text>
-						<text class="stats_text">未读: {{ item.unreadCount }}人</text>
-					</view>
-					<view class="notice_actions">
-						<text v-if="item.status === '待发布'" class="action_btn delete" @click="handleDelete(item.id)">删除</text>
-						<text v-if="item.status !== '已关闭'" class="action_btn edit" @click="handleEdit(item.id)">编辑</text>
-						<text class="action_btn detail" :class="{ disabled: item.status === '待发布' }" @click="handleDetail(item.id)">详情</text>
+					<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>
-				</view>
+				</scroll-view>
 
-				<view v-if="noticeList.length === 0" class="no_data">
-					<text class="no_data_text">暂无通知</text>
-				</view>
-			</scroll-view>
+				<no-data v-if="noticeList.length === 0" text="暂无通知" />
+			</view>
 		</view>
 
 		<common-tabbar></common-tabbar>
@@ -44,10 +49,10 @@
 
 <script setup>
 import { ref } from 'vue';
-import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import NoticeHeader from '@/components/notice-header.vue';
+import NoData from '@/components/no-data.vue';
 
-const tabs = ['全部', '待发布', '已发布', '已关闭'];
+const tabs = ['未读', '已读', '全部'];
 const activeTab = ref(0);
 
 const noticeList = ref([
@@ -60,7 +65,8 @@ const noticeList = ref([
 		department: '教务处',
 		time: '2024-06-03 18:00:00',
 		readCount: 1500,
-		unreadCount: 50
+		unreadCount: 50,
+		icon: '/static/image/todo/xiaoxi.png'
 	},
 	{
 		id: '2',
@@ -71,7 +77,8 @@ const noticeList = ref([
 		department: '人事处',
 		time: '2024-06-02 10:00:00',
 		readCount: 280,
-		unreadCount: 15
+		unreadCount: 15,
+		icon: '/static/image/todo/xiaoxi.png'
 	},
 	{
 		id: '3',
@@ -82,14 +89,30 @@ const noticeList = ref([
 		department: '校长办公室',
 		time: '2024-06-01 09:00:00',
 		readCount: 0,
-		unreadCount: 0
+		unreadCount: 0,
+		icon: '/static/image/todo/xiaoxi.png'
 	}
 ]);
 
+const getDeleteClass = (item) => {
+	if (item.noticeStatus !== 2) {
+		return 'delete disabled';
+	}
+	return 'delete';
+};
+
 const goBack = () => {
 	uni.navigateBack();
 };
 
+const handleTabChange = (idx) => {
+	activeTab.value = idx;
+};
+
+const handleTypeChange = () => {};
+const handleTimeChange = () => {};
+const handleSearchChange = () => {};
+
 const handleDelete = (id) => {
 	uni.showModal({
 		title: '确认删除',
@@ -119,67 +142,43 @@ const handleDetail = (id) => {
 
 <style lang="scss">
 .page_mine {
-	min-height: 100vh;
-	background-color: #F5F5F5;
-	padding-bottom: 120rpx;
-}
-
-.mine_content {
-	padding-top: 100rpx;
 }
 
-.tabs_bar {
-	display: flex;
-	padding: 20rpx 24rpx;
-	background-color: #FFFFFF;
-	gap: 40rpx;
-
-	.tab_item {
-		font-size: 28rpx;
-		color: #666666;
-		position: relative;
-
-		&.active {
-			color: #2E64FA;
-			font-weight: 600;
-
-			&::after {
-				content: '';
-				position: absolute;
-				bottom: -16rpx;
-				left: 0;
-				right: 0;
-				height: 4rpx;
-				background-color: #2E64FA;
-				border-radius: 2rpx;
-			}
-		}
-	}
+.notice_content {
+	padding-top: 325rpx;
+	padding-bottom: 104px;
 }
 
 .notice_list {
-	height: calc(100vh - 320rpx);
-	padding: 16rpx 24rpx;
+	padding: 0rpx 24rpx;
+	box-sizing: border-box;
 }
 
 .notice_item {
-	background-color: #FFFFFF;
-	border-radius: 12rpx;
-	padding: 20rpx;
-	margin-bottom: 16rpx;
+	background-color: #F9FAFF;
+	border-radius: 20rpx;
+	margin-bottom: 24rpx;
+	border: 2rpx solid #E4E7ED;
+
+	.item_content {
+		padding: 24rpx;
 
-	.notice_content {
 		.notice_title_row {
 			display: flex;
-			align-items: flex-start;
-			margin-bottom: 12rpx;
+			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;
-				font-weight: 500;
-				margin-left: 8rpx;
 				overflow: hidden;
 				text-overflow: ellipsis;
 				white-space: nowrap;
@@ -190,25 +189,29 @@ const handleDetail = (id) => {
 			display: flex;
 			align-items: center;
 			flex-wrap: wrap;
-			gap: 12rpx;
+			gap: 16rpx;
 
 			.status_tag {
-				font-size: 22rpx;
-				padding: 4rpx 12rpx;
-				border-radius: 4rpx;
+				font-size: 24rpx;
+				height: 48rpx;
+				width: 96rpx;
+				text-align: center;
+				line-height: 48rpx;
+				border-radius: 8rpx;
+				font-weight: 400;
 
 				&.status-pending {
-					background-color: rgba(74, 108, 247, 0.1);
-					color: #4A6CF7;
+					background: rgba(46, 100, 250, 0.1);
+					color: #2E64FA;
 				}
 
 				&.status-published {
-					background-color: rgba(82, 196, 26, 0.1);
-					color: #52C41A;
+					background: rgba(82, 196, 26, 0.1);
+					color: #2BC644;
 				}
 
 				&.status-closed {
-					background-color: rgba(245, 108, 108, 0.1);
+					background: rgba(245, 108, 108, 0.1);
 					color: #F56C6C;
 				}
 			}
@@ -216,107 +219,83 @@ const handleDetail = (id) => {
 			.meta_text {
 				font-size: 24rpx;
 				color: #999999;
+				line-height: 48rpx;
 			}
 		}
 	}
 
-	.notice_stats {
-		display: flex;
-		margin-top: 12rpx;
-		padding-top: 12rpx;
-		border-top: 1rpx dashed #E8E8E8;
-
-		.stats_text {
-			font-size: 24rpx;
-			color: #999999;
-
-			&:first-child {
-				margin-right: 24rpx;
-			}
-		}
-	}
-
-	.notice_actions {
+	.item_bottom {
+		border-top: 2rpx solid #E4E7ED;
 		display: flex;
-		justify-content: flex-end;
-		gap: 16rpx;
-		margin-top: 16rpx;
-
-		.action_btn {
-			font-size: 24rpx;
-			padding: 8rpx 24rpx;
-			border-radius: 8rpx;
-
-			&.delete {
-				background-color: rgba(245, 108, 108, 0.1);
-				color: #F56C6C;
-			}
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		padding: 24rpx 16rpx;
 
-			&.edit {
-				background-color: rgba(74, 108, 247, 0.1);
-				color: #4A6CF7;
-			}
+		.notice_stats {
+			display: flex;
 
-			&.detail {
-				background-color: #2E64FA;
-				color: #FFFFFF;
+			.stats_text {
+				font-size: 24rpx;
+				color: #999999;
 
-				&.disabled {
-					background-color: #CCCCCC;
+				&:first-child {
+					margin-right: 32rpx;
 				}
 			}
 		}
-	}
-}
 
-.no_data {
-	display: flex;
-	flex-direction: column;
-	align-items: center;
-	padding: 100rpx 0;
+		.notice_actions {
+			display: flex;
+			justify-content: flex-end;
+			gap: 16rpx;
 
-	.no_data_text {
-		font-size: 28rpx;
-		color: #999999;
-	}
-}
+			.action_btn {
+				font-size: 24rpx;
+				padding: 12rpx 24rpx;
+				border-radius: 8rpx;
+				border: 2rpx solid transparent;
 
-.mine_tabbar {
-	position: fixed;
-	bottom: 0;
-	left: 0;
-	right: 0;
-	display: flex;
-	justify-content: space-around;
-	background-color: #FFFFFF;
-	padding: 16rpx 0;
-	padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
-	border-top: 1rpx solid #F0F0F0;
-	height: 84px;
-	box-sizing: border-box;
+				&.delete {
+					border-color: #F56C6C;
+					color: #F56C6C;
+					background-color: #FFFFFF;
 
-	.tabbar_item {
-		display: flex;
-		flex-direction: column;
-		align-items: center;
-		justify-content: center;
+					&.disabled {
+						border-color: #C0C4CC;
+						color: #C0C4CC;
+						background-color: #FFFFFF;
+					}
+				}
 
-		.tabbar_icon {
-			width: 48rpx;
-			height: 48rpx;
-		}
+				&.edit {
+					border-color: #2E64FA;
+					color: #2E64FA;
+					background-color: #FFFFFF;
 
-		.tabbar_text {
-			font-size: 22rpx;
-			color: #999999;
-			margin-top: 4rpx;
-		}
+					&.disabled {
+						border-color: #C0C4CC;
+						color: #C0C4CC;
+						background-color: #FFFFFF;
+					}
+				}
+
+				&.detail {
+					background-color: #2E64FA;
+					color: #FFFFFF;
 
-		&.active {
-			.tabbar_text {
-				color: #2E64FA;
+					&.disabled {
+						border-color: #C0C4CC;
+						color: #C0C4CC;
+						background-color: #FFFFFF;
+					}
+				}
 			}
 		}
 	}
+
+	&:last-child {
+		margin-bottom: 0;
+	}
 }
 </style>

+ 0 - 4
pages/notice/overview/index.vue

@@ -165,10 +165,6 @@ const handleDelete = (id) => {
 
 const handleEdit = (id) => {
 	const item = noticeList.value.find(item => item.id === id);
-	if (item && item.status === '已关闭') {
-		uni.showToast({ title: '已关闭通知无法编辑', icon: 'none' });
-		return;
-	}
 	uni.navigateTo({ url: `/pages/notice/publish/index?id=${id}` });
 };
 

+ 206 - 14
pages/notice/publish/index.vue

@@ -38,10 +38,10 @@
 						<view class="textarea_container">
 							<textarea class="form_textarea" v-model="formData.content" placeholder="请输入通知内容"
 								:maxlength="-1"></textarea>
-							<view class="textarea_actions">
+							<!-- <view class="textarea_actions">
 								<image src="/static/image/publish/full.png" class="full_icon" mode="aspectFit"></image>
 								<text class="action_text">全屏编辑</text>
-							</view>
+							</view> -->
 						</view>
 					</view>
 				</view>
@@ -66,8 +66,8 @@
 					<view class="form_content">
 						<view class="object_row">
 							<view class="object_info">
-								<text class="object_count">按部门</text>
-								<text class="object_count">已选32人</text>
+								<text class="object_count">{{ objectTypeLabel || '请选择' }}</text>
+								<text class="object_count">已选{{ selectedCount }}人</text>
 							</view>
 							<view class="object_action" @click="selectObject">
 								<text class="action_text">选择通知对象</text>
@@ -78,7 +78,7 @@
 				</view>
 			</view>
 
-			<view class="form_item">
+			<view class="form_item" v-if="isEdit">
 				<view class="form_row">
 					<text class="form_label">通知状态</text>
 					<view class="form_content">
@@ -92,12 +92,12 @@
 				</view>
 			</view>
 
-			<view class="form_item">
+			<view class="form_item" v-if="isEdit && formData.status">
 				<view class="form_row">
 					<text class="form_label">已读记录</text>
 					<view class="form_content">
 						<custom-radio v-model="saveRecord" :options="recordOptions" />
-						<text class="record_tip">说明:已读记录将被清空,重新记录</text>
+						<text class="record_tip">说明:{{ saveRecord === 0 ? '已读记录将被保留' : '已读记录将被清空,重新记录' }}</text>
 					</view>
 				</view>
 			</view>
@@ -113,14 +113,16 @@
 
 <script setup>
 import { ref, reactive, onMounted } from 'vue';
+import { 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';
 
 const showTypePicker = ref(false);
-const publishType = ref(1);
-const saveRecord = ref(1);
+const publishType = ref(0);
+const isEdit = ref(false);
+const saveRecord = ref(0);
 const typeIndex = ref(-1);
 const typeData = ref([]);
 const typeOptions = ref([]);
@@ -157,6 +159,14 @@ const formData = reactive({
 	status: true
 });
 
+const selectedCount = ref(0);
+const selectedUsers = ref([]);
+const schoolYearId = ref('');
+const noticeTypeId = ref('');
+const objectTypeLabel = ref('');
+const editId = ref('');
+const originalNoticeType = ref(0);
+
 const fetchNoticeTypeList = async () => {
 	try {
 		const res = await overviewApi.noticeTypeList();
@@ -174,6 +184,75 @@ const fetchNoticeTypeList = async () => {
 
 onMounted(() => {
 	fetchNoticeTypeList();
+	
+	const pages = getCurrentPages();
+	const currentPage = pages[pages.length - 1];
+	if (currentPage.options && currentPage.options.id) {
+		isEdit.value = true;
+		editId.value = currentPage.options.id;
+		fetchNoticeDetail(currentPage.options.id);
+	}
+});
+
+const fetchNoticeDetail = async (id) => {
+	try {
+		const res = await overviewApi.notices_edit({ id });
+		if (res.data) {
+			const data = res.data;
+				formData.title = data.noticeName || '';
+			const content = data.noticeContent || '';
+			formData.content = content.replace(/<[^>]+>/g, '');
+			formData.status = data.isClose !== true;
+			noticeTypeId.value = data.typeId || '';
+			
+			const typeIndexVal = typeData.value.findIndex(item => item.id === data.typeId);
+			typeIndex.value = typeIndexVal >= 0 ? typeIndexVal : -1;
+			
+			publishType.value = data.sendTimeType === 2 ? 1 : 0;
+			publishDateTime.value = data.releaseTime || getCurrentDateTime();
+			
+			saveRecord.value = data.isSave === true ? 0 : 1;
+			
+			if (data.noticeType) {
+				const typeMap = {
+					1: '按部门',
+					2: '按学科',
+					3: '按年级',
+					4: '按权限',
+					5: '按学生'
+				};
+				objectTypeLabel.value = typeMap[data.noticeType] || '';
+				originalNoticeType.value = data.noticeType;
+			}
+			
+			if (data.userList && Array.isArray(data.userList)) {
+				selectedUsers.value = data.userList.map(user => ({
+					id: user.userId,
+					name: user.thirdName
+				}));
+				selectedCount.value = selectedUsers.value.length;
+			}
+		}
+	} catch (error) {
+		console.error('获取通知详情失败:', error);
+	}
+};
+
+onShow(() => {
+	const result = uni.getStorageSync('selectObjectResult');
+	if (result) {
+		try {
+			const data = JSON.parse(result);
+			if (data) {
+				objectTypeLabel.value = data.tab;
+				selectedCount.value = data.count;
+				selectedUsers.value = data.users;
+			}
+		} catch (e) {
+			console.error('解析选择结果失败:', e);
+		}
+		uni.removeStorageSync('selectObjectResult');
+	}
 });
 
 const handleTypeChange = (e) => {
@@ -181,6 +260,7 @@ const handleTypeChange = (e) => {
 	const selectedItem = typeData.value[typeIndex.value];
 	if (selectedItem) {
 		formData.noticeType = selectedItem.name;
+		noticeTypeId.value = selectedItem.id;
 	}
 };
 
@@ -190,13 +270,53 @@ const goBack = () => {
 
 const onStatusChange = (e) => {
 	formData.status = e.detail.value;
+	if (formData.status) {
+		saveRecord.value = 0;
+	}
 };
 
 const selectObject = () => {
-	uni.showToast({ title: '选择通知对象', icon: 'none' });
+	const lastSelection = {
+		tab: objectTypeLabel.value,
+		users: selectedUsers.value,
+		noticeType: originalNoticeType.value || getNoticeTypeValue(objectTypeLabel.value),
+		isEdit: isEdit.value
+	};
+	uni.setStorageSync('selectObjectLastSelection', JSON.stringify(lastSelection));
+	uni.navigateTo({ url: '/pages/notice/publish/selectObject' });
 };
 
-const handleSave = () => {
+const handleObjectSelect = (data) => {
+	if (data) {
+		objectTypeLabel.value = data.tab;
+		selectedCount.value = data.count;
+		selectedUsers.value = data.users;
+	}
+};
+
+const getCurrentTime = () => {
+	const now = new Date();
+	const year = now.getFullYear();
+	const month = String(now.getMonth() + 1).padStart(2, '0');
+	const day = String(now.getDate()).padStart(2, '0');
+	const hours = String(now.getHours()).padStart(2, '0');
+	const minutes = String(now.getMinutes()).padStart(2, '0');
+	const seconds = String(now.getSeconds()).padStart(2, '0');
+	return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+};
+
+const getNoticeTypeValue = (tab) => {
+	const map = {
+		'按部门': 1,
+		'按学科': 2,
+		'按年级': 3,
+		'按权限': 4,
+		'按学生': 5
+	};
+	return map[tab] || 1;
+};
+
+const handleSave = async () => {
 	if (!formData.title) {
 		uni.showToast({ title: '请输入通知标题', icon: 'none' });
 		return;
@@ -205,7 +325,78 @@ const handleSave = () => {
 		uni.showToast({ title: '请输入通知内容', icon: 'none' });
 		return;
 	}
-	uni.showToast({ title: '发布成功', icon: 'success' });
+	if (!objectTypeLabel.value) {
+		uni.showToast({ title: '请选择通知对象', icon: 'none' });
+		return;
+	}
+	if (selectedUsers.value.length === 0) {
+		uni.showToast({ title: '请选择通知人员', icon: 'none' });
+		return;
+	}
+	
+	if (!schoolYearId.value) {
+		try {
+			const res = await overviewApi.findSchoolYear();
+			if (res.data && res.data.length > 0) {
+				schoolYearId.value = res.data[0].id;
+			}
+		} catch (error) {
+			console.error('获取学年失败:', error);
+			uni.showToast({ title: '获取学年信息失败', icon: 'none' });
+			return;
+		}
+	}
+	
+	uni.showLoading({ title: '发布中...' });
+	
+	try {
+		const objectType = getNoticeTypeValue(objectTypeLabel.value);
+		const params = {
+			typeId: noticeTypeId.value || '',
+			noticeName: formData.title,
+			noticeContent: btoa(unescape(encodeURIComponent(formData.content))),
+			sendTimeType: publishType.value + 1,
+			releaseTime: publishType.value === 0 ? getCurrentTime() : publishDateTime.value,
+			noticeType: objectType,
+			isClose: isEdit.value ? !formData.status : false,
+			isSave: isEdit.value ? (saveRecord.value === 0) : false,
+			fileList: []
+		};
+		
+		if (isEdit.value && editId.value) {
+			params.id = editId.value;
+		}
+		
+		const userList = selectedUsers.value.map(user => {
+			const userInfo = {
+				userType: objectType === 5 ? 2 : 1,
+				userId: user.id,
+				thirdName: user.name.split('(')[0].trim(),
+				thirdCode: user.name.match(/\((.*?)\)/)?.[1] || ''
+				
+			};
+			return userInfo;
+		});
+		
+		if (userList.length > 0) {
+			params.userList = userList;
+		}
+		
+		const res = await overviewApi.save_notices(params);
+		if (res.code === 200) {
+			uni.showToast({ title: '发布成功', icon: 'success' });
+			setTimeout(() => {
+				uni.redirectTo({ url: '/pages/notice/overview/index' });
+			}, 1500);
+		} else {
+			uni.showToast({ title: res.message || '发布失败', icon: 'none' });
+		}
+	} catch (error) {
+		console.error('发布失败:', error);
+		uni.showToast({ title: '发布失败', icon: 'none' });
+	} finally {
+		uni.hideLoading();
+	}
 };
 </script>
 
@@ -390,7 +581,8 @@ const handleSave = () => {
 		}
 
 		:deep(.uni-date-x) {
-			padding: 0 16rpx;
+			padding: 0 16rpx;	
+			height: 48rpx;
 		}
 
 		:deep(.uni-date-x--text) {
@@ -448,7 +640,7 @@ const handleSave = () => {
 
 	.switch_row {
 		display: flex;
-		align-items: center;
+		flex-direction:column;
 		// gap: 16rpx;
 
 		.switch_container {

+ 771 - 0
pages/notice/publish/selectObject.vue

@@ -0,0 +1,771 @@
+<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>
+
+		<view class="tabs_container">
+			<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>
+
+		<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>
+
+		<scroll-view scroll-y class="content_list">
+			<TreeNode :tree-data="treeData" :expanded-ids="expandedIds" @toggle-expand="toggleExpand"
+				@toggle-select="toggleSelect" />
+		</scroll-view>
+
+		<view class="select_footer">
+			<view class="confirm_btn" @click="handleConfirm">
+				<text class="btn_text">确定(已选{{ selectedCount }}人)</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<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 overview from '@/reqApi/overview.js';
+
+const activeTab = ref('department');
+const searchKeyword = ref('');
+const schoolYearId = ref('');
+const editMode = ref(false);
+const noticeType = ref(0);
+const selectedUserIds = ref([]);
+
+const tabs = [
+	{ key: 'department', label: '按部门' },
+	{ key: 'subject', label: '按学科' },
+	{ key: 'grade', label: '按年级' },
+	{ key: 'permission', label: '按权限' },
+	{ key: 'student', label: '按学生' }
+];
+
+const treeData = ref([]);
+const fullTreeData = ref([]);
+const expandedIds = ref([]);
+
+// 按 teacherId 去重用户列表
+const deduplicateUsers = (users) => {
+	if (!users || !Array.isArray(users)) return [];
+	const seen = new Set();
+	return users.filter(user => {
+		if (user.teacherId && seen.has(user.teacherId)) {
+			return false;
+		}
+		if (user.teacherId) {
+			seen.add(user.teacherId);
+		}
+		return true;
+	});
+};
+
+// 转换按部门接口数据为树形结构
+const transformDepartmentData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(dept => {
+		const groups = (dept.groupVOS || []).map(group => ({
+			id: group.groupId,
+			name: group.groupName,
+			checked: false,
+			halfChecked: false,
+			children: deduplicateUsers(group.groupUserVOS || []).map(user => ({
+				id: user.userId,
+				name: `${user.teacherName} (${user.userAccount})`,
+				userId: user.userId,
+				checked: false,
+				halfChecked: false
+			}))
+		}));
+		
+		return {
+			id: dept.id,
+			name: dept.name,
+			checked: false,
+			halfChecked: false,
+			children: groups
+		};
+	});
+};
+
+// 根据 userId 同步所有相同用户的选中状态
+const syncTeacherSelection = (userId, checked) => {
+	const syncNode = (nodes) => {
+		nodes.forEach(node => {
+			if (node.userId && node.userId === userId) {
+				node.checked = checked;
+				node.halfChecked = false;
+			}
+			if (node.children && node.children.length > 0) {
+				syncNode(node.children);
+			}
+		});
+	};
+	syncNode(treeData.value);
+};
+
+// 转换按学科的数据为树形结构
+const transformSubjectData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(subject => {
+		const grades = (subject.gradePersonVos || []).map(grade => ({
+			id: 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})`,
+				userId: user.userId,
+				checked: false,
+				halfChecked: false
+			}))
+		}));
+		
+		return {
+			id: subject.subjectCode,
+			name: subject.subjectName,
+			checked: false,
+			halfChecked: false,
+			children: grades
+		};
+	});
+};
+
+// 转换按年级的数据为树形结构
+const transformGradeData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(grade => {
+		const classTypes = (grade.classTypePersonVoList || []).map(classType => {
+			const classes = (classType.classInfoPersonVoList || []).map(cls => ({
+				id: cls.classCode,
+				name: cls.className,
+				checked: false,
+				halfChecked: false,
+				children: deduplicateUsers(cls.personVoList || []).map(user => ({
+					id: user.userId,
+					name: `${user.teacherName} (${user.userAccount})`,
+					userId: user.userId,
+					checked: false,
+					halfChecked: false
+				}))
+			}));
+			
+			return {
+				id: classType.classType,
+				name: classType.classTypeName,
+				checked: false,
+				halfChecked: false,
+				children: classes
+			};
+		});
+		
+		return {
+			id: grade.schoolYearGradeId || grade.gradeCode,
+			name: grade.gradeName,
+			checked: false,
+			halfChecked: false,
+			children: classTypes
+		};
+	});
+};
+
+// 转换按权限的数据为树形结构
+const transformPersonalData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(item => {
+		return {
+			id: item.roleId,
+			name: item.roleName,
+			checked: false,
+			halfChecked: false,
+			children: deduplicateUsers(item.personVoList || []).map(user => ({
+				id: user.userId,
+				name: `${user.teacherName} (${user.userAccount})`,
+				userId: user.userId,
+				checked: false,
+				halfChecked: false
+			}))
+		};
+	});
+};
+
+// 转换按学生的数据为树形结构
+const transformStudentData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(grade => {
+		const classes = (grade.clsVOS || []).map(cls => ({
+			id: cls.classId,
+			name: cls.className,
+			checked: false,
+			halfChecked: false,
+			children: (cls.studentTblVOS || []).map(student => ({
+				id: student.userId,
+				name: `${student.studentName} (${student.studentCode})`,
+				userId: student.userId,
+				checked: false,
+				halfChecked: false
+			}))
+		}));
+		
+		return {
+			id: grade.schoolYearGradeId || grade.gradeCode,
+			name: grade.gradeName,
+			checked: false,
+			halfChecked: false,
+			children: classes
+		};
+	});
+};
+
+// 获取学年
+const getSchoolYear = async () => {
+	try {
+		const res = await overview.findSchoolYear();
+		if (res.data && res.data.length > 0) {
+			schoolYearId.value = res.data[0].id;
+		}
+	} catch (error) {
+		console.error('获取学年失败:', error);
+	}
+};
+
+// 获取按部门数据
+const loadDepartmentData = async () => {
+	if (!schoolYearId.value) return;
+	
+	try {
+		const res = await overview.find_depart_contain_user({ schoolYearId: schoolYearId.value });
+		fullTreeData.value = transformDepartmentData(res.data);
+		treeData.value = fullTreeData.value;
+		expandedIds.value = [];
+	} catch (error) {
+		console.error('获取部门数据失败:', error);
+	}
+};
+
+// 获取按学科/年级/权限数据
+const loadPersonalData = async (type) => {
+	if (!schoolYearId.value) return;
+	
+	try {
+		const res = await overview.find_collect_personal_info({ schoolYearId: schoolYearId.value });
+		
+		let data = [];
+		let transformFn = transformPersonalData;
+		
+		if (type === 'subject') {
+			data = res.data.subjectPersonVoList || [];
+			transformFn = transformSubjectData;
+		} else if (type === 'grade') {
+			data = res.data.collectGradePersonVos || [];
+			transformFn = transformGradeData;
+		} else if (type === 'permission') {
+			data = res.data.collectPermissionPersonVos || [];
+		}
+		
+		fullTreeData.value = transformFn(data);
+		treeData.value = fullTreeData.value;
+		expandedIds.value = [];
+	} catch (error) {
+		console.error(`获取${type}数据失败:`, error);
+	}
+};
+
+// 获取按学生数据
+const loadStudentData = async () => {
+	if (!schoolYearId.value) return;
+	
+	try {
+		const res = await overview.query_notice_student({ id: 0, schoolYearId: schoolYearId.value });
+		fullTreeData.value = transformStudentData(res.data || []);
+		treeData.value = fullTreeData.value;
+		expandedIds.value = [];
+	} catch (error) {
+		console.error('获取学生数据失败:', error);
+	}
+};
+
+// 切换标签
+const handleTabChange = async (key) => {
+	activeTab.value = key;
+	
+	if (key === 'department') {
+		await loadDepartmentData();
+	} else if (key === 'subject' || key === 'grade' || key === 'permission') {
+		await loadPersonalData(key);
+	} else if (key === 'student') {
+		await loadStudentData();
+	}
+};
+
+const getDescendantIds = (nodes, parentId) => {
+	const ids = [];
+	const findDescendants = (items, targetId) => {
+		items.forEach(item => {
+			if (item.id === targetId && item.children && item.children.length > 0) {
+				const collectIds = (children) => {
+					children.forEach(child => {
+						ids.push(child.id);
+						if (child.children && child.children.length > 0) {
+							collectIds(child.children);
+						}
+					});
+				};
+				collectIds(item.children);
+			}
+			if (item.children && item.children.length > 0) {
+				findDescendants(item.children, targetId);
+			}
+		});
+	};
+	findDescendants(nodes, parentId);
+	return ids;
+};
+
+const searchTree = (keyword) => {
+	if (!keyword.trim()) {
+		treeData.value = fullTreeData.value;
+		return;
+	}
+	
+	const filterNodes = (nodes) => {
+		const filtered = [];
+		nodes.forEach(node => {
+			const hasMatch = node.name && node.name.includes(keyword);
+			let childrenFiltered = [];
+			
+			if (node.children && node.children.length > 0) {
+				childrenFiltered = filterNodes(node.children);
+			}
+			
+			if (hasMatch || childrenFiltered.length > 0) {
+				filtered.push({
+					...node,
+					children: childrenFiltered
+				});
+			}
+		});
+		return filtered;
+	};
+	
+	treeData.value = filterNodes(fullTreeData.value);
+};
+
+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];
+	}
+};
+
+// 向下级联:更新所有子节点的选中状态
+const toggleChildren = (children, checked) => {
+	children.forEach(child => {
+		child.checked = checked;
+		child.halfChecked = false;
+		if (child.children && child.children.length > 0) {
+			toggleChildren(child.children, checked);
+		}
+	});
+};
+
+// 向上级联:更新父节点的选中状态
+const updateParentStatus = () => {
+	const updateNode = (nodes) => {
+		nodes.forEach(node => {
+			if (node.children && node.children.length > 0) {
+				updateNode(node.children);
+
+				// 检查子节点的选中状态
+				const childrenChecked = node.children.filter(c => c.checked);
+				const childrenHalfChecked = node.children.filter(c => c.halfChecked);
+
+				if (childrenChecked.length === node.children.length) {
+					// 所有子节点都选中,父节点也选中
+					node.checked = true;
+					node.halfChecked = false;
+				} else if (childrenChecked.length > 0 || childrenHalfChecked.length > 0) {
+					// 部分子节点选中,父节点半选
+					node.checked = false;
+					node.halfChecked = true;
+				} else {
+					// 没有子节点选中
+					node.checked = false;
+					node.halfChecked = false;
+				}
+			}
+		});
+	};
+	updateNode(treeData.value);
+};
+
+const toggleSelect = (item) => {
+	item.checked = !item.checked;
+	item.halfChecked = false;
+
+	// 如果是叶子节点(用户),同步所有相同 userId 的节点
+	if (!item.children || item.children.length === 0) {
+		if (item.userId) {
+			syncTeacherSelection(item.userId, item.checked);
+		}
+	} else {
+		// 向下级联
+		toggleChildren(item.children, item.checked);
+		
+		// 获取所有叶子节点的 userId 并同步到其他分组
+		const getUserIds = (nodes) => {
+			const ids = [];
+			nodes.forEach(node => {
+				if (node.userId) {
+					ids.push(node.userId);
+				} else if (node.children && node.children.length > 0) {
+					ids.push(...getUserIds(node.children));
+				}
+			});
+			return ids;
+		};
+		
+		const userIds = getUserIds(item.children);
+		userIds.forEach(id => {
+			syncTeacherSelection(id, item.checked);
+		});
+	}
+
+	// 向上级联
+	updateParentStatus();
+};
+
+const selectedCount = computed(() => {
+	const selectedUserIds = new Set();
+	const collectChecked = (items) => {
+		items.forEach(item => {
+			if (item.children) {
+				collectChecked(item.children);
+			} else if (item.checked && item.userId) {
+				selectedUserIds.add(item.userId);
+			}
+		});
+	};
+	collectChecked(treeData.value);
+	return selectedUserIds.size;
+});
+
+const goBack = () => {
+	uni.navigateBack();
+};
+
+const collectSelectedUsers = () => {
+	const selectedUsers = [];
+	const collect = (nodes) => {
+		nodes.forEach(node => {
+			if (node.children && node.children.length > 0) {
+				collect(node.children);
+			} else if (node.checked && node.userId) {
+				selectedUsers.push({
+					id: node.id,
+					name: node.name,
+					userId: node.userId
+				});
+			}
+		});
+	};
+	collect(treeData.value);
+	return selectedUsers;
+};
+
+const handleConfirm = () => {
+	const data = {
+		tab: tabs.find(t => t.key === activeTab.value).label,
+		count: selectedCount.value,
+		users: collectSelectedUsers()
+	};
+	
+	uni.setStorageSync('selectObjectResult', JSON.stringify(data));
+	uni.navigateBack();
+};
+
+const setSelectedUsers = (users) => {
+	if (!users || !Array.isArray(users)) return;
+	
+	const selectedTeacherIds = new Set(users.map(u => u.teacherId));
+	
+	const setNode = (nodes) => {
+		nodes.forEach(node => {
+			if (node.children && node.children.length > 0) {
+				setNode(node.children);
+			} else if (node.teacherId && selectedTeacherIds.has(node.teacherId)) {
+				node.checked = true;
+			}
+		});
+	};
+	
+	setNode(treeData.value);
+	updateParentStatus();
+};
+
+const setSelectedUsersById = (userIds) => {
+	if (!userIds || !Array.isArray(userIds)) return;
+	
+	const selectedIds = new Set(userIds);
+	
+	const setNode = (nodes) => {
+		nodes.forEach(node => {
+			if (node.children && node.children.length > 0) {
+				setNode(node.children);
+			} else if (node.id && selectedIds.has(node.id)) {
+				node.checked = true;
+				if (node.teacherId) {
+					syncTeacherSelection(node.teacherId, true);
+				}
+			}
+		});
+	};
+	
+	setNode(treeData.value);
+	updateParentStatus();
+};
+
+onMounted(async () => {
+	const pages = getCurrentPages();
+	if (pages.length === 1) {
+		uni.redirectTo({ url: '/pages/notice/publish/index' });
+		return;
+	}
+	
+	const lastSelection = uni.getStorageSync('selectObjectLastSelection');
+	if (lastSelection) {
+		try {
+			const data = JSON.parse(lastSelection);
+			
+			if (data.isEdit) {
+				editMode.value = true;
+			}
+			
+			if (data.noticeType) {
+				noticeType.value = data.noticeType;
+				const typeMap = {
+					1: 'department',
+					2: 'subject',
+					3: 'grade',
+					4: 'permission',
+					5: 'student'
+				};
+				const tabKey = typeMap[data.noticeType];
+				if (tabKey) {
+					activeTab.value = tabKey;
+				}
+			} else if (data.tab) {
+				const tab = tabs.find(t => t.label === data.tab);
+				if (tab) {
+					activeTab.value = tab.key;
+				}
+			}
+			
+			if (data.users) {
+				selectedUserIds.value = data.users.map(u => u.id);
+			}
+		} catch (e) {
+			console.error('解析上次选择失败:', e);
+		}
+	}
+	
+	await getSchoolYear();
+	
+	if (activeTab.value === 'department') {
+		await loadDepartmentData();
+	} else if (activeTab.value === 'subject' || activeTab.value === 'grade' || activeTab.value === 'permission') {
+		await loadPersonalData(activeTab.value);
+	} else if (activeTab.value === 'student') {
+		await loadStudentData();
+	}
+	
+	if (selectedUserIds.value.length > 0) {
+		setSelectedUsersById(selectedUserIds.value);
+	} else if (lastSelection) {
+		try {
+			const data = JSON.parse(lastSelection);
+			if (data.users) {
+				setSelectedUsers(data.users);
+			}
+		} catch (e) {
+			console.error('设置选中状态失败:', e);
+		}
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.page_select_object {
+	display: flex;
+	flex-direction: column;
+	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;
+	left: 0;
+	right: 0;
+	z-index: 99;
+	display: flex;
+	padding: 24rpx;
+	background-color: #FFFFFF;
+	justify-content: space-between;
+
+	.tab_item {
+		flex-shrink: 0;
+		width: 122rpx;
+		height: 72rpx;
+		background: #FFFFFF;
+		border-radius: 8rpx;
+		border: 2rpx solid #DCDFE6;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		&.active {
+			background: #2E64FA;
+			border-color: #2E64FA;
+
+			.tab_text {
+				color: #FFFFFF;
+			}
+		}
+
+		.tab_text {
+			font-weight: 400;
+			font-size: 28rpx;
+			color: #333333;
+		}
+
+		&.disabled {
+			pointer-events: none;
+			opacity: 0.5;
+		}
+	}
+}
+
+.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;
+}
+
+.select_footer {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	padding: 24rpx;
+	padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+	background-color: #FFFFFF;
+	border-top: 2rpx solid #F3F3F3;
+
+	.confirm_btn {
+		width: 100%;
+		height: 90rpx;
+		background: #2E64FA;
+		border-radius: 8rpx;
+		text-align: center;
+		line-height: 90rpx;
+
+		.btn_text {
+			font-size: 32rpx;
+			font-weight: 500;
+			color: #FFFFFF;
+		}
+	}
+}
+</style>

+ 47 - 4
reqApi/overview.js

@@ -5,12 +5,55 @@ const overview = {
 	noticeTypeList() {
 		return request.get('/api/v1/schoolNotice/notice_type_list')
 	},
-    // 通知总览列表
-    noticesPage(data) {
-        return request.post('/api/v1/schoolNotice/notices_page', data)
-    },
+	// 通知总览列表
+	noticesPage(data) {
+		return request.post('/api/v1/schoolNotice/notices_page', data)
+	},
 	noticeDetail(data) {
 		return request.get(`/api/v1/schoolNotice/user_page/${data.id}`, data)
+	},
+	// 获取学年
+	findSchoolYear() {
+		return request.get('/api/v1/pc_school_year/find?effective=true')
+	},
+	// 按部门-人员查询
+	find_depart_contain_user(params) {
+		return request.get('/api/v1/teaching_plan/collect/target_loading/find_depart_contain_user', params)
+	},
+	// 按学科、按年级、按权限-人员查询
+	find_collect_personal_info(params) {
+		return request.get('/api/v1/teaching_plan/collect/target_loading/find_collect_personal_info', params)
+	},
+	// 按学生-人员查询
+	query_notice_student(params) {
+		return request.get('/api/v1/schoolNotice/query_notice_student', params)
+	},
+	// 保存通知
+	save_notices(data) {
+		return request.post('/api/v1/schoolNotice/save_notices', data)
+	},
+	// 管理员编辑-查看详情
+	notices_edit(params) {
+		return request.get(`/api/v1/schoolNotice/notices_edit/${params.id}`)
+	},
+	// 管理员-删除关闭的通知
+	delete_notices(params) {
+		return request.get(`/api/v1/schoolNotice/del_notices/${params.id}`, params)
+	},
+	// 管理员-查看教师列表
+	user_page(params) {
+		return request.get(`/api/v1/schoolNotice/user_page/${params.id}`, params)
+	},
+
+	// 教师列表
+	teacher_notices_page(params) {
+		return request.post('/api/v1/schoolNotice/teacher_notices_page', params)
+	},
+
+	// 教师-查看详情
+	notices_detail(params) {
+		return request.get(`/api/v1/schoolNotice/notices_detail/${params.id}`)
 	}
+
 }
 export default overview

BIN
static/image/icon/search.png


BIN
static/image/icon/xiala.png


BIN
static/image/publish/correct.png


+ 6 - 0
store/modules/user.js

@@ -70,6 +70,12 @@ const userStore = {
 					schoolName:res.data.schoolName || '',//学校名称
 					schoolId:res.data.schoolId || '',//学校id
 					userImage:res.data.headPic || '',//用户头像
+					roleCodes:res.data.roleCodes || '',//用户角色编码
+					roleNames:res.data.roleNames || '',//用户角色名称
+					identify:res.data.identify || '',//用户身份
+					phone:res.data.phone || '',//用户手机号
+					schoolType:res.data.schoolType || '',//学校类型
+					wxName:res.data.wxName || '',//用户微信昵称
 					// userSex:res.data.gender || 0,//用户性别
 				};
 				commit('SET_USER_INFO', userInfo);

+ 14 - 8
unpackage/dist/cache/.vite/deps/_metadata.json

@@ -1,13 +1,19 @@
 {
-  "hash": "75325abd",
-  "configHash": "1b48dfbe",
-  "lockfileHash": "4eefd62d",
-  "browserHash": "2ae6d303",
+"hash": "6fd40272",
+  "configHash": "5ccaef1e",
+  "lockfileHash": "79268a42",
+  "browserHash": "753a0eb0",  
   "optimized": {
-    "jsencrypt": {
-      "src": "../../../../../node_modules/jsencrypt/lib/index.js",
-      "file": "jsencrypt.js",
-      "fileHash": "5db203d0",
+        "@wangeditor/editor-for-vue": {
+      "src": "../../../../../node_modules/@wangeditor/editor-for-vue/dist/index.esm.js",
+      "file": "@wangeditor_editor-for-vue.js",
+      "fileHash": "11135f85",
+      "needsInterop": false
+    },
+    "uview-plus": {
+      "src": "../../../../../node_modules/uview-plus/index.js",
+      "file": "uview-plus.js",
+      "fileHash": "fa9408bf",
       "needsInterop": false
     }
   },

+ 15 - 15
unpackage/dist/cache/.vite/deps/jsencrypt.js

@@ -1,4 +1,4 @@
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/util.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsbn/util.js
 var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
 function int2char(n) {
   return BI_RM.charAt(n);
@@ -50,7 +50,7 @@ function cbit(x) {
   return r;
 }
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/base64.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsbn/base64.js
 var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 var b64pad = "=";
 function hex2b64(h) {
@@ -111,7 +111,7 @@ function b64tohex(s) {
   return ret;
 }
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/hex.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/asn1js/hex.js
 var decoder;
 var Hex = {
   decode: function(a) {
@@ -162,7 +162,7 @@ var Hex = {
   }
 };
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/base64.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/asn1js/base64.js
 var decoder2;
 var Base64 = {
   decode: function(a) {
@@ -235,7 +235,7 @@ var Base64 = {
   }
 };
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/int10.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/asn1js/int10.js
 var max = 1e13;
 var Int10 = (
   /** @class */
@@ -308,7 +308,7 @@ var Int10 = (
   }()
 );
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/asn1js/asn1.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/asn1js/asn1.js
 var ellipsis = "…";
 var reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;
 var reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/;
@@ -825,7 +825,7 @@ var ASN1Tag = (
   }()
 );
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/jsbn.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsbn/jsbn.js
 var dbits;
 var canary = 244837814094590;
 var j_lm = (canary & 16777215) == 15715070;
@@ -2304,7 +2304,7 @@ function nbits(x) {
 BigInteger.ZERO = nbv(0);
 BigInteger.ONE = nbv(1);
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/prng4.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsbn/prng4.js
 var Arcfour = (
   /** @class */
   function() {
@@ -2347,7 +2347,7 @@ function prng_newstate() {
 }
 var rng_psize = 256;
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/rng.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsbn/rng.js
 var rng_state;
 var rng_pool = null;
 var rng_pptr;
@@ -2421,7 +2421,7 @@ var SecureRandom = (
   }()
 );
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsbn/rsa.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsbn/rsa.js
 function pkcs1pad1(s, n) {
   if (n < s.length + 22) {
     console.error("Message too long for RSA");
@@ -2732,7 +2732,7 @@ function removeDigestHeader(str) {
   return str;
 }
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsrsasign/yahoo.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsrsasign/yahoo.js
 var YAHOO = {};
 YAHOO.lang = {
   /**
@@ -2788,7 +2788,7 @@ YAHOO.lang = {
   }
 };
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/lib/jsrsasign/asn1-1.0.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/lib/jsrsasign/asn1-1.0.js
 var KJUR = {};
 if (typeof KJUR.asn1 == "undefined" || !KJUR.asn1)
   KJUR.asn1 = {};
@@ -3526,7 +3526,7 @@ KJUR.asn1.DERTaggedObject = function(params) {
 };
 YAHOO.lang.extend(KJUR.asn1.DERTaggedObject, KJUR.asn1.ASN1Object);
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/JSEncryptRSAKey.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/JSEncryptRSAKey.js
 var __extends = /* @__PURE__ */ function() {
   var extendStatics = function(d, b) {
     extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) {
@@ -3700,7 +3700,7 @@ var JSEncryptRSAKey = (
   }(RSAKey)
 );
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/JSEncrypt.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/JSEncrypt.js
 var _a;
 var version = typeof process !== "undefined" ? (_a = process.env) === null || _a === void 0 ? void 0 : _a.npm_package_version : void 0;
 var JSEncrypt = (
@@ -3784,7 +3784,7 @@ var JSEncrypt = (
   }()
 );
 
-// D:/huijiaoyan/mobile8.1/workApp/node_modules/jsencrypt/lib/index.js
+// ../../../../wpl/hjy/workApp/node_modules/jsencrypt/lib/index.js
 var lib_default = JSEncrypt;
 export {
   JSEncrypt,