Selaa lähdekoodia

修改组件名称和引用,改组件名称为单驼峰命名

吴朋磊 2 viikkoa sitten
vanhempi
commit
be6a2e558b
52 muutettua tiedostoa jossa 2354 lisäystä ja 264 poistoa
  1. 23 0
      common/common.js
  2. 0 0
      components/commonHeader.vue
  3. 0 0
      components/confirmModal.vue
  4. 0 0
      components/customPopupDialog.vue
  5. 2 2
      components/navBar.vue
  6. 0 0
      components/noData.vue
  7. 4 3
      components/noticeHeader.vue
  8. 0 0
      components/pageHeader.vue
  9. 0 0
      components/pageTabbar.vue
  10. 2 2
      components/popupDialog.vue
  11. 0 0
      components/searchBar.vue
  12. 0 0
      components/treeNode.vue
  13. 183 0
      components/treeNodeNoDedup.vue
  14. 21 0
      pages.json
  15. 2 2
      pages/login/agreement.vue
  16. 3 3
      pages/login/index.vue
  17. 397 126
      pages/materialCollection/reviewMaterial/index.vue
  18. 64 28
      pages/materialCollection/submitMaterial/index.vue
  19. 697 0
      pages/materialCollection/taskOverview/components/index.vue
  20. 178 0
      pages/materialCollection/taskOverview/components/personList.vue
  21. 674 0
      pages/materialCollection/taskOverview/components/selectObjectNoDedup.vue
  22. 15 9
      pages/materialCollection/taskOverview/index.vue
  23. 4 4
      pages/message/index.vue
  24. 2 2
      pages/my/index.vue
  25. 2 2
      pages/my/updatePassword.vue
  26. 2 2
      pages/notice/mine/detail.vue
  27. 5 5
      pages/notice/mine/index.vue
  28. 2 2
      pages/notice/mine/preview.vue
  29. 6 6
      pages/notice/overview/index.vue
  30. 2 2
      pages/notice/overview/noticeDetail.vue
  31. 2 2
      pages/notice/publish/index.vue
  32. 4 4
      pages/notice/publish/personList.vue
  33. 4 4
      pages/notice/publish/selectObject.vue
  34. 2 2
      pages/studentStudy/courseCenter/index.vue
  35. 2 2
      pages/studentStudy/index.vue
  36. 2 2
      pages/studentStudy/learningMonitor/index.vue
  37. 2 2
      pages/studentStudy/learningMonitor/taskDetails.vue
  38. 2 2
      pages/studentStudy/myLearning/index.vue
  39. 4 4
      pages/studentStudy/videoDetails.vue
  40. 2 2
      pages/teacherHonor/honorOverview/honorTypeDetail.vue
  41. 8 8
      pages/teacherHonor/honorOverview/index.vue
  42. 2 2
      pages/teacherHonor/honorTeacher/index.vue
  43. 2 2
      pages/teacherHonor/index.vue
  44. 2 2
      pages/teacherStudy/courseCenter/index.vue
  45. 2 2
      pages/teacherStudy/index.vue
  46. 2 2
      pages/teacherStudy/learningMonitor/index.vue
  47. 2 2
      pages/teacherStudy/learningMonitor/taskDetails.vue
  48. 2 2
      pages/teacherStudy/myLearning/index.vue
  49. 4 4
      pages/teacherStudy/videoDetails.vue
  50. 4 4
      pages/todo/index.vue
  51. 4 4
      pages/workspace/index.vue
  52. 4 4
      pages/workspace/schedule.vue

+ 23 - 0
common/common.js

@@ -118,4 +118,27 @@ export function  getFormatNumber(numberStr) {
     formatted = formatted.replace(/\.?0+$/, '');
     
     return formatted;
+}
+
+export function remainingDays(endTime) {
+    if (!endTime) return null;
+
+    const MS_PER_DAY = 24 * 60 * 60 * 1000;
+
+    // 1) 将结束时间转为 Date 对象
+    const end = new Date(endTime);
+    if (isNaN(end.getTime())) return null;
+
+    // 2) 取今天的"零点"日期(精确到日,不考虑时分秒)
+    const today = new Date();
+    const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
+
+    // 3) 取结束日期的"零点"
+    const endStart = new Date(end.getFullYear(), end.getMonth(), end.getDate());
+
+    // 4) 计算天数差并 +1(含当天和结束天)
+    const diffDays = Math.round((endStart.getTime() - todayStart.getTime()) / MS_PER_DAY);
+
+    if (diffDays < 0) return null;   // 已过期:不显示剩余时间
+    return diffDays + 1;             // 18号到22号:22-18=4,加1后=5天
 }

+ 0 - 0
components/common-header.vue → components/commonHeader.vue


+ 0 - 0
components/confirm-modal.vue → components/confirmModal.vue


+ 0 - 0
components/custom-popup-dialog.vue → components/customPopupDialog.vue


+ 2 - 2
components/nav-bar.vue → components/navBar.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<uni-nav-bar :class="{'custom_uni_nav_bar':!onlyTitle,'custom_navbar_only_title':onlyTitle}"
 		:height="`${height}rpx`" :backgroundColor="backgroundColor" :title="title" color="#333333" :fixed="true"
@@ -35,7 +35,7 @@
 </template>
 
 <script setup>
-	import uniNavBar from '@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue';
+	import uniNavBar from '@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-navBar.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
 	import {

+ 0 - 0
components/no-data.vue → components/noData.vue


+ 4 - 3
components/notice-header.vue → components/noticeHeader.vue

@@ -534,9 +534,10 @@ const handleStageChange = (idx) => {
 		padding: 4rpx 24rpx 16rpx 24rpx;
 
 		.tab_item {
-			font-size: 30rpx;
-			margin-right: 40rpx;
-
+			font-size: 28rpx;
+			margin-right: 48rpx;
+			color: #999999;
+			font-weight: 400;
 			&.active {
 				color: #2E64FA;
 				font-weight: 500;

+ 0 - 0
components/page-header.vue → components/pageHeader.vue


+ 0 - 0
components/page-tabbar.vue → components/pageTabbar.vue


+ 2 - 2
components/popup-dialog.vue → components/popupDialog.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 提示框 可自定义内容-->
 	<uni-popup ref="popupDialog" type="dialog">
 		<uni-popup-dialog :class="{'white':!showTitle,'footBorder':showFootBorder}" :showClose="showCloseBtn" :style="{'width': `${dialogWidth}rpx`}" @confirm="DialogConfirm" @close="CloseDialog">
@@ -17,7 +17,7 @@
 
 <script setup>
 	import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue';
-	import uniPopupDialog from '@/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue';
+	import uniPopupDialog from '@/uni_modules/uni-popup/components/uni-popup-dialog/uni-popupDialog.vue';
 	import { ref } from 'vue';
 	const props = defineProps({
 		showTitle:{//是否显示标题

+ 0 - 0
components/search-bar.vue → components/searchBar.vue


+ 0 - 0
components/tree-node.vue → components/treeNode.vue


+ 183 - 0
components/treeNodeNoDedup.vue

@@ -0,0 +1,183 @@
+<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>
+				<view class="row_text_container">
+					<text class="row_text" :class="{ 'leaf-node': !item.children || item.children.length === 0 }">{{ item.name }}</text>
+					<text v-if="item.userAccount" class="row_account">({{ item.userAccount }})</text>
+				</view>
+				<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">
+				<TreeNodeNoDedup :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: 'TreeNodeNoDedup',
+	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_container {
+			flex: 1;
+			display: flex;
+			align-items: center;
+			overflow: hidden;
+		}
+
+		.row_text {
+			font-size: 28rpx;
+			color: #333333;
+
+			&.leaf-node {
+				color: #666666;
+			}
+		}
+
+		.row_account {
+			font-size: 28rpx;
+			color: #666666;
+			margin-left: 12rpx;
+		}
+
+		.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>

+ 21 - 0
pages.json

@@ -190,6 +190,27 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/materialCollection/taskOverview/components/index",
+			"style": {
+				"navigationBarTitleText": "新建任务",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/materialCollection/taskOverview/components/selectObjectNoDedup",
+			"style": {
+				"navigationBarTitleText": "选择通知对象",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/materialCollection/taskOverview/components/personList",
+			"style": {
+				"navigationBarTitleText": "人员列表",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/materialCollection/submitMaterial/index",
 			"style": {

+ 2 - 2
pages/login/agreement.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="page_body">
 		<!-- 导航 -->
 		<nav-bar height="88" backPath="" :showHeaderborder="false" title="用户协议和隐私政策" @GoBack="GoBack"></nav-bar>
@@ -52,7 +52,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	//返回
 	const GoBack = () => {
 		uni.navigateTo({

+ 3 - 3
pages/login/index.vue

@@ -34,7 +34,7 @@
 		</view>
 	</view>
 	<!-- 提示框 -->
-	<popup-dialog ref="popupDialogRef" popType="custom" title="选择学校" @DialogConfirm="DialogConfirm">
+	<popupDialog ref="popupDialogRef" popType="custom" title="选择学校" @DialogConfirm="DialogConfirm">
 		<view class="school_list">
 			<view :class="['list_item', { active: state.schoolId == item.schoolId }]" @click="SelectSchool(item.schoolId)"
 				v-for="item in state.schoolsList" :key="item.schoolId">
@@ -46,7 +46,7 @@
 				<uni-icons class="item_right" type="right"></uni-icons>
 			</view>
 		</view>
-	</popup-dialog>
+	</popupDialog>
 	<custom-dialog ref="popupSlideCode" title="滑动验证">
 		<view class="slide_code_content">
 			<slide-code ref="slideRef" :sliderBgUrl="state.sliderBgUrl" :sliderUUID="state.sliderUUID"
@@ -71,7 +71,7 @@
 import user from '@/reqApi/user.js';
 import { encrypt } from '@/common/crypto.js';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
-import popupDialog from '@/components/popup-dialog.vue';
+import popupDialog from '@/components/popupDialog.vue';
 import customDialog from '@/components/customDialog.vue';
 import slideCode from '@/components/slideCode.vue';
 import {

+ 397 - 126
pages/materialCollection/reviewMaterial/index.vue

@@ -1,189 +1,460 @@
 <template>
-	<view class="page_review" :style="{paddingTop: statusBarHeight + 120 + 'rpx', paddingBottom: safeAreaBottom + 100 + 'rpx' }">
-		<notice-header title="审核材料" mode="review">
-		</notice-header>
-
-		<view class="review_content">
-			<view class="review_list">
-				<scroll-view v-if="reviewList.length > 0" scroll-y>
-					<view v-for="item in reviewList" :key="item.id" class="review_item">
-						<view class="item_header">
-							<text class="submitter_name">{{ item.submitter }}</text>
-							<view class="review_status" :class="item.statusClass">
-								<text>{{ item.status }}</text>
-							</view>
+	<view class="page_notice" :style="{paddingTop: statusBarHeight + 430 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
+		<noticeHeader 
+			title="审核材料" 
+			mode="material"
+			tabMode="index"
+			:showSearch="true" 
+			:showFilter="true" 
+			:showStageBtn="true"
+			:showTabs="true" 
+			:showCreateBtn="true"
+			:tabs="tabs"
+			:activeTab="activeTab"
+			:academicYearOptions="academicYearOptions"
+			:academicYearIds="academicYearIds"
+			:categoryData="categoryData"
+			@tabChange="handleTabChange" 
+			@academicYearChange="handleAcademicYearChange"
+			@categoryChange="handleCategoryChange"
+			@materialTypeChange="handleMaterialTypeChange"
+			@searchChange="handleSearchChange"
+			@stageChange="handleStageChange"
+			@create="handleCreate">
+		</noticeHeader>
+
+		<view class="notice_content">
+			<view class="notice_list">
+				<scroll-view scroll-y v-for="item in taskList" :key="item.id" class="notice_item">
+					<!-- 任务内容 -->
+					<view class="item_content">
+						<view class="notice_title_row">
+							<image class="notice_icon" src="/static/image/materialCollection/docIcon.png" mode="aspectFit"></image>
+							<text class="notice_title">{{ item.taskName }}</text>
 						</view>
-						<view class="item_info">
-							<text class="info_text">提交时间: {{ item.submitTime }}</text>
-							<text class="info_text">材料: {{ item.materialCount }}个</text>
+						<view class="notice_meta">
+							<view class="status_tag" :class="statusClassMap[item.taskStatus] || ''">
+								<text>{{ taskStatusMap[item.taskStatus] }}</text>
+							</view>
+							<text class="meta_text ">{{ truncateText(item.departmentName, 5) }}</text>
+							<text class="meta_text ">{{ truncateText(item.materialCategoryName, 5) }}</text>
+							<text class="meta_text">{{ truncateText(item.createdBy, 5) }}</text>
 						</view>
-						<view class="item_actions">
-							<view class="action_btn reject" @click="handleReject(item)">
-								<text>驳回</text>
+					</view>
+					<!-- 任务底部 -->
+					<view class="item_bottom">
+						<view class="notice_stats">
+							<view class="status_tag" :class="tabStatusClassMap[activeTab] || ''">
+								<text>{{ tabs[activeTab] }}: {{ item.taskTargetVos.length }}</text>
 							</view>
-							<view class="action_btn approve" @click="handleApprove(item)">
-								<text>通过</text>
+							<text class="stats_text">剩余时间: {{remainingDays(item.endTime) }}天</text>
+						</view>
+						<view class="notice_actions">
+							<view class="action_btn edit" >
+								<text>查看</text>
 							</view>
 						</view>
 					</view>
 				</scroll-view>
 
-				<no-data v-else centered text="暂无待审核材料" />
+				<noData v-if="taskList.length === 0" centered text="暂无审核材料" />
 			</view>
 		</view>
+
+		<common-tabbar></common-tabbar>
 	</view>
 </template>
 
 <script setup>
 import { ref, onMounted } from 'vue';
-import NoticeHeader from '@/components/notice-header.vue';
-import NoData from '@/components/no-data.vue';
+import NoticeHeader from '@/components/noticeHeader.vue';
+import CommonTabbar from '@/components/commonTabbar.vue';
+import NoData from '@/components/noData.vue';
 import { useSafeArea } from '@/common/safeArea';
+import notification from '@/reqApi/notification.js';
+import {truncateText, remainingDays } from '@/common/common.js';
 
 const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
-const reviewList = ref([
-	{
-		id: 1,
-		submitter: '张老师',
-		status: '待审核',
-		statusClass: 'status-pending',
-		submitTime: '2025-07-28 10:30',
-		materialCount: 5
-	},
-	{
-		id: 2,
-		submitter: '李老师',
-		status: '待审核',
-		statusClass: 'status-pending',
-		submitTime: '2025-07-28 11:20',
-		materialCount: 3
-	},
-	{
-		id: 3,
-		submitter: '王老师',
-		status: '待审核',
-		statusClass: 'status-pending',
-		submitTime: '2025-07-27 16:45',
-		materialCount: 8
+const tabs = ['待提交', '审核中', '已驳回', '已提交'];
+const activeTab = ref(0);
+const stageIndex = ref(0);
+const academicYearIndex = ref(0);
+const academicYearOptions = ref(['']);
+const academicYearIds = ref(['']);
+const academicYearCodes = ref(['']);
+const currentSchoolYearId = ref('');
+const currentSchoolYearCode = ref('');
+const categoryIndex = ref(0);
+const categoryId = ref('');
+const materialTypeIndex = ref(0);
+const materialTypeId = ref('');
+const categoryData = ref([]);
+const word = ref('');
+const taskList = ref([]);
+const statusMap = {
+	0: '待开始',
+	1: '进行中',
+	2: '已结束'
+};
+
+const statusClassMap = {
+	0: 'status_pending',
+	1: 'status_published',
+	2: 'status_closed'
+};
+
+const taskStatusMap = {
+	0: '待开始',
+	1: '进行中',
+	2: '已结束'
+}
+const tabStatusClassMap = {
+	0: 'tab_not_submit',    // 待提交
+	1: 'tab_reviewing',     // 审核中
+	2: 'tab_rejected',      // 已驳回
+	3: 'tab_submitted'      // 已提交
+};
+
+const fetchTaskList = async () => {
+	taskList.value = [];
+	try {
+		// stageIndex: 0-进行中(taskStatus=1), 1-已结束(taskStatus=2)
+		const taskStatusMap = { 0: 1, 1: 2 };
+		const params = {
+			schoolYearCode: currentSchoolYearCode.value || null,
+			schoolYearId: currentSchoolYearId.value || null,
+			departmentId: categoryId.value || null,
+			materialCategory: materialTypeId.value || null,
+			taskName: word.value || '',
+			taskStatus: taskStatusMap[stageIndex.value] || null,
+			pageNum: 1,
+			pageSize: 9999
+		};
+		const res = await notification.find_teacher_task_target_list(params);
+		if (res.data) {
+			// 根据当前选中的标签返回不同数组数据
+			const listMap = {
+				0: 'notSubmitList',     // 待提交
+				1: 'notReviewList',     // 审核中
+				2: 'reviewReasonList',  // 已驳回
+				3: 'reviewList'         // 已提交
+			};
+			const listKey = listMap[activeTab.value] || 'notSubmitList';
+			taskList.value = res.data[listKey] || [];
+		} else {
+			taskList.value = [];
+		}
+	} catch (error) {
+		console.error('获取任务列表失败:', error);
+		taskList.value = [];
 	}
-]);
+};
 
-onMounted(() => {
+const fetchSchoolYearList = async () => {
+	try {
+		const res = await notification.getSchoolYearList();
+		if (res.data && Array.isArray(res.data)) {
+			const names = res.data.map(item => item.schoolYearName);
+			const ids = res.data.map(item => item.id);
+			const codes = res.data.map(item => item.schoolYearCode);
+			academicYearOptions.value = [...names];
+			academicYearIds.value = [...ids];
+			academicYearCodes.value = [...codes];
+			// 默认选中第一个学年学期
+			if (res.data.length > 0) {
+				academicYearIndex.value = 0;
+				currentSchoolYearId.value = res.data[0].id;
+				currentSchoolYearCode.value = res.data[0].schoolYearCode;
+			}
+		}
+	} catch (error) {
+		console.error('获取学年学期列表失败:', error);
+	}
+};
+
+const fetchCategoryList = async () => {
+	try {
+		const res = await notification.getCategoryList();
+		if (res.data && Array.isArray(res.data)) {
+			categoryData.value = res.data;
+		}
+	} catch (error) {
+		console.error('获取分类列表失败:', error);
+	}
+};
+
+onMounted(async () => {
 	initSafeArea();
+	await fetchSchoolYearList();
+	fetchCategoryList();
+	fetchTaskList();
 });
 
-const handleApprove = (item) => {
-	uni.showModal({
-		title: '确认通过',
-		content: `确定要通过 ${item.submitter} 提交的材料吗?`,
-		success: (res) => {
-			if (res.confirm) {
-				uni.showToast({ title: '审核通过', icon: 'success' });
-				reviewList.value = reviewList.value.filter(i => i.id !== item.id);
-			}
-		}
+const handleTabChange = (status) => {
+	activeTab.value = status;
+	fetchTaskList();
+};
+
+const handleStageChange = (index) => {
+	stageIndex.value = index;
+	// stageIndex: 0-进行中, 1-已结束
+	// 后续可以根据阶段筛选任务列表
+	fetchTaskList();
+};
+
+const handleAcademicYearChange = (index, id) => {
+	academicYearIndex.value = index;
+	currentSchoolYearId.value = id || '';
+	currentSchoolYearCode.value = academicYearCodes.value[index] || '';
+	fetchTaskList();
+};
+// 分类选择
+const handleCategoryChange = (index, id) => {
+	categoryIndex.value = index;
+	categoryId.value = id;
+	// 清空类目选择
+	materialTypeIndex.value = 0;
+	materialTypeId.value = null;
+	fetchTaskList();
+};
+// 类目选择
+const handleMaterialTypeChange = (index, id) => {
+	materialTypeIndex.value = index;
+	materialTypeId.value = id;
+	fetchTaskList();
+};
+
+const handleSearchChange = (keyword) => {
+	word.value = keyword;
+	fetchTaskList();
+};
+
+const handleCreate = () => {
+	uni.navigateTo({
+		url: '/pages/materialCollection/submitMaterial/index'
 	});
 };
 
-const handleReject = (item) => {
-	uni.showModal({
-		title: '确认驳回',
-		content: `确定要驳回 ${item.submitter} 提交的材料吗?`,
-		success: (res) => {
-			if (res.confirm) {
-				uni.showToast({ title: '已驳回', icon: 'none' });
-				reviewList.value = reviewList.value.filter(i => i.id !== item.id);
-			}
-		}
+const handleDetail = (id) => {
+	uni.navigateTo({
+		url: `/pages/materialCollection/submitMaterial/index?id=${id}`
 	});
 };
 </script>
 
-<style lang="scss" scoped>
-.page_review {
+<style lang="scss">
+.page_notice {
 	min-height: 100vh;
-	background-color: #F5F7FA;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
+	box-sizing: border-box;
 }
 
-.review_content {
+.notice_content {
 	flex: 1;
-	padding: 24rpx;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding-left: 0rpx;
+	padding-right: 0rpx;
 }
 
-.review_list {
-	background-color: #FFFFFF;
-	border-radius: 16rpx;
-	overflow: hidden;
+.notice_list {
 	flex: 1;
+	overflow-y: auto;
+	overflow-x: hidden;
+	padding: 0 24rpx;
 	display: flex;
 	flex-direction: column;
 }
 
-.review_item {
-	padding: 24rpx;
-	border-bottom: 1rpx solid #EBEEF5;
+.notice_item {
+	background-color: #F9FAFF;
+	border-radius: 20rpx;
+	margin-bottom: 24rpx;
+	border: 2rpx solid #E4E7ED;
 
-	&:last-child {
-		border-bottom: none;
-	}
+	.item_content {
+		padding: 24rpx;
 
-	.item_header {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		margin-bottom: 16rpx;
+		.notice_title_row {
+			display: flex;
+			align-items: center;
+			margin-bottom: 16rpx;
 
-		.submitter_name {
-			font-size: 30rpx;
-			font-weight: 500;
-			color: #333333;
+			.notice_icon {
+				width: 32rpx;
+				height: 32rpx;
+				margin-right: 16rpx;
+			}
+
+			.notice_title {
+				flex: 1;
+				font-weight: 500;
+				font-size: 28rpx;
+				color: #333333;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+			}
 		}
 
-		.review_status {
-			font-size: 24rpx;
-			padding: 8rpx 16rpx;
-			border-radius: 8rpx;
+		.notice_meta {
+			display: flex;
+			align-items: center;
+			flex-wrap: wrap;
+			gap: 16rpx;
+
+			.status_tag {
+				font-size: 24rpx;
+				height: 48rpx;
+				width: 96rpx;
+				text-align: center;
+				line-height: 48rpx;
+				border-radius: 4rpx;
+				font-weight: 400;
+				border-radius: 8rpx;
+
+				&.status_pending {
+					background: rgba(46, 100, 250, 0.1);
+					color: #2E64FA;
+				}
 
-			&.status-pending {
-				background: rgba(230, 162, 60, 0.1);
-				color: #E6A23C;
+				&.status_published {
+					background: rgba(82, 196, 26, 0.1);
+					color: #2BC644;
+				}
+
+				&.status_closed {
+					background: #6666661a;
+					color: #666666;
+				}
+			}
+			.need_shenhe {
+				width: 48rpx;
+				height: 48rpx;
+				background: rgba(245,108,108,0.1);
+				border-radius: 8rpx 8rpx 8rpx 8rpx;
+				font-size: 24rpx;
+				color: #F56C6C;
+				text-align: center;
+				line-height: 48rpx;
+			}
+			.meta_text {
+				font-size: 24rpx;
+				color: #999999;
+				line-height: 48rpx;
 			}
+			.textLength {
+					max-width: 160rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
 		}
 	}
 
-	.item_info {
+	.item_bottom {
+		border-top: 2rpx solid #E4E7ED;
 		display: flex;
-		gap: 32rpx;
-		margin-bottom: 20rpx;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		padding: 24rpx 16rpx;
 
-		.info_text {
-			font-size: 26rpx;
-			color: #999999;
-		}
-	}
+		.notice_stats {
+			display: flex;
+			align-items: center;
+			gap: 16rpx;
+			.stats_text {
+				font-size: 24rpx;
+				color: #999999;
 
-	.item_actions {
-		display: flex;
-		justify-content: flex-end;
-		gap: 24rpx;
-
-		.action_btn {
-			font-size: 28rpx;
-			padding: 16rpx 32rpx;
-			border-radius: 12rpx;
-
-			&.reject {
-				background-color: #FFFFFF;
-				border: 2rpx solid #E4E7ED;
-				color: #666666;
+				&:first-child {
+					margin-right: 32rpx;
+				}
+			}
+
+			.status_tag{
+				font-size: 24rpx;
+				text-align: center;
+				padding: 8rpx 12rpx;
+				// height: 48rpx;
+				// line-height: 48rpx;
+				font-weight: 400;
+				border-radius: 8rpx;
+				&.tab_not_submit {
+					background: rgba(46,100,250,0.1);
+					color: #2E64FA;
+				}
+				&.tab_reviewing {
+					background: rgba(251,159,52,0.1);
+					color: #FB9F34;
+				}
+				&.tab_rejected {
+					background: rgba(245,108,108,0.1);
+					color: #F56C6C;
+				}
+				&.tab_submitted {
+					background: rgba(43,198,68,0.1);
+					color: #2BC644;
+				}
 			}
+		}
+
+		.notice_actions {
+			display: flex;
+			justify-content: flex-end;
+			gap: 16rpx;
+			.action_btn {
+				font-size: 24rpx;
+				padding: 12rpx 24rpx;
+				border-radius: 8rpx;
+				border: 2rpx solid transparent;
+				&.delete {
+					border-color: #F56C6C;
+					color: #F56C6C;
+					background-color: #FFFFFF;
+
+					&.disabled {
+						border-color: #C0C4CC;
+						color: #C0C4CC;
+						background-color: #FFFFFF;
+					}
+				}
+
+				&.edit {
+					border-color: #2E64FA;
+					color: #2E64FA;
+					background-color: #FFFFFF;
 
-			&.approve {
-				background-color: #2E64FA;
-				color: #FFFFFF;
+					&.disabled {
+						border-color: #C0C4CC;
+						color: #C0C4CC;
+						background-color: #FFFFFF;
+					}
+				}
+
+				&.disable {
+					color: #C0C4CC !important;
+					cursor: not-allowed;
+					border-color: #C0C4CC !important;
+				}
+				&.disableLock{
+					color: #C0C4CC !important;
+					cursor: not-allowed;
+					background-color: #F3F3F3 !important;
+					border-color: #F3F3F3 !important;
+				}
 			}
+			
 		}
 	}
+
+	&:last-child {
+		margin-bottom: 0;
+	}
 }
 </style>

+ 64 - 28
pages/materialCollection/submitMaterial/index.vue

@@ -1,8 +1,9 @@
 <template>
 	<view class="page_notice" :style="{paddingTop: statusBarHeight + 430 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
-		<notice-header 
+		<noticeHeader 
 			title="提交材料" 
 			mode="material"
+			tabMode="index"
 			:showSearch="true" 
 			:showFilter="true" 
 			:showStageBtn="true"
@@ -20,7 +21,7 @@
 			@searchChange="handleSearchChange"
 			@stageChange="handleStageChange"
 			@create="handleCreate">
-		</notice-header>
+		</noticeHeader>
 
 		<view class="notice_content">
 			<view class="notice_list">
@@ -35,34 +36,28 @@
 							<view class="status_tag" :class="statusClassMap[item.taskStatus] || ''">
 								<text>{{ taskStatusMap[item.taskStatus] }}</text>
 							</view>
-							<view class="need_shenhe" v-if="item.auditMechanism">
-								<text>审</text>
-							</view>
-							<text class="meta_text textLength">{{ item.departmentName }}</text>
-							<text class="meta_text textLength">{{ item.materialCategoryName }}</text>
-							<text class="meta_text">{{ item.createdBy }}</text>
+							<text class="meta_text ">{{ truncateText(item.departmentName, 5) }}</text>
+							<text class="meta_text ">{{ truncateText(item.materialCategoryName, 5) }}</text>
+							<text class="meta_text">{{ truncateText(item.createdBy, 5) }}</text>
 						</view>
 					</view>
 					<!-- 任务底部 -->
 					<view class="item_bottom">
 						<view class="notice_stats">
-							<text class="stats_text">开始时间: {{ item.startTime }}</text>
+							<view class="status_tag" :class="tabStatusClassMap[activeTab] || ''">
+								<text>{{ tabs[activeTab] }}: {{ item.taskTargetVos.length }}</text>
+							</view>
+							<text class="stats_text">剩余时间: {{remainingDays(item.endTime) }}天</text>
 						</view>
 						<view class="notice_actions">
-							<view class="action_btn delete" :class="{ disable: !!item.submitNum }">
-								<text>删除</text>
-							</view>
 							<view class="action_btn edit" >
-								<text>编辑</text>
-							</view>
-							<view class="action_btn edit" :class="{ disableLock: item.taskStatus === 0 }">
 								<text>查看</text>
 							</view>
 						</view>
 					</view>
 				</scroll-view>
 
-				<no-data v-if="taskList.length === 0" centered text="暂无提交材料" />
+				<noData v-if="taskList.length === 0" centered text="暂无提交材料" />
 			</view>
 		</view>
 
@@ -72,19 +67,20 @@
 
 <script setup>
 import { ref, onMounted } from 'vue';
-import NoticeHeader from '@/components/notice-header.vue';
+import NoticeHeader from '@/components/noticeHeader.vue';
 import CommonTabbar from '@/components/commonTabbar.vue';
-import NoData from '@/components/no-data.vue';
+import NoData from '@/components/noData.vue';
 import { useSafeArea } from '@/common/safeArea';
 import notification from '@/reqApi/notification.js';
+import {truncateText, remainingDays } from '@/common/common.js';
 
 const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const tabs = ['待提交', '审核中', '已驳回', '已提交'];
-const activeTab = ref(-1);
+const activeTab = ref(0);
 const stageIndex = ref(0);
 const academicYearIndex = ref(0);
-const academicYearOptions = ref(['请选择学年学期']);
+const academicYearOptions = ref(['']);
 const academicYearIds = ref(['']);
 const academicYearCodes = ref(['']);
 const currentSchoolYearId = ref('');
@@ -96,7 +92,6 @@ const materialTypeId = ref('');
 const categoryData = ref([]);
 const word = ref('');
 const taskList = ref([]);
-
 const statusMap = {
 	0: '待开始',
 	1: '进行中',
@@ -113,24 +108,40 @@ const taskStatusMap = {
 	0: '待开始',
 	1: '进行中',
 	2: '已结束'
+}
+const tabStatusClassMap = {
+	0: 'tab_not_submit',    // 待提交
+	1: 'tab_reviewing',     // 审核中
+	2: 'tab_rejected',      // 已驳回
+	3: 'tab_submitted'      // 已提交
 };
 
 const fetchTaskList = async () => {
 	taskList.value = [];
 	try {
+		// stageIndex: 0-进行中(taskStatus=1), 1-已结束(taskStatus=2)
+		const taskStatusMap = { 0: 1, 1: 2 };
 		const params = {
 			schoolYearCode: currentSchoolYearCode.value || null,
 			schoolYearId: currentSchoolYearId.value || null,
 			departmentId: categoryId.value || null,
 			materialCategory: materialTypeId.value || null,
 			taskName: word.value || '',
-			taskStatus: activeTab.value === -1 ? null : activeTab.value,
+			taskStatus: taskStatusMap[stageIndex.value] || null,
 			pageNum: 1,
 			pageSize: 9999
 		};
-		const res = await notification.find_collection_task(params);
-		if (res.data && res.data.records) {
-			taskList.value = res.data.records
+		const res = await notification.find_teacher_task_target_list(params);
+		if (res.data) {
+			// 根据当前选中的标签返回不同数组数据
+			const listMap = {
+				0: 'notSubmitList',     // 待提交
+				1: 'notReviewList',     // 审核中
+				2: 'reviewReasonList',  // 已驳回
+				3: 'reviewList'         // 已提交
+			};
+			const listKey = listMap[activeTab.value] || 'notSubmitList';
+			taskList.value = res.data[listKey] || [];
 		} else {
 			taskList.value = [];
 		}
@@ -355,7 +366,8 @@ const handleDetail = (id) => {
 
 		.notice_stats {
 			display: flex;
-
+			align-items: center;
+			gap: 16rpx;
 			.stats_text {
 				font-size: 24rpx;
 				color: #999999;
@@ -364,19 +376,43 @@ const handleDetail = (id) => {
 					margin-right: 32rpx;
 				}
 			}
+
+			.status_tag{
+				font-size: 24rpx;
+				text-align: center;
+				padding: 8rpx 12rpx;
+				// height: 48rpx;
+				// line-height: 48rpx;
+				font-weight: 400;
+				border-radius: 8rpx;
+				&.tab_not_submit {
+					background: rgba(46,100,250,0.1);
+					color: #2E64FA;
+				}
+				&.tab_reviewing {
+					background: rgba(251,159,52,0.1);
+					color: #FB9F34;
+				}
+				&.tab_rejected {
+					background: rgba(245,108,108,0.1);
+					color: #F56C6C;
+				}
+				&.tab_submitted {
+					background: rgba(43,198,68,0.1);
+					color: #2BC644;
+				}
+			}
 		}
 
 		.notice_actions {
 			display: flex;
 			justify-content: flex-end;
 			gap: 16rpx;
-
 			.action_btn {
 				font-size: 24rpx;
 				padding: 12rpx 24rpx;
 				border-radius: 8rpx;
 				border: 2rpx solid transparent;
-
 				&.delete {
 					border-color: #F56C6C;
 					color: #F56C6C;

+ 697 - 0
pages/materialCollection/taskOverview/components/index.vue

@@ -0,0 +1,697 @@
+<template>
+	<view class="page_publish" >
+		<commonHeader :title="isEdit ? '编辑任务' : '新建任务'" @back="goBack"></commonHeader>
+
+		<scroll-view scroll-y class="publish_content" :style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 'rpx' }">
+			<view class="form_item">
+				<view class="form_row form_row_type">
+					<text class="form_label">任务类型</text>
+					<view class="form_content">
+						<picker mode="selector" :range="typeOptions" :value="typeIndex" @change="handleTypeChange">
+							<view class="filter_item">
+								<text class="filter_text" :class="{ placeholder: typeIndex === -1 }">{{
+									formData.noticeType || '请选择类型' }}</text>
+								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							</view>
+						</picker>
+					</view>
+				</view>
+			</view>
+
+			<view class="form_item">
+				<view class="form_row form_row_type">
+					<text class="form_label">任务名称</text>
+					<view class="form_content">
+						<input type="text" class="form_input" v-model="formData.title" placeholder="请输入任务名称" />
+					</view>
+				</view>
+			</view>
+
+			<view class="form_item">
+				<view class="form_row">
+					<text class="form_label">任务内容</text>
+					<view class="form_content">
+						<view class="textarea_container">
+							<textarea class="form_textarea" v-model="formData.content" placeholder="请输入任务内容"
+								:maxlength="-1"></textarea>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<view class="form_item">
+				<view class="form_row">
+					<text class="form_label">发布时间</text>
+					<view class="form_content">
+						<custom-radio v-model="publishType" :options="publishOptions" />
+						<view v-if="publishType === 1" class="time_picker">
+							<uni-datetime-picker v-model="publishDateTime" type="datetime" :start="startTime"
+								:end="endTime" return-type="string" />
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<view class="form_item">
+				<view class="form_row">
+					<text class="form_label">通知对象</text>
+					<view class="form_content">
+						<view class="object_row">
+							<view class="object_info" @click="goPersonList">
+								<text class="object_count">{{ objectTypeLabel || '请选择' }}</text>
+								<text class="object_count">已选{{ selectedCount }}人</text>
+							</view>
+							<view class="object_action" @click="selectObject">
+								<text class="action_text">选择通知对象</text>
+								<text class="action_arrow">></text>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<view class="form_item" v-if="isEdit">
+				<view class="form_row">
+					<text class="form_label">任务状态</text>
+					<view class="form_content">
+						<view class="switch_row">
+							<view class="switch_container">
+								<switch :checked="formData.status" color="#2E64FA" @change="onStatusChange" />
+							</view>
+							<text class="switch_text">说明:关闭后通知不对通知对象显示</text>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<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">说明:{{ saveRecord === 0 ? '已读记录将被保留' : '已读记录将被清空,重新记录' }}</text>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+
+		<view class="publish_footer" :style="{ paddingBottom: safeAreaBottom + 24 + 'rpx' }">
+			<view class="confirm_btn" @click="handleSave">
+				<text class="btn_text">确定</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue';
+import { onLoad, onShow } from '@dcloudio/uni-app';
+import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
+import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue';
+import overviewApi from '@/reqApi/overview.js';
+import notification from '@/reqApi/notification.js';
+import CustomRadio from '@/components/customRadio.vue';
+import CommonHeader from '@/components/commonHeader.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
+
+const showTypePicker = ref(false);
+const publishType = ref(0);
+const isEdit = ref(false);
+const saveRecord = ref(0);
+const typeIndex = ref(-1);
+const typeData = ref([]);
+const typeOptions = ref([]);
+
+const getCurrentDateTime = () => {
+	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 publishDateTime = ref(getCurrentDateTime());
+const startTime = ref('2024-01-01 00:00:00');
+const endTime = ref('2030-12-31 23:59:59');
+
+const publishOptions = [
+	{ label: '立即发布', value: 0 },
+	{ label: '预约发布', value: 1 }
+];
+
+const recordOptions = [
+	{ label: '保存', value: 0 },
+	{ label: '不保存', value: 1 }
+];
+
+const formData = reactive({
+	title: '',
+	noticeType: '',
+	content: '',
+	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();
+		if (res.data && Array.isArray(res.data)) {
+			typeData.value = res.data.map(item => ({
+				id: item.id,
+				name: item.name
+			}));
+			typeOptions.value = typeData.value.map(item => item.name);
+		}
+	} catch (error) {
+		console.error('获取任务类型失败:', error);
+	}
+};
+
+onMounted(() => {
+	initSafeArea();
+});
+
+onLoad(async (options) => {
+	await fetchNoticeTypeList();
+
+	if (options && options.id) {
+		isEdit.value = true;
+		editId.value = options.id;
+		fetchNoticeDetail(options.id);
+	}
+});
+
+const fetchNoticeDetail = async (id) => {
+	try {
+		const res = await notification.find_collection_task_target_list({ id });
+		if (res.data) {
+			const data = res.data;
+			formData.title = data.taskName || data.noticeName || '';
+			const content = data.noticeContent || data.content || '';
+			formData.content = content.replace(/<[^>]+>/g, '');
+			formData.status = data.isClose !== true;
+			noticeTypeId.value = data.typeId || data.noticeTypeId || '';
+			
+			const typeIndexVal = typeData.value.findIndex(item => item.id === (data.typeId || data.noticeTypeId));
+			typeIndex.value = typeIndexVal >= 0 ? typeIndexVal : -1;
+			if (typeIndexVal >= 0) {
+				formData.noticeType = typeData.value[typeIndexVal].name;
+			}
+			
+			publishType.value = data.sendTimeType === 2 ? 1 : 0;
+			publishDateTime.value = data.releaseTime || getCurrentDateTime();
+			
+			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 || user.id,
+					name: user.thirdName,
+					userId: user.userId,
+					thirdCode: user.thirdCode || user.userAccount || user.teacherId || user.studentCode || '',
+					userAccount: user.userAccount || user.thirdCode,
+					departmentName: user.departmentName || '',
+					groupName: user.groupName || '',
+					subjectName: user.subjectName || '',
+					gradeName: user.gradeName || '',
+					classTypeName: user.classTypeName || '',
+					className: user.className || '',
+					roleName: user.roleName || ''
+				}));
+				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 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) => {
+	typeIndex.value = e.detail.value;
+	const selectedItem = typeData.value[typeIndex.value];
+	if (selectedItem) {
+		formData.noticeType = selectedItem.name;
+		noticeTypeId.value = selectedItem.id;
+	}
+};
+
+const goBack = () => {
+	uni.navigateBack();
+};
+
+const onStatusChange = (e) => {
+	formData.status = e.detail.value;
+	if (formData.status) {
+		saveRecord.value = 0;
+	}
+};
+
+const selectObject = () => {
+	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/materialCollection/taskOverview/components/selectObjectNoDedup' });
+};
+
+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/materialCollection/taskOverview/components/personList' });
+};
+
+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;
+	}
+	if (!formData.content) {
+		uni.showToast({ title: '请输入任务内容', icon: 'none' });
+		return;
+	}
+	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 => {
+			return {
+				userType: objectType === 5 ? 2 : 1,
+				userId: user.userId || user.id,
+				thirdName: user.name,
+				thirdCode: user.thirdCode || '',
+				gradeName: user.gradeName || '',
+				classTypeName: user.classTypeName || '',
+				className: user.className || '',
+				departmentName: user.departmentName || '',
+				groupName: user.groupName || '',
+				subjectName: user.subjectName || '',
+				roleName: user.roleName || ''
+			};
+		});
+		
+		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.navigateBack();
+			}, 1500);
+		} else {
+			uni.showToast({ title: res.message || '保存失败', icon: 'none' });
+		}
+	} catch (error) {
+		console.error('保存失败:', error);
+		uni.showToast({ title: '保存失败', icon: 'none' });
+	} finally {
+		uni.hideLoading();
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.page_publish {
+	min-height: 100vh;
+	background-color: #FFFFFF;
+	display: flex;
+	flex-direction: column;
+	box-sizing: border-box;
+}
+
+.publish_content {
+	flex: 1;
+	box-sizing: border-box;
+	padding-top: 36rpx;
+	padding-left: 24rpx;
+	padding-right: 24rpx;
+}
+
+.form_item {
+	background-color: #FFFFFF;
+	border-radius: 8rpx;
+	padding-bottom: 24rpx;
+	margin-bottom: 24rpx;
+	border-bottom: 2rpx solid #F3F3F3;
+	&:last-child {
+		border-bottom: none;
+	}
+	.form_label {
+		font-size: 28rpx;
+		color: #666666;
+		font-weight: 400;
+		white-space: nowrap;
+		flex-shrink: 0;
+		margin-right: 16rpx;
+
+		&::after {
+			content: ':';
+			margin-left: 8rpx;
+		}
+	}
+
+	.form_row {
+		display: flex;
+		align-items: flex-start;
+
+		.form_content {
+			flex: 1;
+		}
+	}
+
+	.form_row_type {
+		align-items: center;
+
+		.filter_item {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 0 16rpx;
+			height: 72rpx;
+			line-height: 72rpx;
+			background-color: #FFFFFF;
+			border-radius: 8rpx;
+			width: 320rpx;
+			border: 2rpx solid #DCDFE6;
+
+			.filter_text {
+				font-size: 28rpx;
+				color: #333333;
+				margin-right: 8rpx;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				&.placeholder {
+					color: #909399;
+				}
+			}
+		}
+	}
+
+	.form_input {
+		font-size: 28rpx;
+		color: #333333;
+		padding: 0 24rpx;
+		background-color: #FFFFFF;
+		border-radius: 8rpx;
+		width: 100%;
+		height: 72rpx;
+		box-sizing: border-box;
+		border: 2rpx solid #E9E9E9;
+	}
+
+	.form_select {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 16rpx;
+		background-color: #F5F7FA;
+		border-radius: 8rpx;
+		border: 2rpx solid #DCDFE6;
+
+		.select_text {
+			font-size: 28rpx;
+			color: #333333;
+		}
+
+		.select_right {
+			.select_arrow {
+				font-size: 28rpx;
+				color: #999999;
+			}
+		}
+	}
+
+	.textarea_container {
+		position: relative;
+
+		.form_textarea {
+			width: 100%;
+			height: 240rpx;
+			font-size: 28rpx;
+			color: #333333;
+			line-height: 1.6;
+			padding: 14rpx 24rpx 56rpx 24rpx;
+			background-color: #FFFFFF;
+			border-radius: 8rpx;
+			box-sizing: border-box;
+			border: 2rpx solid #E9E9E9;
+		}
+
+		.textarea_actions {
+			position: absolute;
+			bottom: 13rpx;
+			right: 16rpx;
+			display: flex;
+			align-items: center;
+			gap: 8rpx;
+
+			.full_icon {
+				width: 28rpx;
+				height: 28rpx;
+			}
+
+			.action_text {
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #2E64FA;
+			}
+		}
+	}
+
+	.time_picker {
+		margin-top: 20rpx;
+		min-width: 300rpx;
+		height: 68rpx;
+		float: right;
+
+		:deep(.uni-date-x--border) {
+			height: 68rpx;
+		}
+
+		:deep(.uni-date-x) {
+			padding: 0 16rpx;	
+			height: 48rpx;
+		}
+
+		:deep(.uni-date-x--text) {
+			font-weight: 400;
+			font-size: 28rpx;
+			color: #333333;
+		}
+
+		:deep(.uni-date__x-input) {
+			font-size: 28rpx;
+			color: #333333;
+		}
+	}
+
+	.object_row {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+
+		.object_info {
+			display: flex;
+			align-items: center;
+			gap: 12rpx;
+
+			.object_count {
+				font-size: 28rpx;
+				color: #2E64FA;
+			}
+		}
+
+		.object_action {
+			display: flex;
+			align-items: center;
+			gap: 8rpx;
+
+			.action_text {
+				font-size: 28rpx;
+				color: #2E64FA;
+			}
+
+			.action_arrow {
+				font-size: 28rpx;
+				color: #2E64FA;
+			}
+		}
+	}
+
+	.object_tip {
+		font-size: 24rpx;
+		color: #F56C6C;
+		margin-top: 12rpx;
+		display: block;
+	}
+
+	.switch_row {
+		display: flex;
+		flex-direction:column;
+
+		.switch_container {
+			transform: scale(0.8);
+			transform-origin: left center;
+		}
+
+		.switch_text {
+			font-weight: 400;
+			font-size: 28rpx;
+			color: #999999;
+		}
+	}
+
+	.record_tip {
+		font-weight: 400;
+		font-size: 28rpx;
+		color: #999999;
+		margin-top: 16rpx;
+		display: block;
+	}
+}
+
+.publish_footer {
+	padding: 24rpx;
+	background-color: #FFFFFF;
+
+	.confirm_btn {
+		width: 702rpx;
+		height: 90rpx;
+		background: #2E64FA;
+		border-radius: 8rpx 8rpx 8rpx 8rpx;
+		text-align: center;
+		line-height: 90rpx;
+		.btn_text {
+			font-size: 32rpx;
+			font-weight: 500;
+			color: #FFFFFF;
+		}
+	}
+}
+</style>

+ 178 - 0
pages/materialCollection/taskOverview/components/personList.vue

@@ -0,0 +1,178 @@
+<template>
+	<view class="page_person_list">
+		<CommonHeader :title="headerTitle" @back="goBack"></CommonHeader>
+
+		<SearchBar v-model="searchWord" @search="handleSearch" :isFixed="true" :top="searchBarTop + 'rpx'" :border="false"></SearchBar>
+
+		<scroll-view scroll-y class="person_list" :style="{ paddingTop: contentPaddingTop + 'rpx' }">
+			<view v-for="(person, index) in filteredList" :key="person.userId || person.id || index" 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/commonHeader.vue';
+import SearchBar from '@/components/searchBar.vue';
+import ConfirmModal from '@/components/confirmModal.vue';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, initSafeArea } = useSafeArea();
+
+const HEADER_CONTENT_HEIGHT = 174;
+const SEARCHBAR_HEIGHT = 60;
+const SPACING = 24;
+
+const searchBarTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT);
+const contentPaddingTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + SEARCHBAR_HEIGHT + SPACING);
+
+const searchWord = ref('');
+
+const personList = ref([]);
+const objectTypeLabel = ref('');
+
+const showDeleteModal = ref(false);
+const deleteIndex = ref(-1);
+const deletePersonName = ref('');
+
+const filteredList = computed(() => {
+	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(() => {
+	initSafeArea();
+
+	const data = uni.getStorageSync('personListData');
+	if (data) {
+		try {
+			const parsed = JSON.parse(data);
+			// 不去重,直接使用所有用户
+			personList.value = parsed.users || [];
+			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-left: 24rpx;
+	padding-right: 24rpx;
+	padding-bottom: 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>

+ 674 - 0
pages/materialCollection/taskOverview/components/selectObjectNoDedup.vue

@@ -0,0 +1,674 @@
+<template>
+	<view class="page_select_object">
+		<CommonHeader title="选择通知对象" @back="goBack"></CommonHeader>
+
+		<view class="tabs_container" :style="{ top: tabsTop + 'rpx' }">
+			<view v-for="tab in tabs" :key="tab.key" class="tab_item" :class="{ active: activeTab === tab.key, disabled: editMode }"
+				@click="!editMode && handleTabChange(tab.key)">
+				<text class="tab_text">{{ tab.label }}</text>
+			</view>
+		</view>
+
+		<SearchBar v-model="searchKeyword" @search="searchTree" :border="true" :isFixed="true" :top="searchBarTop + 'rpx'" :zIndex="100"></SearchBar>
+
+		<scroll-view scroll-y class="content_list" :style="{ paddingTop: contentPaddingTop + 'rpx' }">
+			<TreeNodeNoDedup :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 TreeNodeNoDedup from '@/components/treeNodeNoDedup.vue';
+import SearchBar from '@/components/searchBar.vue';
+import CommonHeader from '@/components/commonHeader.vue';
+import overview from '@/reqApi/overview.js';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, initSafeArea } = useSafeArea();
+
+const HEADER_CONTENT_HEIGHT = 130;
+const TABS_HEIGHT = 120;
+const SEARCHBAR_HEIGHT = 84;
+const SPACING = 16;
+
+const tabsTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT);
+const searchBarTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + TABS_HEIGHT);
+const contentPaddingTop = computed(() => statusBarHeight.value + HEADER_CONTENT_HEIGHT + TABS_HEIGHT + SEARCHBAR_HEIGHT + SPACING);
+
+const activeTab = ref('department');
+const searchKeyword = ref('');
+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([]);
+
+// 转换按部门接口数据为树形结构(不去重)
+const transformDepartmentData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(dept => {
+		const groups = (dept.groupVOS || []).map(group => ({
+			id: `${dept.id}_${group.groupId}`,
+			name: group.groupName,
+			checked: false,
+			halfChecked: false,
+			children: (group.groupUserVOS || []).map(user => ({
+				id: user.userId,
+				name: user.teacherName,
+				userId: user.userId,
+				userAccount: user.userAccount,
+				checked: false,
+				halfChecked: false
+			}))
+		}));
+		
+		return {
+			id: dept.id,
+			name: dept.name,
+			checked: false,
+			halfChecked: false,
+			children: groups
+		};
+	});
+};
+
+// 转换按学科的数据为树形结构(不去重)
+const transformSubjectData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(subject => {
+		const grades = (subject.gradePersonVos || []).map(grade => ({
+			id: `${subject.subjectCode}_${grade.schoolYearGradeId || grade.gradeCode}`,
+			name: grade.gradeName,
+			checked: false,
+			halfChecked: false,
+			children: (grade.personVoList || []).map(user => ({
+				id: user.userId,
+				name: user.teacherName,
+				userId: user.userId,
+				userAccount: user.userAccount,
+				checked: false,
+				halfChecked: false
+			}))
+		}));
+		
+		return {
+			id: `subject_${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: `${classType.classType}_${cls.classCode}`,
+				name: cls.className,
+				checked: false,
+				halfChecked: false,
+				children: (cls.personVoList || []).map(user => ({
+					id: user.userId,
+					name: user.teacherName,
+					userId: user.userId,
+					userAccount: user.userAccount,
+					checked: false,
+					halfChecked: false
+				}))
+			}));
+			
+			return {
+				id: `${grade.schoolYearGradeId || grade.gradeCode}_${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: (item.personVoList || []).map(user => ({
+				id: user.userId,
+				name: user.teacherName,
+				userId: user.userId,
+				userAccount: user.userAccount,
+				checked: false,
+				halfChecked: false
+			}))
+		};
+	});
+};
+
+// 转换按学生的数据为树形结构(不去重)
+const transformStudentData = (data) => {
+	if (!data || !Array.isArray(data)) return [];
+	
+	return data.map(grade => {
+		const classes = (grade.clsVOS || []).map(cls => ({
+			id: `${grade.schoolYearGradeId || grade.gradeCode}_${cls.classId}`,
+			name: cls.className,
+			checked: false,
+			halfChecked: false,
+			children: (cls.studentTblVOS || []).map(student => ({
+				id: student.userId,
+				name: student.studentName,
+				userId: student.userId,
+				userAccount: student.studentCode,
+				checked: false,
+				halfChecked: false
+			}))
+		}));
+		
+		return {
+			id: `student_${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 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 {
+		expandedIds.value = expandedIds.value.filter(id => {
+			if (siblingIds.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;
+
+	if (item.children && item.children.length > 0) {
+		// 向下级联
+		toggleChildren(item.children, item.checked);
+	}
+
+	// 向上级联
+	updateParentStatus();
+};
+
+const selectedCount = computed(() => {
+	let count = 0;
+	const collectChecked = (items) => {
+		items.forEach(item => {
+			if (item.children && item.children.length > 0) {
+				collectChecked(item.children);
+			} else if (item.checked && item.userId) {
+				count++;
+			}
+		});
+	};
+	collectChecked(treeData.value);
+	return count;
+});
+
+const goBack = () => {
+	uni.navigateBack();
+};
+
+const collectSelectedUsers = () => {
+	const selectedUsers = [];
+	const currentTab = activeTab.value;
+
+	const collect = (nodes, parentInfo = {}) => {
+		nodes.forEach(node => {
+			if (node.children && node.children.length > 0) {
+				const newParentInfo = { ...parentInfo };
+				if (node.name && !node.userId) {
+					if (currentTab === 'department') {
+						if (!newParentInfo.departmentName) {
+							newParentInfo.departmentName = node.name;
+						} else if (!newParentInfo.groupName) {
+							newParentInfo.groupName = node.name;
+						}
+					} else if (currentTab === 'subject') {
+						if (!newParentInfo.subjectName) {
+							newParentInfo.subjectName = node.name;
+						} else if (!newParentInfo.gradeName) {
+							newParentInfo.gradeName = node.name;
+						}
+					} else if (currentTab === 'grade') {
+						if (!newParentInfo.gradeName) {
+							newParentInfo.gradeName = node.name;
+						} else if (!newParentInfo.classTypeName) {
+							newParentInfo.classTypeName = node.name;
+						} else if (!newParentInfo.className) {
+							newParentInfo.className = node.name;
+						}
+					} else if (currentTab === 'permission') {
+						if (!newParentInfo.roleName) {
+							newParentInfo.roleName = node.name;
+						}
+					} else if (currentTab === 'student') {
+						if (!newParentInfo.gradeName) {
+							newParentInfo.gradeName = node.name;
+						} else if (!newParentInfo.classTypeName) {
+							newParentInfo.classTypeName = node.name;
+						} else if (!newParentInfo.className) {
+							newParentInfo.className = node.name;
+						}
+					}
+				}
+				collect(node.children, newParentInfo);
+			} else if (node.checked && node.userId) {
+				selectedUsers.push({
+					id: node.id,
+					name: node.name,
+					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 || ''
+				});
+			}
+		});
+	};
+	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;
+			}
+		});
+	};
+	
+	setNode(treeData.value);
+	updateParentStatus();
+};
+
+onMounted(async () => {
+	initSafeArea();
+
+	const pages = getCurrentPages();
+	if (pages.length === 1) {
+		uni.navigateBack();
+		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.userId || 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;
+}
+
+.tabs_container {
+	position: fixed;
+	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;
+		}
+	}
+}
+
+.content_list {
+	flex: 1;
+	padding-bottom: 150rpx;
+}
+
+.select_footer {
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	padding: 24rpx;
+	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>

+ 15 - 9
pages/materialCollection/taskOverview/index.vue

@@ -1,6 +1,6 @@
-<template>
+<template>
 	<view class="page_notice" :style="{paddingTop: statusBarHeight + 362 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
-		<notice-header 
+		<noticeHeader 
 			title="任务总览" 
 			mode="taskOverview"
 			:showSearch="true" 
@@ -18,7 +18,7 @@
 			@materialTypeChange="handleMaterialTypeChange"
 			@searchChange="handleSearchChange"
 			@create="handleCreate">
-		</notice-header>
+		</noticeHeader>
 
 		<view class="notice_content">
 			<view class="notice_list">
@@ -50,7 +50,7 @@
 							<view class="action_btn delete" :class="{ disable: !!item.submitNum }">
 								<text>删除</text>
 							</view>
-							<view class="action_btn edit" >
+							<view class="action_btn edit" @click="handleEdit(item)" >
 								<text>编辑</text>
 							</view>
 							<view class="action_btn edit" :class="{ disableLock: item.taskStatus === 0 }">
@@ -60,7 +60,7 @@
 					</view>
 				</scroll-view>
 
-				<no-data v-if="taskList.length === 0" centered text="暂无任务" />
+				<noData v-if="taskList.length === 0" centered text="暂无任务" />
 			</view>
 		</view>
 
@@ -70,9 +70,9 @@
 
 <script setup>
 import { ref, onMounted } from 'vue';
-import NoticeHeader from '@/components/notice-header.vue';
+import NoticeHeader from '@/components/noticeHeader.vue';
 import CommonTabbar from '@/components/commonTabbar.vue';
-import NoData from '@/components/no-data.vue';
+import NoData from '@/components/noData.vue';
 import { useSafeArea } from '@/common/safeArea';
 import notification from '@/reqApi/notification.js';
 import { truncateText } from '@/common/common.js';
@@ -82,7 +82,7 @@ const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 const tabs = ['全部', '待开始', '进行中', '已结束'];
 const activeTab = ref(-1);
 const academicYearIndex = ref(0);
-const academicYearOptions = ref(['请选择学年学期']);
+const academicYearOptions = ref(['']);
 const academicYearIds = ref(['']);
 const academicYearCodes = ref(['']);
 const currentSchoolYearId = ref('');
@@ -212,7 +212,13 @@ const handleSearchChange = (keyword) => {
 
 const handleCreate = () => {
 	uni.navigateTo({
-		url: '/pages/materialCollection/submitMaterial/index'
+		url: '/pages/materialCollection/taskOverview/components/index'
+	});
+};
+
+const handleEdit = (item) => {
+	uni.navigateTo({
+		url: `/pages/materialCollection/taskOverview/components/index?id=${item.id}`
 	});
 };
 

+ 4 - 4
pages/message/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<view class="page_message" :style="{paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
-		<page-header></page-header>
+		<pageHeader></pageHeader>
 		<view class="message_content">
 			<view class="message_header">
 				<text class="message_title">系统消息({{ unreadCount }}项)</text>
@@ -30,7 +30,7 @@
 				<image src="@/static/image/bg/no_data.png" class="empty_image" mode="aspectFit"></image>
 				<text class="empty_text">暂无系统消息</text>
 			</view> -->
-			<no-data v-if="messageList.length === 0" centered text="暂无系统消息"></no-data>
+			<noData v-if="messageList.length === 0" centered text="暂无系统消息"></noData>
 		</view>
 		<common-tabbar></common-tabbar>
 	</view>
@@ -40,11 +40,11 @@
 	import { ref, onMounted } from 'vue';
 	import { onShow } from '@dcloudio/uni-app';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
-	import PageHeader from '@/components/page-header.vue';
+	import PageHeader from '@/components/pageHeader.vue';
 	import CommonTabbar from '@/components/commonTabbar.vue';
 	import workspace from '@/reqApi/workspace.js';
 	import { useSafeArea } from '@/common/safeArea';
-	import NoData from '@/components/no-data.vue';
+	import NoData from '@/components/noData.vue';
 	import { truncateText } from '@/common/common.js';
 
 	const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();

+ 2 - 2
pages/my/index.vue

@@ -36,13 +36,13 @@
 		</view>
 	</view>
 	<common-tabbar></common-tabbar>
-	<popup-dialog ref="popupDialogRef" :showTitle="false" content="确定退出当前账号?" @DialogConfirm="DialogConfirm" />
+	<popupDialog ref="popupDialogRef" :showTitle="false" content="确定退出当前账号?" @DialogConfirm="DialogConfirm" />
 </template>
 
 <script setup>
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 	import uniTooltip from '@/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue';
-	import popupDialog from '@/components/popup-dialog.vue';
+	import popupDialog from '@/components/popupDialog.vue';
 	import { ref, onMounted } from 'vue';
 	import { useStore } from 'vuex';
 

+ 2 - 2
pages/my/updatePassword.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="page_body">
 		<!-- 导航 -->
 		<nav-bar height="88" :showHeaderborder="false" @GoBack="GoBack"></nav-bar>
@@ -43,7 +43,7 @@
 
 <script setup>
 	import { reactive, ref } from 'vue';
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import uniForms from '@/uni_modules/uni-forms/components/uni-forms/uni-forms.vue';
 	import uniFormsItem from '@/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue';
 	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';

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

@@ -1,6 +1,6 @@
 <template>
 	<view class="page_detail" :style="{ paddingBottom: safeAreaBottom + 'rpx' }">
-		<common-header title="消息通知" @back="goBack"></common-header>
+		<commonHeader title="消息通知" @back="goBack"></commonHeader>
 
 		<view class="detail_content" :style="{ paddingTop: statusBarHeight + 130 + 'rpx' }">
 			<view class="detail_header">
@@ -57,7 +57,7 @@
 <script setup>
 import { ref, onMounted } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
-import CommonHeader from '@/components/common-header.vue';
+import CommonHeader from '@/components/commonHeader.vue';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import overviewApi from '@/reqApi/overview.js';
 import { useSafeArea } from '@/common/safeArea';

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

@@ -1,7 +1,7 @@
 <template>
 	<view class="page_mine" :style="{paddingTop: statusBarHeight + 352 + 'rpx', paddingBottom: showTabbar ? safeAreaBottom + 160 : '24' + 'rpx' }">
-		<notice-header title="我的通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
-			:activeTab="activeTab" tabMode="index" @tabChange="handleTabChange" @typeChange="handleTypeChange" @timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
+		<noticeHeader title="我的通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
+			:activeTab="activeTab" tabMode="index" @tabChange="handleTabChange" @typeChange="handleTypeChange" @timeChange="handleTimeChange" @searchChange="handleSearchChange"></noticeHeader>
 
 		<view class="notice_content">
 			<view class="notice_list">
@@ -25,7 +25,7 @@
 					</view>
 				</scroll-view>
 
-				<no-data v-if="noticeList.length === 0" centered text="暂无通知" />
+				<noData v-if="noticeList.length === 0" centered text="暂无通知" />
 			</view>
 		</view>
 
@@ -36,8 +36,8 @@
 <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 NoticeHeader from '@/components/noticeHeader.vue';
+import NoData from '@/components/noData.vue';
 import overviewApi from '@/reqApi/overview.js';
 import { useSafeArea } from '@/common/safeArea';
 import { truncateText } from '@/common/common.js';

+ 2 - 2
pages/notice/mine/preview.vue

@@ -1,6 +1,6 @@
 <template>
 	<view class="page_preview">
-		<common-header :title="fileName" @back="goBack"></common-header>
+		<commonHeader :title="fileName" @back="goBack"></commonHeader>
 
 		<!-- #ifdef APP-PLUS -->
 		<view class="app_container">
@@ -53,7 +53,7 @@
 <script setup>
 import { ref, onMounted, onUnmounted } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
-import CommonHeader from '@/components/common-header.vue';
+import CommonHeader from '@/components/commonHeader.vue';
 
 const PREVIEW_STORAGE_KEY = 'noticePreviewFile';
 const fileUrl = ref('');

+ 6 - 6
pages/notice/overview/index.vue

@@ -1,8 +1,8 @@
 <template>
 	<view class="page_notice" :style="{paddingTop: statusBarHeight + 352 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
-		<notice-header title="校内通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
+		<noticeHeader title="校内通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
 			:activeTab="activeTab" @tabChange="handleTabChange" @typeChange="handleTypeChange"
-			@timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
+			@timeChange="handleTimeChange" @searchChange="handleSearchChange"></noticeHeader>
 
 		<view class="notice_content">
 			<view class="notice_list">
@@ -43,7 +43,7 @@
 
 				</scroll-view>
 
-				<no-data v-if="noticeList.length === 0" centered text="暂无通知" />
+				<noData v-if="noticeList.length === 0" centered text="暂无通知" />
 			</view>
 		</view>
 
@@ -58,11 +58,11 @@
 <script setup>
 import { ref, onMounted } from 'vue';
 import { onShow } from '@dcloudio/uni-app';
-import NoticeHeader from '@/components/notice-header.vue';
-import ConfirmModal from '@/components/confirm-modal.vue';
+import NoticeHeader from '@/components/noticeHeader.vue';
+import ConfirmModal from '@/components/confirmModal.vue';
 import overviewApi from '@/reqApi/overview.js';
 import store from '@/store/index.js';
-import NoData from '@/components/no-data.vue';
+import NoData from '@/components/noData.vue';
 import { useSafeArea } from '@/common/safeArea';
 import { truncateText } from '@/common/common.js';
 

+ 2 - 2
pages/notice/overview/noticeDetail.vue

@@ -69,7 +69,7 @@
 					</view>
 				</scroll-view>
 
-				<no-data v-else centered text="暂无数据" />
+				<noData v-else centered text="暂无数据" />
 			</view>
 		</view>
 	</view>
@@ -80,7 +80,7 @@ import { ref, computed, onMounted } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import overviewApi from '@/reqApi/overview.js';
-import NoData from '@/components/no-data.vue';
+import NoData from '@/components/noData.vue';
 import { useSafeArea } from '@/common/safeArea';
 import { truncateText } from '@/common/common.js';
 

+ 2 - 2
pages/notice/publish/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<view class="page_publish" >
-		<common-header :title="isEdit ? '编辑通知' : '发布通知'" @back="goBack"></common-header>
+		<commonHeader :title="isEdit ? '编辑通知' : '发布通知'" @back="goBack"></commonHeader>
 
 		<scroll-view scroll-y class="publish_content" :style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 'rpx' }">
 			<view class="form_item">
@@ -114,7 +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';
+import CommonHeader from '@/components/commonHeader.vue';
 import { useSafeArea } from '@/common/safeArea';
 
 const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();

+ 4 - 4
pages/notice/publish/personList.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="page_person_list">
 		<CommonHeader :title="headerTitle" @back="goBack"></CommonHeader>
 
@@ -34,9 +34,9 @@
 
 <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';
+import CommonHeader from '@/components/commonHeader.vue';
+import SearchBar from '@/components/searchBar.vue';
+import ConfirmModal from '@/components/confirmModal.vue';
 import { useSafeArea } from '@/common/safeArea';
 
 const { statusBarHeight, initSafeArea } = useSafeArea();

+ 4 - 4
pages/notice/publish/selectObject.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="page_select_object">
 		<CommonHeader title="选择通知对象" @back="goBack"></CommonHeader>
 
@@ -26,9 +26,9 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
-import TreeNode from '@/components/tree-node.vue';
-import SearchBar from '@/components/search-bar.vue';
-import CommonHeader from '@/components/common-header.vue';
+import TreeNode from '@/components/treeNode.vue';
+import SearchBar from '@/components/searchBar.vue';
+import CommonHeader from '@/components/commonHeader.vue';
 import overview from '@/reqApi/overview.js';
 import { useSafeArea } from '@/common/safeArea';
 

+ 2 - 2
pages/studentStudy/courseCenter/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<nav-bar height="312" backPath="workspace" title="学生学习" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view class="page_content">
@@ -37,7 +37,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {queryTeacherCourseTypeList,teacherCenterPageData} from '@/reqApi/studentStudy.js';
 	import {
 		reactive,

+ 2 - 2
pages/studentStudy/index.vue

@@ -7,12 +7,12 @@
 		<!-- 学习监控 -->
 		<learning-monitor v-if="state.tabBarValue=='learningMonitor'"></learning-monitor>
 		<!-- 底部tabbar -->
-		<page-tabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></page-tabbar>
+		<pageTabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></pageTabbar>
 	</view>
 </template>
 
 <script setup>
-	import pageTabbar from '@/components/page-tabbar.vue';//头部
+	import pageTabbar from '@/components/pageTabbar.vue';//头部
 	import courseCenter from './courseCenter/index.vue';//课程中心
 	import myLearning from './myLearning/index.vue';//我的学习
 	import learningMonitor from './learningMonitor/index.vue';//学习监控

+ 2 - 2
pages/studentStudy/learningMonitor/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<nav-bar :height="state.courseType == '0'?'312':'224'" backPath="workspace" title="学生学习" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view :class="['page_content',{'self_content_height':state.courseType == '1'}]">
@@ -47,7 +47,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {queryTeacherCourseTypeList,teacherMinitorPageListData} from '@/reqApi/studentStudy.js';
 	import {
 		reactive,

+ 2 - 2
pages/studentStudy/learningMonitor/taskDetails.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="page_body">
 		<!-- 头部 -->
 		<nav-bar height="96" title="任务详情" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
@@ -156,7 +156,7 @@
 	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 	import uvSticky from '@/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue'; //吸顶
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {
 		queryTeacherMinitorDetailData
 	} from '@/reqApi/studentStudy.js';

+ 2 - 2
pages/studentStudy/myLearning/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<nav-bar height="312" backPath="workspace" title="学生学习" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view class="page_content">
@@ -45,7 +45,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {queryTeacherCourseTypeList,teacherRecordPageListData} from '@/reqApi/studentStudy.js';
 	import {
 		reactive,

+ 4 - 4
pages/studentStudy/videoDetails.vue

@@ -51,20 +51,20 @@
 			</view>
 		</view>
 		<!-- 提示框 -->
-		<custom-popup-dialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCloseBtn="false" @DialogConfirm="DialogConfirm">
+		<customPopupDialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCloseBtn="false" @DialogConfirm="DialogConfirm">
 			<view class="dialog_questions_input">
 				<view class="questions_title">{{state?.question?.codeName}}:{{state?.question?.questionsName}}</view>
 				<uni-easyinput type="textarea" v-model="state.questionsAnswer" :style="state.easyinputStyle" placeholder="请输入你的回答" placeholderStyle="font-weight: 400;font-size: 28rpx;color: #999999;"></uni-easyinput>
 			</view>
-		</custom-popup-dialog>	
+		</customPopupDialog>	
 	</view>
 </template>
 
 <script setup>
 	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
-	import navBar from '@/components/nav-bar.vue';
-	import customPopupDialog from '@/components/custom-popup-dialog.vue';
+	import navBar from '@/components/navBar.vue';
+	import customPopupDialog from '@/components/customPopupDialog.vue';
 	import { teacherSelectCourseDetail,addQuestionAnswerData,updateFollowCountTeacher,synTeacherLearnData } from '@/reqApi/studentStudy.js';
 	import {
 		onLoad,

+ 2 - 2
pages/teacherHonor/honorOverview/honorTypeDetail.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="page_body">
 		<!-- 头部 -->
 		<nav-bar height="96" :title="state.title" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
@@ -39,7 +39,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import uniForms from '@/uni_modules/uni-forms/components/uni-forms/uni-forms.vue';
 	import uniFormsItem from '@/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue';
 	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';

+ 8 - 8
pages/teacherHonor/honorOverview/index.vue

@@ -46,10 +46,10 @@
 					</view>
 				</view>
 			</template>
-			<no-data v-if="!state.isLoading && state.tableData.length == 0" />
+			<noData v-if="!state.isLoading && state.tableData.length == 0" />
 		</view>
 	</view>
-	<custom-popup-dialog ref="popupDialogRef" :title="state.popupDialog.title" :isMaskClick="true" :showDialogClose="false" :showDialogFooter="false">
+	<customPopupDialog ref="popupDialogRef" :title="state.popupDialog.title" :isMaskClick="true" :showDialogClose="false" :showDialogFooter="false">
 		<view class="popup_content">
 			<view class="content_item" @click="AddEditHonorType('edit')">
 				<view class="content_item_left">
@@ -65,16 +65,16 @@
 				</view>
 			</view>
 		</view>
-	</custom-popup-dialog>
+	</customPopupDialog>
 	<!-- 删除确认框 -->
-	<popup-dialog ref="popupDelDialogRef" :showFootBorder="true" :content="state.popupDelDialog.content" @DialogConfirm="DialogDelConfirm" />
+	<popupDialog ref="popupDelDialogRef" :showFootBorder="true" :content="state.popupDelDialog.content" @DialogConfirm="DialogDelConfirm" />
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
-	import noData from '@/components/no-data.vue';
-	import customPopupDialog from '@/components/custom-popup-dialog.vue';
-	import popupDialog from '@/components/popup-dialog.vue';
+	import navBar from '@/components/navBar.vue';
+	import noData from '@/components/noData.vue';
+	import customPopupDialog from '@/components/customPopupDialog.vue';
+	import popupDialog from '@/components/popupDialog.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 	import {queryHonorTypeOverView,deleteHonorType} from '@/reqApi/teacherHonor.js';
 	import { onShow } from '@dcloudio/uni-app';

+ 2 - 2
pages/teacherHonor/honorTeacher/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<nav-bar height="224" backPath="workspace" title="教师专业发展" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="false" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view class="page_content">
@@ -38,7 +38,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {queryHonorTeacherListData} from '@/reqApi/teacherHonor.js';
 	import {
 		reactive,

+ 2 - 2
pages/teacherHonor/index.vue

@@ -9,12 +9,12 @@
 		<!-- 荣誉审核 -->
 		<honor-audit v-if="state.tabBarValue=='myHonor'"></honor-audit>
 		<!-- 底部tabbar -->
-		<page-tabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></page-tabbar>
+		<pageTabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></pageTabbar>
 	</view>
 </template>
 
 <script setup>
-	import pageTabbar from '@/components/page-tabbar.vue';//头部
+	import pageTabbar from '@/components/pageTabbar.vue';//头部
 	import honorOverview from './honorOverview/index.vue';//荣誉总览
 	import honorTeacher from './honorTeacher/index.vue';//荣誉教师
 	import myHonor from './myHonor/index.vue';//我的荣誉

+ 2 - 2
pages/teacherStudy/courseCenter/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<nav-bar height="312" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view class="page_content">
@@ -37,7 +37,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {queryCourseTypeDataList,centerPageListData} from '@/reqApi/teacherStudy.js';
 	import {
 		reactive,

+ 2 - 2
pages/teacherStudy/index.vue

@@ -7,12 +7,12 @@
 		<!-- 研修监控 -->
 		<learning-monitor v-if="state.tabBarValue=='learningMonitor'"></learning-monitor>
 		<!-- 底部tabbar -->
-		<page-tabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></page-tabbar>
+		<pageTabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></pageTabbar>
 	</view>
 </template>
 
 <script setup>
-	import pageTabbar from '@/components/page-tabbar.vue';//头部
+	import pageTabbar from '@/components/pageTabbar.vue';//头部
 	import courseCenter from './courseCenter/index.vue';//课程中心
 	import myLearning from './myLearning/index.vue';//我的研修
 	import learningMonitor from './learningMonitor/index.vue';//研修监控

+ 2 - 2
pages/teacherStudy/learningMonitor/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<nav-bar :height="state.courseType == '0'?'312':'224'" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view :class="['page_content',{'self_content_height':state.courseType == '1'}]">
@@ -47,7 +47,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {queryCourseTypeDataList,minitorPageListData} from '@/reqApi/teacherStudy.js';
 	import {
 		reactive,

+ 2 - 2
pages/teacherStudy/learningMonitor/taskDetails.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<view class="page_body">
 		<!-- 头部 -->
 		<nav-bar height="96" title="任务详情" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
@@ -156,7 +156,7 @@
 	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 	import uvSticky from '@/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue'; //吸顶
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {
 		queryMinitorDetailData
 	} from '@/reqApi/teacherStudy.js';

+ 2 - 2
pages/teacherStudy/myLearning/index.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
 	<!-- 头部 -->
 	<nav-bar height="312" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view class="page_content">
@@ -45,7 +45,7 @@
 </template>
 
 <script setup>
-	import navBar from '@/components/nav-bar.vue';
+	import navBar from '@/components/navBar.vue';
 	import {queryCourseTypeDataList,recordPageListData} from '@/reqApi/teacherStudy.js';
 	import {
 		reactive,

+ 4 - 4
pages/teacherStudy/videoDetails.vue

@@ -51,20 +51,20 @@
 			</view>
 		</view>
 		<!-- 提示框 -->
-		<custom-popup-dialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCloseBtn="false" @DialogConfirm="DialogConfirm">
+		<customPopupDialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCloseBtn="false" @DialogConfirm="DialogConfirm">
 			<view class="dialog_questions_input">
 				<view class="questions_title">{{state?.question?.codeName}}:{{state?.question?.questionsName}}</view>
 				<uni-easyinput type="textarea" v-model="state.questionsAnswer" :style="state.easyinputStyle" placeholder="请输入你的回答" placeholderStyle="font-weight: 400;font-size: 28rpx;color: #999999;"></uni-easyinput>
 			</view>
-		</custom-popup-dialog>	
+		</customPopupDialog>	
 	</view>
 </template>
 
 <script setup>
 	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
-	import navBar from '@/components/nav-bar.vue';
-	import customPopupDialog from '@/components/custom-popup-dialog.vue';
+	import navBar from '@/components/navBar.vue';
+	import customPopupDialog from '@/components/customPopupDialog.vue';
 	import { selectCourseDetail,addQuestionAnswerData,updateFollowCount,synTeachLearnData } from '@/reqApi/teacherStudy.js';
 	import {
 		onLoad,

+ 4 - 4
pages/todo/index.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="page_todo"
 		:style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
-		<page-header></page-header>
+		<pageHeader></pageHeader>
 		<view class="todo_content">
 			<!-- 待办事项 -->
 			<view class="todo_header">
@@ -40,7 +40,7 @@
 					</view>
 				</view>
 			</view>
-			<no-data v-if="todoList.length === 0" centered text="暂无待办事项"></no-data>
+			<noData v-if="todoList.length === 0" centered text="暂无待办事项"></noData>
 		</view>
 		<common-tabbar></common-tabbar>
 	</view>
@@ -50,9 +50,9 @@
 import { ref, onMounted } from 'vue';
 import { onShow } from '@dcloudio/uni-app';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
-import PageHeader from '@/components/page-header.vue';
+import PageHeader from '@/components/pageHeader.vue';
 import CommonTabbar from '@/components/commonTabbar.vue';
-import NoData from '@/components/no-data.vue';
+import NoData from '@/components/noData.vue';
 import workspace from '@/reqApi/workspace.js';
 import { useSafeArea } from '@/common/safeArea';
 import { truncateText } from '@/common/common.js';

+ 4 - 4
pages/workspace/index.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="page_workspace"
 		:style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
-		<page-header></page-header>
+		<pageHeader></pageHeader>
 		<scroll-view scroll-y class="workspace_content">
 			<!-- 系统列表 -->
 			<view class="system_list">
@@ -92,7 +92,7 @@
 				<!-- 日程列表 -->
 				<view class="schedule_list">
 					<view class="schedule_title">当日日程</view>
-					<no-data v-if="scheduleList.length === 0" text="暂无日程安排" />
+					<noData v-if="scheduleList.length === 0" text="暂无日程安排" />
 					<view v-else v-for="item in scheduleList" :key="item.id" class="schedule_item">
 						<view class="schedule_dot"></view>
 						<view class="schedule_text_container">
@@ -141,8 +141,8 @@ import { ref, onMounted, computed } from 'vue';
 import { onShow } from '@dcloudio/uni-app';
 import { useStore } from 'vuex';
 import workspaceApi from '@/reqApi/workspace.js';
-import PageHeader from '@/components/page-header.vue';
-import NoData from '@/components/no-data.vue';
+import PageHeader from '@/components/pageHeader.vue';
+import NoData from '@/components/noData.vue';
 import CommonTabbar from '@/components/commonTabbar.vue';
 import { useSafeArea } from '@/common/safeArea';
 

+ 4 - 4
pages/workspace/schedule.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="page" :style="{ paddingTop: statusBarHeight + 140 + 'rpx', paddingBottom: safeAreaBottom + 'rpx' }">
 		<!-- 导航栏 -->
-		 <common-header :title="'校历安排'" @back="handleBack"></common-header>
+		 <commonHeader :title="'校历安排'" @back="handleBack"></commonHeader>
 		<!-- <view class="nav_bar" :style="{ paddingTop: statusBarHeight + 24 + 'rpx' }">
 			<view class="nav_back" @click="handleBack">
 				<uni-icons type="back" size="26" color="#333333"></uni-icons>
@@ -51,7 +51,7 @@
 				</view>
 			</view>
 
-			<no-data v-if="currentScheduleList.length === 0" centered text="暂无日程安排"></no-data>
+			<noData v-if="currentScheduleList.length === 0" centered text="暂无日程安排"></noData>
 
 			<view v-else class="schedule_list">
 				<view v-for="item in currentScheduleList" :key="item.id" class="schedule_item">
@@ -97,8 +97,8 @@ import { ref, computed, onMounted } from 'vue';
 import workspaceApi from '@/reqApi/workspace';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import { useSafeArea } from '@/common/safeArea';
-import commonHeader from '@/components/common-header.vue';
-import NoData from '@/components/no-data.vue';
+import commonHeader from '@/components/commonHeader.vue';
+import NoData from '@/components/noData.vue';
 
 const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();