Browse Source

校内通知-问题修改

吴朋磊 2 weeks ago
parent
commit
3592c43579

+ 157 - 25
components/notice-header.vue

@@ -27,13 +27,9 @@
 				</view>
 			</picker>
 		</view>
-		<!-- 通知模式标签栏 -->
-		<view v-if="showTabs && mode === 'notice'" class="tabs_bar">
-			<text v-for="(tab, idx) in tabs" :key="idx" class="tab_item" :class="{ active: activeTabIndex === idx }" @click="handleTabChange(idx)">{{ tab }}</text>
-		</view>
-		
-		<!-- 任务总览模式过滤栏 -->
-		<view v-if="showFilter && mode === 'taskOverview'" class="filter_bar task_overview_filter">
+
+		<!-- 通用过滤栏(任务总览/材料采集模式) -->
+		<view v-if="showFilter && (mode === 'taskOverview' || mode === 'material')" class="filter_bar task_overview_filter">
 			<picker mode="selector" class="picker_wide" :range="academicYearOptions" :value="academicYearIndex" @change="handleAcademicYearChange">
 				<view class="filter_item">
 					<text class="filter_text">{{ academicYearOptions[academicYearIndex] }}</text>
@@ -53,19 +49,52 @@
 				</view>
 			</picker>
 		</view>
-		<!-- 任务总览模式标签栏 -->
-		<view v-if="(showTabs || showCreateBtn) && mode === 'taskOverview'" class="tabs_bar task_overview_tabs">
-			<text v-for="(tab, idx) in tabs" :key="idx" class="tab_item" :class="{ active: activeTabIndex === idx }" @click="handleTabChange(idx)">{{ tab }}</text>
-			<view v-if="showCreateBtn" class="create_btn" @click="handleCreate">
+
+		<!-- 材料采集模式阶段按钮 -->
+		<view v-if="showStageBtn && mode === 'material'" class="stage_btn_bar">
+			<view 
+				class="stage_btn" 
+				:class="{ active: stageIndex === 0 }"
+				@click="handleStageChange(0)"
+			>
+				<text>进行中</text>
+			</view>
+			<view 
+				class="stage_btn" 
+				:class="{ active: stageIndex === 1 }"
+				@click="handleStageChange(1)"
+			>
+				<text>已结束</text>
+			</view>
+		</view>
+
+		<!-- 通用标签栏(三种模式通用) -->
+		<view 
+			v-if="(showTabs || (showCreateBtn && mode === 'taskOverview'))" 
+			class="tabs_bar"
+			:class="{
+				'task_overview_tabs': mode === 'taskOverview',
+				'material_tabs': mode === 'material'
+			}"
+		>
+			<text 
+				v-for="(tab, idx) in tabs" 
+				:key="idx" 
+				class="tab_item" 
+				:class="{ active: activeTabIndex === idx }" 
+				@click="handleTabChange(idx)"
+			>{{ tab }}</text>
+			<view v-if="showCreateBtn && mode === 'taskOverview'" class="create_btn" @click="handleCreate">
 				<image src="/static/image/workspace/add.png" class="add_icon" mode="aspectFit"></image>
 				<text class="create_text">新建</text>
 			</view>
 		</view>
+
 	</view>
 </template>
 
 <script setup>
-import { ref, onMounted, computed } from 'vue';
+import { ref, onMounted, computed, watch } from 'vue';
 import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
 import overviewApi from '@/reqApi/overview.js';
 import { useSafeArea } from '@/common/safeArea';
@@ -81,6 +110,11 @@ const searchWord = ref('');
 const academicYearIndex = ref(0);
 const categoryIndex = ref(0);
 const materialTypeIndex = ref(0);
+const categoryDataList = ref([]); // 存储分类树形数据(来自prop)
+const categoryOptions = ref(['全部分类']); // 分类选项
+const materialTypeOptions = ref(['全部类目']); // 类目选项(当前选中分类的childList)
+
+const stageIndex = ref(0);
 
 const props = defineProps({
 	title: {
@@ -90,7 +124,7 @@ const props = defineProps({
 	mode: {
 		type: String,
 		default: 'notice',
-		validator: (value) => ['notice', 'taskOverview'].includes(value)
+		validator: (value) => ['notice', 'taskOverview', 'material'].includes(value)
 	},
 	showSearch: {
 		type: Boolean,
@@ -104,21 +138,25 @@ const props = defineProps({
 		type: Boolean,
 		default: false
 	},
+	showStageBtn: {
+		type: Boolean,
+		default: false
+	},
 	timeOptions: {
 		type: Array,
 		default: () => ['全部时间', '最近3天', '最近7天', '最近1月', '最近3月']
 	},
 	academicYearOptions: {
 		type: Array,
-		default: () => ['2025-2026学年', '2024-2025学年', '2023-2024学年']
+		default: () => ['']
 	},
-	categoryOptions: {
+	academicYearIds: {
 		type: Array,
-		default: () => ['全部分类', '日常教学', '考试测评', '教研活动']
+		default: () => ['']
 	},
-	materialTypeOptions: {
+	categoryData: {
 		type: Array,
-		default: () => ['全部类目', '教案', '课件', '试卷', '作业']
+		default: () => []
 	},
 	showTabs: {
 		type: Boolean,
@@ -153,7 +191,8 @@ const activeTabIndex = computed(() => {
 
 const emit = defineEmits([
 	'save', 'tabChange', 'typeChange', 'timeChange', 'searchChange',
-	'academicYearChange', 'categoryChange', 'materialTypeChange', 'create'
+	'academicYearChange', 'categoryChange', 'materialTypeChange', 'create',
+	'stageChange'
 ]);
 
 const fetchNoticeTypeList = async () => {
@@ -179,6 +218,26 @@ onMounted(() => {
 	}
 });
 
+// 监听学年学期数据,默认选中第一条实际数据(跳过"全部")
+watch(() => props.academicYearOptions, (newVal) => {
+	if (newVal && newVal.length > 0) {
+		academicYearIndex.value = 0;
+	}
+}, { immediate: true });
+
+// 监听分类数据,填充分类选项
+watch(() => props.categoryData, (newVal) => {
+	if (newVal && Array.isArray(newVal)) {
+		categoryDataList.value = newVal;
+		// 分类选项:全部分类 + 一级分类名称
+		const categoryNames = newVal.map(item => item.categoryName);
+		categoryOptions.value = ['全部分类', ...categoryNames];
+		// 默认选中全部分类,类目为空
+		categoryIndex.value = 0;
+		materialTypeOptions.value = ['全部类目'];
+	}
+}, { immediate: true });
+
 const handleTabChange = (idx) => {
 	if (props.tabMode === 'index') {
 		emit('tabChange', idx);
@@ -201,17 +260,38 @@ const handleTimeChange = (e) => {
 
 const handleAcademicYearChange = (e) => {
 	academicYearIndex.value = e.detail.value;
-	emit('academicYearChange', academicYearIndex.value);
+	const selectedId = props.academicYearIds[academicYearIndex.value] || '';
+	emit('academicYearChange', academicYearIndex.value, selectedId);
 };
 
 const handleCategoryChange = (e) => {
 	categoryIndex.value = e.detail.value;
-	emit('categoryChange', categoryIndex.value);
+	materialTypeIndex.value = 0;
+	if (categoryIndex.value === 0) {
+		// 全部分类:类目默认只有"全部类目"
+		materialTypeOptions.value = ['全部类目'];
+		emit('categoryChange', categoryIndex.value, '');
+	} else {
+		// 具体分类:加载该分类下的 childList 作为类目选项(前面加"全部类目")
+		const selectedCategory = categoryDataList.value[categoryIndex.value - 1];
+		const childList = selectedCategory?.childList || [];
+		materialTypeOptions.value = ['全部类目', ...childList.map(child => child.categoryName)];
+		emit('categoryChange', categoryIndex.value, selectedCategory?.id || '');
+	}
 };
 
 const handleMaterialTypeChange = (e) => {
 	materialTypeIndex.value = e.detail.value;
-	emit('materialTypeChange', materialTypeIndex.value);
+	if (categoryIndex.value === 0) {
+		// 全部分类时,类目只有"全部类目"
+		emit('materialTypeChange', materialTypeIndex.value, '');
+	} else {
+		const selectedCategory = categoryDataList.value[categoryIndex.value - 1];
+		const childList = selectedCategory?.childList || [];
+		// index=0 是"全部类目",无对应ID
+		const selectedChildId = materialTypeIndex.value === 0 ? '' : (childList[materialTypeIndex.value - 1]?.id || '');
+		emit('materialTypeChange', materialTypeIndex.value, selectedChildId);
+	}
 };
 
 const goBack = () => {
@@ -231,6 +311,11 @@ const handleSearchInput = () => {
 const handleCreate = () => {
 	emit('create');
 };
+
+const handleStageChange = (idx) => {
+	stageIndex.value = idx;
+	emit('stageChange', idx);
+};
 </script>
 
 <style lang="scss" scoped>
@@ -248,7 +333,7 @@ const handleCreate = () => {
 		display: flex;
 		align-items: center;
 		justify-content: space-between;
-		padding:36rpx 24rpx 24rpx;
+		padding:56rpx 24rpx 24rpx;
 
 		.header_left {
 			display: flex;
@@ -267,10 +352,11 @@ const handleCreate = () => {
 			align-items: center;
 			background-color: #fff;
 			border-radius: 8rpx;
-			padding: 14rpx 24rpx;
+			padding: 0rpx 24rpx;
 			width: 265rpx;
 			border: 2rpx solid #DCDFE6;
-
+			height: 72rpx;
+			line-height: 72rpx;
 			.search_icon {
 				width: 32rpx;
 				height: 32rpx;
@@ -326,6 +412,9 @@ const handleCreate = () => {
 
 		.picker_wide {
 			flex: 1.2;
+			.filter_text{
+				max-width: 240rpx !important;
+			}
 		}
 
 		.picker_narrow {
@@ -342,6 +431,8 @@ const handleCreate = () => {
 				overflow: hidden;
 				text-overflow: ellipsis;
 				white-space: nowrap;
+				max-width: 140rpx;
+				margin-right: 0rpx;
 			}
 		}
 	}
@@ -411,5 +502,46 @@ const handleCreate = () => {
 			}
 		}
 	}
+
+	// 材料采集模式 - 阶段按钮
+	.stage_btn_bar {
+		display: flex;
+		gap: 30rpx;
+		padding:0rpx 24rpx 24rpx 24rpx;
+
+		.stage_btn {
+			flex: 1;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			height: 64rpx;
+			border-radius: 8rpx;
+			font-size: 28rpx;
+			color: #333333;
+			border: 2rpx solid #DCDFE6;
+			border-radius: 8rpx 8rpx 8rpx 8rpx;
+			&.active {
+				background: rgba(46,100,250,0.1);
+				color: #2E64FA;
+				border: 2rpx solid #2E64FA;
+			}
+		}
+	}
+
+	// 材料采集模式 - 标签栏
+	.material_tabs {
+		height: 48rpx;
+		padding: 4rpx 24rpx 16rpx 24rpx;
+
+		.tab_item {
+			font-size: 30rpx;
+			margin-right: 40rpx;
+
+			&.active {
+				color: #2E64FA;
+				font-weight: 500;
+			}
+		}
+	}
 }
 </style>

+ 3 - 2
components/search-bar.vue

@@ -67,11 +67,12 @@ const handleInput = (e) => {
 <style lang="scss">
 
 .search_container {
+	padding: 0 24rpx;
 	padding-bottom: 24rpx;
-	margin: 0 24rpx;
 	border-bottom: 2rpx solid #F3F3F3;
 	display: flex;
-
+	background-color: #FFFFFF;
+	
 	.search_box {
 		flex: 1;
 		display: flex;

+ 17 - 11
components/tree-node.vue

@@ -8,10 +8,10 @@
 						<image v-else-if="item.checked && (!item.children || item.children.length === 0)" src="/static/image/publish/correct.png" class="checkbox_check_image" mode="aspectFit"></image>
 					</view>
 				</view>
-				<text class="row_text" :class="{ 'leaf-node': !item.children || item.children.length === 0 }">
-					{{ item.name }}
-					<text v-if="item.userAccount" class="user-account">({{ item.userAccount }})</text>
-				</text>
+				<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>
@@ -124,22 +124,28 @@ export default {
 			}
 		}
 
-		.row_text {
+		.row_text_container {
 			flex: 1;
+			display: flex;
+			align-items: center;
+			overflow: hidden;
+		}
+
+		.row_text {
 			font-size: 28rpx;
 			color: #333333;
 
-			.user-account {
-				font-size: 24rpx;
-				color: #999999;
-				margin-left: 8rpx;
-			}
-
 			&.leaf-node {
 				color: #666666;
 			}
 		}
 
+		.row_account {
+			font-size: 28rpx;
+			color: #666666;
+			margin-left: 12rpx;
+		}
+
 		.expand_icon {
 				flex-shrink: 0;
 				margin-left: 8rpx;

+ 356 - 192
pages/materialCollection/submitMaterial/index.vue

@@ -1,260 +1,424 @@
 <template>
-	<view class="page_submit" :style="{paddingTop: statusBarHeight + 200 + 'rpx', paddingBottom: safeAreaBottom + 100 + 'rpx' }">
+	<view class="page_notice" :style="{paddingTop: statusBarHeight + 400 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
 		<notice-header 
-			:title="isEdit ? '编辑任务' : '新建任务'" 
-			mode="submit"
-			:showSave="true"
-			@save="handleSave">
+			title="提交材料" 
+			mode="material"
+			: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">
 		</notice-header>
 
-		<view class="form_container">
-			<scroll-view scroll-y class="form_scroll">
-				<view class="form_section">
-					<view class="section_title">基本信息</view>
-					
-					<view class="form_item">
-						<text class="form_label">任务标题</text>
-						<input class="form_input" v-model="formData.title" placeholder="请输入任务标题" />
-					</view>
-
-					<view class="form_item">
-						<text class="form_label">学年</text>
-						<picker mode="selector" :range="academicYearOptions" :value="academicYearIndex" @change="handleAcademicYearChange">
-							<view class="picker_item">
-								<text>{{ academicYearOptions[academicYearIndex] }}</text>
-								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+		<view 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="notice_meta">
+							<view class="status_tag" :class="statusClassMap[item.taskStatus] || ''">
+								<text>{{ taskStatusMap[item.taskStatus] }}</text>
 							</view>
-						</picker>
-					</view>
-
-					<view class="form_item">
-						<text class="form_label">归属分类</text>
-						<picker mode="selector" :range="categoryOptions" :value="categoryIndex" @change="handleCategoryChange">
-							<view class="picker_item">
-								<text>{{ categoryOptions[categoryIndex] }}</text>
-								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							<view class="need_shenhe" v-if="item.auditMechanism">
+								<text>审</text>
 							</view>
-						</picker>
+							<text class="meta_text textLength">{{ item.departmentName }}</text>
+							<text class="meta_text textLength">{{ item.materialCategoryName }}</text>
+							<text class="meta_text">{{ item.createdBy }}</text>
+						</view>
 					</view>
-
-					<view class="form_item">
-						<text class="form_label">材料类目</text>
-						<picker mode="selector" :range="materialTypeOptions" :value="materialTypeIndex" @change="handleMaterialTypeChange">
-							<view class="picker_item">
-								<text>{{ materialTypeOptions[materialTypeIndex] }}</text>
-								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+					<!-- 任务底部 -->
+					<view class="item_bottom">
+						<view class="notice_stats">
+							<text class="stats_text">开始时间: {{ item.startTime }}</text>
+						</view>
+						<view class="notice_actions">
+							<view class="action_btn delete" :class="{ disable: !!item.submitNum }">
+								<text>删除</text>
 							</view>
-						</picker>
-					</view>
-				</view>
-
-				<view class="form_section">
-					<view class="section_title">时间设置</view>
-					
-					<view class="form_item">
-						<text class="form_label">开始日期</text>
-						<picker mode="date" :value="formData.startDate" @change="handleStartDateChange">
-							<view class="picker_item">
-								<text>{{ formData.startDate || '请选择开始日期' }}</text>
-								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							<view class="action_btn edit" >
+								<text>编辑</text>
 							</view>
-						</picker>
-					</view>
-
-					<view class="form_item">
-						<text class="form_label">截止日期</text>
-						<picker mode="date" :value="formData.deadline" @change="handleDeadlineChange">
-							<view class="picker_item">
-								<text>{{ formData.deadline || '请选择截止日期' }}</text>
-								<uni-icons type="down" size="16" color="#999999"></uni-icons>
+							<view class="action_btn edit" :class="{ disableLock: item.taskStatus === 0 }">
+								<text>查看</text>
 							</view>
-						</picker>
+						</view>
 					</view>
-				</view>
-
-				<view class="form_section">
-					<view class="section_title">任务描述</view>
-					<textarea 
-						class="form_textarea" 
-						v-model="formData.description" 
-						placeholder="请输入任务描述" 
-						placeholder-class="textarea_placeholder"
-						maxlength="500"
-					></textarea>
-				</view>
-			</scroll-view>
+				</scroll-view>
+
+				<no-data v-if="taskList.length === 0" centered text="暂无任务" />
+			</view>
 		</view>
+
+		<common-tabbar></common-tabbar>
 	</view>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue';
-import { onLoad } from '@dcloudio/uni-app';
+import { ref, onMounted } from 'vue';
 import NoticeHeader from '@/components/notice-header.vue';
-import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
+import CommonTabbar from '@/components/commonTabbar.vue';
+import NoData from '@/components/no-data.vue';
 import { useSafeArea } from '@/common/safeArea';
+import notification from '@/reqApi/notification.js';
 
 const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
-const taskId = ref(null);
-const isEdit = computed(() => !!taskId.value);
-
-const academicYearOptions = ['2025-2026学年', '2024-2025学年', '2023-2024学年'];
-const categoryOptions = ['全部分类', '日常教学', '考试测评', '教研活动'];
-const materialTypeOptions = ['全部类目', '教案', '课件', '试卷', '作业'];
-
+const tabs = ['待提交', '审核中', '已驳回', '已提交'];
+const activeTab = ref(-1);
+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 formData = ref({
-	title: '',
-	startDate: '',
-	deadline: '',
-	description: ''
-});
+const statusClassMap = {
+	0: 'status_pending',
+	1: 'status_published',
+	2: 'status_closed'
+};
+
+const taskStatusMap = {
+	0: '待开始',
+	1: '进行中',
+	2: '已结束'
+};
+
+const fetchTaskList = async () => {
+	taskList.value = [];
+	try {
+		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,
+			pageNum: 1,
+			pageSize: 9999
+		};
+		const res = await notification.find_collection_task(params);
+		if (res.data && res.data.records) {
+			taskList.value = res.data.records
+		} else {
+			taskList.value = [];
+		}
+	} catch (error) {
+		console.error('获取任务列表失败:', error);
+		taskList.value = [];
+	}
+};
 
-onLoad((options) => {
-	if (options && options.id) {
-		taskId.value = options.id;
-		loadTaskDetail(options.id);
+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(() => {
+onMounted(async () => {
 	initSafeArea();
+	await fetchSchoolYearList();
+	fetchCategoryList();
+	fetchTaskList();
 });
 
-const loadTaskDetail = (id) => {
-	// 模拟加载任务详情
-	formData.value = {
-		title: '2025年秋季学期教案材料收集',
-		startDate: '2025-07-28',
-		deadline: '2025-08-15',
-		description: '请各位教师按时提交本学期的教案材料'
-	};
+const handleTabChange = (status) => {
+	activeTab.value = status;
+	fetchTaskList();
 };
 
-const handleAcademicYearChange = (e) => {
-	academicYearIndex.value = e.detail.value;
+const handleStageChange = (index) => {
+	stageIndex.value = index;
+	// stageIndex: 0-进行中, 1-已结束
+	// 后续可以根据阶段筛选任务列表
+	fetchTaskList();
 };
 
-const handleCategoryChange = (e) => {
-	categoryIndex.value = e.detail.value;
+const handleAcademicYearChange = (index, id) => {
+	academicYearIndex.value = index;
+	currentSchoolYearId.value = id || '';
+	currentSchoolYearCode.value = academicYearCodes.value[index] || '';
+	fetchTaskList();
 };
-
-const handleMaterialTypeChange = (e) => {
-	materialTypeIndex.value = e.detail.value;
+// 分类选择
+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 handleStartDateChange = (e) => {
-	formData.value.startDate = e.detail.value;
+const handleSearchChange = (keyword) => {
+	word.value = keyword;
+	fetchTaskList();
 };
 
-const handleDeadlineChange = (e) => {
-	formData.value.deadline = e.detail.value;
+const handleCreate = () => {
+	uni.navigateTo({
+		url: '/pages/materialCollection/submitMaterial/index'
+	});
 };
 
-const handleSave = () => {
-	if (!formData.value.title) {
-		uni.showToast({ title: '请输入任务标题', icon: 'none' });
-		return;
-	}
-	
-	uni.showModal({
-		title: '提示',
-		content: isEdit.value ? '确定要保存修改吗?' : '确定要创建任务吗?',
-		success: (res) => {
-			if (res.confirm) {
-				uni.showToast({ 
-					title: isEdit.value ? '保存成功' : '创建成功', 
-					icon: 'success' 
-				});
-				setTimeout(() => {
-					uni.navigateBack();
-				}, 1500);
-			}
-		}
+const handleDetail = (id) => {
+	uni.navigateTo({
+		url: `/pages/materialCollection/submitMaterial/index?id=${id}`
 	});
 };
 </script>
 
-<style lang="scss" scoped>
-.page_submit {
+<style lang="scss">
+.page_notice {
 	min-height: 100vh;
-	background-color: #F5F7FA;
+	display: flex;
+	flex-direction: column;
+	background-color: #FFFFFF;
+	box-sizing: border-box;
 }
 
-.form_container {
+.notice_content {
 	flex: 1;
-	padding: 24rpx;
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+	padding-left: 0rpx;
+	padding-right: 0rpx;
 }
 
-.form_scroll {
-	height: 100%;
+.notice_list {
+	flex: 1;
+	overflow-y: auto;
+	overflow-x: hidden;
+	padding: 0 24rpx;
+	display: flex;
+	flex-direction: column;
 }
 
-.form_section {
-	background-color: #FFFFFF;
-	border-radius: 16rpx;
-	padding: 24rpx;
+.notice_item {
+	background-color: #F9FAFF;
+	border-radius: 20rpx;
 	margin-bottom: 24rpx;
+	border: 2rpx solid #E4E7ED;
 
-	.section_title {
-		font-size: 30rpx;
-		font-weight: 600;
-		color: #333333;
-		margin-bottom: 24rpx;
-	}
-}
+	.item_content {
+		padding: 24rpx;
 
-.form_item {
-	display: flex;
-	align-items: center;
-	padding: 24rpx 0;
-	border-bottom: 1rpx solid #EBEEF5;
+		.notice_title_row {
+			display: flex;
+			align-items: center;
+			margin-bottom: 16rpx;
 
-	&:last-child {
-		border-bottom: none;
-	}
+			.notice_icon {
+				width: 32rpx;
+				height: 32rpx;
+				margin-right: 16rpx;
+			}
 
-	.form_label {
-		width: 180rpx;
-		font-size: 28rpx;
-		color: #666666;
-	}
+			.notice_title {
+				flex: 1;
+				font-weight: 500;
+				font-size: 28rpx;
+				color: #333333;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+			}
+		}
 
-	.form_input {
-		flex: 1;
-		font-size: 28rpx;
-		color: #333333;
-		text-align: right;
+		.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_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;
+				}
+		}
 	}
 
-	.picker_item {
-		flex: 1;
+	.item_bottom {
+		border-top: 2rpx solid #E4E7ED;
 		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
 		align-items: center;
-		justify-content: flex-end;
-		font-size: 28rpx;
-		color: #333333;
-		gap: 8rpx;
-	}
-}
+		padding: 24rpx 16rpx;
 
-.form_textarea {
-	width: 100%;
-	min-height: 200rpx;
-	font-size: 28rpx;
-	color: #333333;
-	padding: 16rpx;
-	background-color: #F9FAFF;
-	border-radius: 12rpx;
-	box-sizing: border-box;
-}
+		.notice_stats {
+			display: flex;
 
-.textarea_placeholder {
-	color: #999999;
-	font-size: 28rpx;
+			.stats_text {
+				font-size: 24rpx;
+				color: #999999;
+
+				&:first-child {
+					margin-right: 32rpx;
+				}
+			}
+		}
+
+		.notice_actions {
+			display: flex;
+			justify-content: flex-end;
+			gap: 16rpx;
+
+			.action_btn {
+				font-size: 24rpx;
+				padding: 12rpx 24rpx;
+				border-radius: 8rpx;
+				border: 2rpx solid transparent;
+
+				&.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;
+
+					&.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>

+ 169 - 103
pages/materialCollection/taskOverview/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="page_notice" :style="{paddingTop: statusBarHeight + 310 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
+	<view class="page_notice" :style="{paddingTop: statusBarHeight + 332 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
 		<notice-header 
 			title="任务总览" 
 			mode="taskOverview"
@@ -8,7 +8,10 @@
 			:showTabs="true" 
 			:showCreateBtn="true"
 			:tabs="tabs"
-			:activeTab="activeTab" 
+			:activeTab="activeTab"
+			:academicYearOptions="academicYearOptions"
+			:academicYearIds="academicYearIds"
+			:categoryData="categoryData"
 			@tabChange="handleTabChange" 
 			@academicYearChange="handleAcademicYearChange"
 			@categoryChange="handleCategoryChange"
@@ -20,28 +23,38 @@
 		<view class="notice_content">
 			<view class="notice_list">
 				<scroll-view scroll-y v-for="item in taskList" :key="item.id" class="notice_item">
+					<!-- 任务内容 -->
 					<view class="item_content">
 						<view class="notice_title_row">
-							<image class="notice_icon" :src="item.icon" mode="aspectFit"></image>
-							<text class="notice_title">{{ item.title }}</text>
+							<image class="notice_icon" src="/static/image/materialCollection/docIcon.png" mode="aspectFit"></image>
+							<text class="notice_title">{{ item.taskName }}</text>
 						</view>
 						<view class="notice_meta">
-							<view class="status_tag" :class="item.statusClass">
-								<text>{{ item.status }}</text>
+							<view class="status_tag" :class="statusClassMap[item.taskStatus] || ''">
+								<text>{{ taskStatusMap[item.taskStatus] }}</text>
 							</view>
-							<text class="meta_text textLength">{{ item.academicYear }}</text>
-							<text class="meta_text textLength">{{ item.category }}</text>
-							<text class="meta_text">{{ item.time }}</text>
+							<view 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>
 						</view>
 					</view>
+					<!-- 任务底部 -->
 					<view class="item_bottom">
 						<view class="notice_stats">
-							<text class="stats_text">提交人: {{ item.submitter }}</text>
-							<text class="stats_text">截止: {{ item.deadline }}</text>
+							<text class="stats_text">开始时间: {{ item.startTime }}</text>
 						</view>
 						<view class="notice_actions">
-							<view class="action_btn detail" @click="handleDetail(item.id)">
-								<text>详情</text>
+							<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>
@@ -61,14 +74,23 @@ import NoticeHeader from '@/components/notice-header.vue';
 import CommonTabbar from '@/components/commonTabbar.vue';
 import NoData from '@/components/no-data.vue';
 import { useSafeArea } from '@/common/safeArea';
+import notification from '@/reqApi/notification.js';
 
 const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const tabs = ['全部', '待开始', '进行中', '已结束'];
 const activeTab = ref(-1);
 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([]);
 
@@ -79,83 +101,79 @@ const statusMap = {
 };
 
 const statusClassMap = {
-	0: 'status-pending',
-	1: 'status-progress',
-	2: 'status-ended'
+	0: 'status_pending',
+	1: 'status_published',
+	2: 'status_closed'
+};
+
+const taskStatusMap = {
+	0: '待开始',
+	1: '进行中',
+	2: '已结束'
 };
 
-const mockTaskList = [
-	{
-		id: 1,
-		title: '2025年秋季学期教案材料收集',
-		status: '待开始',
-		statusClass: 'status-pending',
-		academicYear: '2025-2026学年',
-		category: '日常教学',
-		time: '2025-07-28',
-		submitter: '张老师',
-		deadline: '2025-08-15',
-		icon: '/static/image/workspace/icon2.png'
-	},
-	{
-		id: 2,
-		title: '期中考试试卷提交任务',
-		status: '进行中',
-		statusClass: 'status-progress',
-		academicYear: '2025-2026学年',
-		category: '考试测评',
-		time: '2025-07-25',
-		submitter: '李老师',
-		deadline: '2025-08-30',
-		icon: '/static/image/workspace/icon1.png'
-	},
-	{
-		id: 3,
-		title: '课件资源库更新任务',
-		status: '进行中',
-		statusClass: 'status-progress',
-		academicYear: '2025-2026学年',
-		category: '日常教学',
-		time: '2025-07-20',
-		submitter: '王老师',
-		deadline: '2025-09-01',
-		icon: '/static/image/workspace/icon3.png'
-	},
-	{
-		id: 4,
-		title: '教研活动材料归档',
-		status: '已结束',
-		statusClass: 'status-ended',
-		academicYear: '2024-2025学年',
-		category: '教研活动',
-		time: '2025-06-15',
-		submitter: '赵老师',
-		deadline: '2025-07-01',
-		icon: '/static/image/workspace/icon4.png'
+const fetchTaskList = async () => {
+	taskList.value = [];
+	try {
+		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,
+			pageNum: 1,
+			pageSize: 9999
+		};
+		const res = await notification.find_collection_task(params);
+		if (res.data && res.data.records) {
+			taskList.value = res.data.records
+		} else {
+			taskList.value = [];
+		}
+	} catch (error) {
+		console.error('获取任务列表失败:', error);
+		taskList.value = [];
 	}
-];
-
-const fetchTaskList = () => {
-	let filteredList = [...mockTaskList];
-	
-	if (activeTab.value >= 0) {
-		filteredList = filteredList.filter(item => {
-			const statusIndex = Object.keys(statusMap).findIndex(key => statusMap[key] === item.status);
-			return statusIndex === activeTab.value;
-		});
+};
+
+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);
 	}
-	
-	if (word.value) {
-		filteredList = filteredList.filter(item => 
-			item.title.includes(word.value)
-		);
+};
+
+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);
 	}
-	
-	taskList.value = filteredList;
 };
 
-onMounted(() => {
+onMounted(async () => {
 	initSafeArea();
+	await fetchSchoolYearList();
+	fetchCategoryList();
 	fetchTaskList();
 });
 
@@ -164,18 +182,25 @@ const handleTabChange = (status) => {
 	fetchTaskList();
 };
 
-const handleAcademicYearChange = (index) => {
+const handleAcademicYearChange = (index, id) => {
 	academicYearIndex.value = index;
+	currentSchoolYearId.value = id || '';
+	currentSchoolYearCode.value = academicYearCodes.value[index] || '';
 	fetchTaskList();
 };
-
-const handleCategoryChange = (index) => {
+// 分类选择
+const handleCategoryChange = (index, id) => {
 	categoryIndex.value = index;
+	categoryId.value = id;
+	// 清空类目选择
+	materialTypeIndex.value = 0;
+	materialTypeId.value = null;
 	fetchTaskList();
 };
-
-const handleMaterialTypeChange = (index) => {
+// 类目选择
+const handleMaterialTypeChange = (index, id) => {
 	materialTypeIndex.value = index;
+	materialTypeId.value = id;
 	fetchTaskList();
 };
 
@@ -271,33 +296,42 @@ const handleDetail = (id) => {
 				font-weight: 400;
 				border-radius: 8rpx;
 
-				&.status-pending {
+				&.status_pending {
 					background: rgba(46, 100, 250, 0.1);
 					color: #2E64FA;
 				}
 
-				&.status-progress {
-					background: rgba(230, 162, 60, 0.1);
-					color: #E6A23C;
+				&.status_published {
+					background: rgba(82, 196, 26, 0.1);
+					color: #2BC644;
 				}
 
-				&.status-ended {
-					background: rgba(144, 147, 153, 0.1);
-					color: #909399;
+				&.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;
-			}
+					max-width: 160rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
 		}
 	}
 
@@ -333,11 +367,43 @@ const handleDetail = (id) => {
 				border-radius: 8rpx;
 				border: 2rpx solid transparent;
 
-				&.detail {
-					background-color: #2E64FA;
-					color: #FFFFFF;
+				&.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;
+
+					&.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;
 				}
 			}
+			
 		}
 	}
 

+ 3 - 1
pages/message/index.vue

@@ -4,7 +4,7 @@
 		<view class="message_content">
 			<view class="message_header">
 				<text class="message_title">系统消息({{ unreadCount }}项)</text>
-				<text class="mark_all_read" @click="mark_all_read">全部已读</text>
+				<text class="mark_all_read" @click="mark_all_read" v-if="messageList.length > 0">全部已读</text>
 			</view>
 			<view class="message_list" v-if="messageList.length > 0">
 				<view v-for="item in messageList" :key="item.id" class="message_item">
@@ -145,6 +145,8 @@
 		font-weight: 500;
 		font-size: 32rpx;
 		color: #333333;
+		height: 64rpx;
+		line-height: 64rpx;
 	}
 
 	.mark_all_read {

+ 25 - 5
pages/notice/mine/detail.vue

@@ -1,8 +1,8 @@
 <template>
-	<view class="page_detail">
+	<view class="page_detail" :style="{ paddingBottom: safeAreaBottom + 'rpx' }">
 		<common-header title="消息通知" @back="goBack"></common-header>
 
-		<view class="detail_content">
+		<view class="detail_content" :style="{ paddingTop: statusBarHeight + 130 + 'rpx' }">
 			<view class="detail_header">
 				<text class="detail_title">{{ noticeDetail.noticeName }}</text>
 				<view class="detail_meta">
@@ -30,9 +30,12 @@
 		<view class="attachment_modal" v-if="showAttachmentModal" @click="showAttachmentModal = false">
 			<view class="modal_content" @click.stop>
 				<view class="modal_header">
+					<view class="modal_back" @click="showAttachmentModal = false">
+						<uni-icons type="back" size="24" color="#333333"></uni-icons>
+					</view>
 					<text class="modal_title">查看附件</text>
 					<view class="modal_close" @click="showAttachmentModal = false">
-						<text>×</text>
+						<uni-icons type="closeempty" size="20" color="#999999"></uni-icons>
 					</view>
 				</view>
 				<view class="modal_list_wrapper">
@@ -52,10 +55,13 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onMounted } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
 import CommonHeader from '@/components/common-header.vue';
 import overviewApi from '@/reqApi/overview.js';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
 
 const noticeDetail = ref({
 	noticeName: '',
@@ -95,6 +101,10 @@ const fetchDetail = async (id) => {
 	}
 };
 
+onMounted(() => {
+	initSafeArea();
+});
+
 onLoad((options) => {
 	if (options && options.id) {
 		fetchDetail(options.id);
@@ -184,7 +194,7 @@ const goBack = () => {
 	.section_scroll_wrapper {
 		flex: 1;
 		overflow: hidden;
-		padding: 0 24rpx 90rpx 24rpx;
+		padding: 0 24rpx 30rpx 24rpx;
 		box-sizing: border-box;
 
 		.section_content {
@@ -236,10 +246,20 @@ const goBack = () => {
 			padding: 32rpx;
 			border-bottom: 2rpx solid #F3F3F3;
 
+			.modal_back {
+				width: 64rpx;
+				height: 64rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+			}
+
 			.modal_title {
+				flex: 1;
 				font-weight: 500;
 				font-size: 32rpx;
 				color: #333333;
+				text-align: center;
 			}
 
 			.modal_close {

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

@@ -1,5 +1,5 @@
 <template>
-	<view class="page_mine" :style="{paddingTop: statusBarHeight + 322 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
+	<view class="page_mine" :style="{paddingTop: statusBarHeight + 352 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
 		<notice-header title="我的通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
 			:activeTab="activeTab" tabMode="index" @tabChange="handleTabChange" @typeChange="handleTypeChange" @timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
 

+ 360 - 67
pages/notice/mine/preview.vue

@@ -1,98 +1,312 @@
 <template>
 	<view class="page_preview">
 		<common-header :title="fileName" @back="goBack"></common-header>
-		
-		<web-view 
-			v-if="previewUrl" 
-			:src="previewUrl" 
+
+		<!-- #ifdef APP-PLUS -->
+		<view class="app_container">
+			<view v-if="showLoading" class="loading_container">
+				<text class="loading_text">{{ loadingText }}</text>
+			</view>
+
+			<view v-if="showError" class="error_container">
+				<text class="error_title">文件暂时无法预览</text>
+				<text class="error_text">{{ errorMessage }}</text>
+				<view class="error_btn" @click="retryPreview">
+					<text class="error_btn_text">重新尝试</text>
+				</view>
+			</view>
+
+			<view v-if="showDownloadBtn && !showLoading && !showError" class="download_container">
+				<text class="file_name">{{ fileName }}</text>
+				<text class="file_tip">{{ fileTip }}</text>
+				<view class="download_btn" @click="handleDownload">
+					<text class="download_btn_text">点击下载预览</text>
+				</view>
+			</view>
+		</view>
+		<!-- #endif -->
+
+		<!-- #ifdef H5 -->
+		<web-view
+			v-if="resolvedPreviewUrl"
+			:src="resolvedPreviewUrl"
 			class="preview_webview"
 			@error="handleWebViewError"
 			@load="handleWebViewLoad"
 		></web-view>
-		
-		<view v-if="showRetry" class="retry_container">
-			<text class="retry_text">预览加载失败,正在尝试其他服务...</text>
-		</view>
-		
+
 		<view v-if="showLoading" class="loading_container">
 			<text class="loading_text">加载中...</text>
 		</view>
+
+		<view v-if="showError" class="error_container">
+			<text class="error_title">文件暂时无法预览</text>
+			<text class="error_text">当前文件可能不支持在线预览,或预览服务暂时不可用。</text>
+			<view class="error_btn" @click="retryPreview">
+				<text class="error_btn_text">重新尝试</text>
+			</view>
+		</view>
+		<!-- #endif -->
 	</view>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue';
+import { ref, onMounted, onUnmounted } from 'vue';
 import { onLoad } from '@dcloudio/uni-app';
 import CommonHeader from '@/components/common-header.vue';
 
+const PREVIEW_STORAGE_KEY = 'noticePreviewFile';
 const fileUrl = ref('');
 const fileName = ref('文件预览');
-const currentServiceIndex = ref(0);
-const showRetry = ref(false);
+const fileExtension = ref('');
+const resolvedPreviewUrl = ref('');
 const showLoading = ref(true);
+const showError = ref(false);
+const showDownloadBtn = ref(false);
+const loadingText = ref('正在加载...');
+const errorMessage = ref('当前文件可能不支持在线预览,或预览服务暂时不可用。');
+const fileTip = ref('当前文件需下载后才能预览');
 
-const isPdf = computed(() => {
-	const ext = fileUrl.value.split('.').pop()?.toLowerCase();
-	return ext === 'pdf';
-});
+const getFileExtension = (url) => {
+	if (!url) return '';
+	const cleanUrl = url.split('?')[0].split('#')[0];
+	return cleanUrl.split('.').pop()?.toLowerCase() || '';
+};
 
-const previewServices = [
-	(fileUrl) => `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(fileUrl)}`,
-	(fileUrl) => `http://api.idocv.com/view/url?url=${encodeURIComponent(fileUrl)}&name=${encodeURIComponent(fileName.value)}`,
-	(fileUrl) => fileUrl
-];
-
-const previewUrl = computed(() => {
-	const ext = fileUrl.value.split('.').pop()?.toLowerCase();
-	if (!ext) return fileUrl.value;
-	
-	if (ext === 'pdf') {
-		return `/static/pdfh5/index.html?url=${encodeURIComponent(fileUrl.value)}`;
-	}
-	
-	if (['docx', 'doc', 'xlsx', 'xls', 'ppt', 'pptx', 'txt', 'csv', 'wps', 'dps', 'et'].includes(ext)) {
-		return previewServices[currentServiceIndex.value](fileUrl.value);
-	}
-	
-	return fileUrl.value;
-});
+const isImageFile = (ext) => {
+	return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext);
+};
 
-const handleWebViewError = () => {
-	if (!isPdf.value && currentServiceIndex.value < previewServices.length - 1) {
-		showRetry.value = true;
-		currentServiceIndex.value++;
-		setTimeout(() => {
-			showRetry.value = false;
-		}, 2000);
+const isPdfFile = (ext) => ext === 'pdf';
+
+const isOfficeFile = (ext) => {
+	return ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv'].includes(ext);
+};
+
+const applyPreviewData = (data) => {
+	if (!data) return;
+
+	if (data.url) fileUrl.value = data.url;
+	if (data.name) fileName.value = data.name;
+
+	fileExtension.value = getFileExtension(fileUrl.value);
+
+	// #ifdef APP-PLUS
+	initAppPreview();
+	// #endif
+
+	// #ifdef H5
+	initH5Preview();
+	// #endif
+};
+
+// #ifdef APP-PLUS
+const initAppPreview = () => {
+	showLoading.value = true;
+	showError.value = false;
+	showDownloadBtn.value = false;
+
+	const ext = fileExtension.value;
+
+	if (!fileUrl.value) {
+		showLoading.value = false;
+		showError.value = true;
+		errorMessage.value = '文件地址无效';
+		return;
+	}
+
+	if (isImageFile(ext)) {
+		previewImage();
+	} else if (isPdfFile(ext) || isOfficeFile(ext)) {
+		downloadAndOpenFile();
 	} else {
 		showLoading.value = false;
-		uni.showToast({
-			title: '预览失败,请重试',
-			icon: 'none'
-		});
+		showDownloadBtn.value = true;
+		fileTip.value = `当前文件类型(.${ext || '未知'})需下载后查看`;
+	}
+};
+
+const previewImage = () => {
+	showLoading.value = true;
+	loadingText.value = '正在加载图片...';
+
+	uni.previewImage({
+		urls: [fileUrl.value],
+		current: fileUrl.value,
+		fail: (err) => {
+			console.error('预览图片失败:', err);
+			downloadAndOpenFile();
+		}
+	});
+};
+
+const downloadAndOpenFile = () => {
+	showLoading.value = true;
+	loadingText.value = '正在下载文件...';
+	showError.value = false;
+
+	const token = uni.getStorageSync('token');
+
+	const downloadOptions = {
+		url: fileUrl.value,
+		success: (res) => {
+			if (res.statusCode === 200) {
+				openFile(res.tempFilePath);
+			} else {
+				showLoading.value = false;
+				showError.value = true;
+				errorMessage.value = `下载失败(${res.statusCode}),请检查网络后重试`;
+			}
+		},
+		fail: (err) => {
+			console.error('下载文件失败:', err);
+			showLoading.value = false;
+			showError.value = true;
+			errorMessage.value = '下载失败,请检查网络连接后重试';
+		}
+	};
+
+	if (token) {
+		downloadOptions.header = {
+			'Authorization': token
+		};
 	}
+
+	uni.downloadFile(downloadOptions);
+};
+
+const openFile = (filePath) => {
+	showLoading.value = false;
+
+	uni.openDocument({
+		filePath: filePath,
+		showMenu: true,
+		fail: (err) => {
+			console.error('打开文件失败:', err);
+			uni.showToast({
+				title: '当前设备不支持预览此文件',
+				icon: 'none'
+			});
+		}
+	});
+};
+
+const handleDownload = () => {
+	downloadAndOpenFile();
+};
+// #endif
+
+// #ifdef H5
+const initH5Preview = () => {
+	showLoading.value = true;
+	showError.value = false;
+
+	const ext = fileExtension.value;
+	const url = fileUrl.value;
+
+	if (!url) {
+		showLoading.value = false;
+		showError.value = true;
+		return;
+	}
+
+	if (isImageFile(ext)) {
+		previewImageH5();
+	} else if (isPdfFile(ext)) {
+		resolvedPreviewUrl.value = url;
+	} else if (isOfficeFile(ext)) {
+		resolvedPreviewUrl.value = buildOfficePreviewUrl(url);
+	} else {
+		resolvedPreviewUrl.value = url;
+	}
+};
+
+const previewImageH5 = () => {
+	uni.previewImage({
+		urls: [fileUrl.value],
+		current: fileUrl.value,
+		fail: () => {
+			resolvedPreviewUrl.value = fileUrl.value;
+		}
+	});
+};
+
+const buildOfficePreviewUrl = (url) => {
+	return `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}`;
 };
 
 const handleWebViewLoad = () => {
 	showLoading.value = false;
-	showRetry.value = false;
+	showError.value = false;
+};
+
+const handleWebViewError = () => {
+	showLoading.value = false;
+	showError.value = true;
+};
+// #endif
+
+const retryPreview = () => {
+	// #ifdef APP-PLUS
+	initAppPreview();
+	// #endif
+
+	// #ifdef H5
+	initH5Preview();
+	// #endif
 };
 
 onLoad((options) => {
-	if (options && options.url) {
+	const eventChannel = typeof uni.getOpenerEventChannel === 'function'
+		? uni.getOpenerEventChannel()
+		: null;
+	if (eventChannel && typeof eventChannel.on === 'function') {
+		eventChannel.on('previewFileData', (data) => {
+			applyPreviewData(data);
+		});
+	}
+
+	const storageData = uni.getStorageSync(PREVIEW_STORAGE_KEY);
+	if (storageData) {
+		try {
+			applyPreviewData(JSON.parse(storageData));
+		} catch (error) {
+			console.error('解析预览文件信息失败:', error);
+		}
+		uni.removeStorageSync(PREVIEW_STORAGE_KEY);
+	}
+
+	if (options && options.url && !fileUrl.value) {
 		fileUrl.value = decodeURIComponent(options.url);
 	}
-	if (options && options.name) {
+	if (options && options.name && !fileName.value) {
 		fileName.value = decodeURIComponent(options.name);
 	}
+
+	if (!resolvedPreviewUrl.value && fileUrl.value) {
+		applyPreviewData({
+			url: fileUrl.value,
+			name: fileName.value
+		});
+	} else if (!fileUrl.value) {
+		showLoading.value = false;
+		showError.value = true;
+		errorMessage.value = '文件地址无效';
+	}
 });
 
 onMounted(() => {
-	if (!previewUrl.value) {
+	if (!fileUrl.value) {
 		showLoading.value = false;
+		showError.value = true;
+		errorMessage.value = '未获取到文件信息';
 	}
 });
 
+onUnmounted(() => {
+	resolvedPreviewUrl.value = '';
+});
+
 const goBack = () => {
 	uni.navigateBack();
 };
@@ -111,19 +325,50 @@ const goBack = () => {
 	margin-top: 96rpx;
 }
 
-.retry_container {
-	position: fixed;
-	top: 120rpx;
-	left: 50%;
-	transform: translateX(-50%);
-	background-color: rgba(0, 0, 0, 0.7);
-	padding: 20rpx 40rpx;
-	border-radius: 12rpx;
-	z-index: 100;
-	
-	.retry_text {
+.app_container {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	padding: 0 48rpx;
+}
+
+.download_container {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	padding: 60rpx 48rpx;
+	background-color: #F9FAFF;
+	border-radius: 20rpx;
+	width: 100%;
+
+	.file_name {
+		font-size: 32rpx;
+		font-weight: 500;
+		color: #333333;
+		margin-bottom: 16rpx;
+		text-align: center;
+		word-break: break-all;
+	}
+
+	.file_tip {
 		font-size: 26rpx;
-		color: #FFFFFF;
+		color: #999999;
+		margin-bottom: 40rpx;
+		text-align: center;
+	}
+
+	.download_btn {
+		background-color: #2E64FA;
+		border-radius: 12rpx;
+		padding: 24rpx 64rpx;
+
+		.download_btn_text {
+			font-size: 30rpx;
+			color: #FFFFFF;
+			font-weight: 500;
+		}
 	}
 }
 
@@ -132,14 +377,62 @@ const goBack = () => {
 	top: 50%;
 	left: 50%;
 	transform: translate(-50%, -50%);
-	background-color: rgba(0, 0, 0, 0.5);
+	background-color: rgba(0, 0, 0, 0.6);
 	padding: 40rpx 60rpx;
-	border-radius: 12rpx;
+	border-radius: 16rpx;
 	z-index: 1000;
-	
+
 	.loading_text {
 		font-size: 28rpx;
 		color: #FFFFFF;
 	}
 }
-</style>
+
+.error_container {
+	position: fixed;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%, -50%);
+	width: 560rpx;
+	padding: 48rpx 40rpx;
+	background: #FFFFFF;
+	border-radius: 24rpx;
+	box-shadow: 0 16rpx 56rpx rgba(0, 0, 0, 0.15);
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	z-index: 1001;
+
+	.error_title {
+		font-size: 34rpx;
+		font-weight: 600;
+		color: #333333;
+		margin-bottom: 20rpx;
+	}
+
+	.error_text {
+		font-size: 26rpx;
+		line-height: 1.8;
+		color: #8A9099;
+		text-align: center;
+		margin-bottom: 40rpx;
+	}
+
+	.error_btn {
+		min-width: 240rpx;
+		height: 80rpx;
+		padding: 0 40rpx;
+		border-radius: 40rpx;
+		background: #2E64FA;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		.error_btn_text {
+			font-size: 30rpx;
+			color: #FFFFFF;
+			font-weight: 500;
+		}
+	}
+}
+</style>

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

@@ -1,5 +1,5 @@
 <template>
-	<view class="page_notice" :style="{paddingTop: statusBarHeight + 322 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
+	<view class="page_notice" :style="{paddingTop: statusBarHeight + 352 + 'rpx', paddingBottom: safeAreaBottom + 160 + 'rpx' }">
 		<notice-header title="校内通知" :showSearch="true" :showFilter="true" :showTabs="true" :tabs="tabs"
 			:activeTab="activeTab" @tabChange="handleTabChange" @typeChange="handleTypeChange"
 			@timeChange="handleTimeChange" @searchChange="handleSearchChange"></notice-header>
@@ -17,8 +17,8 @@
 							<view class="status_tag" :class="item.statusClass">
 								<text>{{ item.status }}</text>
 							</view>
-							<text class="meta_text textLength">{{ item.category }}</text>
-							<text class="meta_text textLength">{{ item.department }}</text>
+							<text class="meta_text ">{{ truncateText(item.category, 5) }}</text>
+							<text class="meta_text ">{{ truncateText(item.department, 5) }}</text>
 							<text class="meta_text">{{ item.time }}</text>
 						</view>
 					</view>
@@ -43,7 +43,7 @@
 
 				</scroll-view>
 
-				<no-data v-if="noticeList.length === 0" text="暂无通知" />
+				<no-data v-if="noticeList.length === 0" centered text="暂无通知" />
 			</view>
 		</view>
 
@@ -158,6 +158,14 @@ const getDeleteClass = (item) => {
 	return 'delete ';
 };
 
+const truncateText = (text, maxLength) => {
+	if (!text) return '';
+	if (text.length > maxLength) {
+		return text.substring(0, maxLength) + '...';
+	}
+	return text;
+};
+
 const handleDelete = (id) => {
 	const item = noticeList.value.find(item => item.id === id);
 	if (item && item.noticeStatus !== 2) {
@@ -189,11 +197,17 @@ const confirmDelete = async () => {
 
 const handleEdit = (id) => {
 	const item = noticeList.value.find(item => item.id === id);
+	if (item) {
+		uni.setStorageSync('editNoticeData', JSON.stringify(item));
+	}
 	uni.navigateTo({ url: `/pages/notice/publish/index?id=${id}` });
 };
 
 const handleDetail = (id) => {
 	const item = noticeList.value.find(item => item.id === id);
+	if (item) {
+		uni.setStorageSync('detailNoticeData', JSON.stringify(item));
+	}
 	if (item && item.status === '待发布') {
 		uni.showToast({ title: '待发布通知暂无法查看详情', icon: 'none' });
 		return;
@@ -225,6 +239,8 @@ const handleDetail = (id) => {
 	overflow-y: auto;
 	overflow-x: hidden;
 	padding: 0 24rpx;
+	display: flex;
+	flex-direction: column;
 }
 
 .notice_item {
@@ -295,12 +311,6 @@ const handleDetail = (id) => {
 				color: #999999;
 				line-height: 48rpx;
 			}
-			.textLength {
-					max-width: 160rpx;
-					overflow: hidden;
-					text-overflow: ellipsis;
-					white-space: nowrap;
-				}
 		}
 	}
 

+ 38 - 3
pages/notice/overview/noticeDetail.vue

@@ -11,8 +11,8 @@
 				<text class="notice_title">{{ noticeItem.title }}</text>
 				<view class="notice_meta">
 					<view class="notice_left">
-						<text class="meta_item">{{ noticeItem.category }}</text>
-						<text class="meta_item">{{ noticeItem.department }}</text>
+						<text class="meta_item">{{ truncateText(noticeItem.category, 8) }}</text>
+						<text class="meta_item">{{ truncateText(noticeItem.department, 8) }}</text>
 						<text class="meta_item">{{ noticeItem.time }}</text>
 					</view>
 
@@ -118,6 +118,14 @@ const getStatusClass = (status) => {
 	return statusClassMap[status] || '';
 };
 
+const truncateText = (text, maxLength) => {
+	if (!text) return '';
+	if (text.length > maxLength) {
+		return text.substring(0, maxLength) + '...';
+	}
+	return text;
+};
+
 const filteredReaders = computed(() => {
 	let list = readerList.value;
 	if (activeTab.value === 1) {
@@ -132,6 +140,25 @@ const filteredReaders = computed(() => {
 	return list;
 });
 
+const loadNoticeData = () => {
+	try {
+		const storedData = uni.getStorageSync('detailNoticeData');
+		if (storedData) {
+			const item = JSON.parse(storedData);
+			noticeItem.value = {
+				title: item.title || '',
+				category: item.category || '',
+				department: item.department || '',
+				time: item.time || '',
+				noticeStatus: item.noticeStatus
+			};
+			noticeId.value = item.id || noticeId.value;
+		}
+	} catch (error) {
+		console.error('读取通知详情数据失败:', error);
+	}
+};
+
 const fetchNoticeInfo = async () => {
 	if (!noticeId.value) {
 		return;
@@ -203,11 +230,19 @@ onMounted(() => {
 });
 
 onLoad((options) => {
+	// 优先从全局变量读取数据
+	loadNoticeData();
+	
 	if (options && options.id) {
 		noticeId.value = options.id;
+	}
+	
+	// 如果没有从全局变量读取到数据,则调用接口获取
+	if (!noticeItem.value.title) {
 		fetchNoticeInfo();
-		fetchReaderList();
 	}
+	
+	fetchReaderList();
 });
 </script>
 

+ 10 - 31
pages/notice/publish/index.vue

@@ -405,40 +405,19 @@ const handleSave = async () => {
 		}
 		
 		const userList = selectedUsers.value.map(user => {
-			const userInfo = {
+			return {
 				userType: objectType === 5 ? 2 : 1,
-				userId: user.id,
+				userId: user.userId || user.id,
 				thirdName: user.name,
-				thirdCode: user.thirdCode || user.userAccount || '',
-				gradeName: '',
-				classTypeName: '',
-				className: '',
-				departmentName: '',
-				groupName: '',
-				subjectName: '',
-				roleName: ''
+				thirdCode: user.thirdCode || '',
+				gradeName: user.gradeName || '',
+				classTypeName: user.classTypeName || '',
+				className: user.className || '',
+				departmentName: user.departmentName || '',
+				groupName: user.groupName || '',
+				subjectName: user.subjectName || '',
+				roleName: user.roleName || ''
 			};
-
-			switch (objectType) {
-				case 1:
-					userInfo.departmentName = user.departmentName || '';
-					userInfo.groupName = user.groupName || '';
-					break;
-				case 2:
-					userInfo.subjectName = user.subjectName || '';
-					userInfo.gradeName = user.gradeName || '';
-					break;
-				case 3:
-					userInfo.gradeName = user.gradeName || '';
-					userInfo.classTypeName = user.classTypeName || '';
-					userInfo.className = user.className || '';
-					break;
-				case 4:
-					userInfo.roleName = user.roleName || '';
-					break;
-			}
-
-			return userInfo;
 		});
 		
 		if (userList.length > 0) {

+ 32 - 15
pages/notice/publish/selectObject.vue

@@ -493,33 +493,50 @@ const goBack = () => {
 const collectSelectedUsers = () => {
 	const selectedUsers = [];
 	const seen = new Set();
+	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 (!newParentInfo.departmentName && !newParentInfo.subjectName && !newParentInfo.gradeName && !newParentInfo.roleName) {
-						if (activeTab.value === 1) {
+					if (currentTab === 'department') {
+						// 按部门: 第一级是部门名称,第二级是分组名称
+						if (!newParentInfo.departmentName) {
 							newParentInfo.departmentName = node.name;
-						} else if (activeTab.value === 2) {
+						} else if (!newParentInfo.groupName) {
+							newParentInfo.groupName = node.name;
+						}
+					} else if (currentTab === 'subject') {
+						// 按学科: 第一级是科目,第二级是年级
+						if (!newParentInfo.subjectName) {
 							newParentInfo.subjectName = node.name;
-						} else if (activeTab.value === 3) {
+						} else if (!newParentInfo.gradeName) {
 							newParentInfo.gradeName = node.name;
-						} else if (activeTab.value === 4) {
-							newParentInfo.roleName = node.name;
-						} else if (activeTab.value === 5) {
+						}
+					} 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 (!newParentInfo.groupName && !newParentInfo.classTypeName && !newParentInfo.className) {
-						if (activeTab.value === 1) {
-							newParentInfo.groupName = node.name;
-						} else if (activeTab.value === 3) {
+					} 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 (activeTab.value === 5) {
+						} else if (!newParentInfo.className) {
 							newParentInfo.className = node.name;
 						}
-					} else if (!newParentInfo.className && activeTab.value === 3) {
-						newParentInfo.className = node.name;
 					}
 				}
 				collect(node.children, newParentInfo);
@@ -529,7 +546,7 @@ const collectSelectedUsers = () => {
 					id: node.id,
 					name: node.name,
 					userId: node.userId,
-					thirdCode: node.userAccount,
+					thirdCode: node.userAccount || '',
 					departmentName: parentInfo.departmentName || '',
 					groupName: parentInfo.groupName || '',
 					subjectName: parentInfo.subjectName || '',

+ 65 - 54
pages/todo/index.vue

@@ -1,5 +1,6 @@
 <template>
-	<view class="page_todo" :style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
+	<view class="page_todo"
+		:style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 150 + 'rpx' }">
 		<page-header></page-header>
 		<view class="todo_content">
 			<!-- 待办事项 -->
@@ -12,26 +13,28 @@
 					<view class="todo_left">
 						<view class="todo_info">
 							<view class="todo_title_row">
-								<image :src="item.source === 1 ? '/static/image/todo/xiaoxi.png' : (item.source === 2 ? '/static/image/todo/daiban.png' : '/static/image/todo/shipin.png')" class="todo_icon" mode="aspectFit"></image>
+								<image
+									:src="item.source === 1 ? '/static/image/todo/xiaoxi.png' : (item.source === 2 ? '/static/image/todo/daiban.png' : '/static/image/todo/shipin.png')"
+									class="todo_icon" mode="aspectFit"></image>
 								<text class="todo_name">{{ item.title }}</text>
 								<uni-icons type="right" size="18" color="#999999"></uni-icons>
 							</view>
 							<view class="todo_meta" v-if="item.source === 1">
-								<text class="meta_tag" >未读</text>
-								<text class="meta_type" >{{ item.noticeType }}</text>
-								<text class="meta_type" >{{ item.createdBy }}</text>
-								<text class="meta_type" >{{ item.releaseTime }}</text>
+								<text class="meta_tag">未读</text>
+								<text class="meta_type">{{ item.noticeType }}</text>
+								<text class="meta_type">{{ item.createdBy }}</text>
+								<text class="meta_type">{{ item.releaseTime }}</text>
 							</view>
 							<view class="todo_meta" v-if="item.source === 2">
-								<text class="meta_tag" :class="getTagStyle('进行中')" >{{ '进行中' }}</text>
-								<text class="meta_type" >{{ item.departmentName }}</text>
-								<text class="meta_type" >{{ item.materialCategoryName }}</text>
-								<text class="meta_type" >{{"剩余" + item.daysRemaining + "天"}}</text>
+								<text class="meta_tag" :class="getTagStyle('进行中')">{{ '进行中' }}</text>
+								<text class="meta_type">{{ item.departmentName }}</text>
+								<text class="meta_type">{{ item.materialCategoryName }}</text>
+								<text class="meta_type">{{ "剩余" + item.daysRemaining + "天" }}</text>
 							</view>
 							<view class="todo_meta" v-if="item.source === 3">
-								<text class="meta_tag" >{{ item.taskTyp }}</text>
-								<text class="meta_type" >{{ item.workspaceTheme }}</text>
-								<text class="meta_type" >{{ item.contentDomain }}</text>
+								<text class="meta_tag">{{ item.taskTyp }}</text>
+								<text class="meta_type">{{ item.workspaceTheme }}</text>
+								<text class="meta_type">{{ item.contentDomain }}</text>
 							</view>
 						</view>
 					</view>
@@ -44,47 +47,47 @@
 </template>
 
 <script setup>
-	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 CommonTabbar from '@/components/commonTabbar.vue';
-	import NoData from '@/components/no-data.vue';
-	import workspace from '@/reqApi/workspace.js';
-	import { useSafeArea } from '@/common/safeArea';
-
-	const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
-	const todoList = ref([]);
-
-	const getTagStyle = (status) => {
-		if (status === '已驳回') {
-			return 'meta_tag_red'
-		}
-		if (status === '进行中' || status === "已通过") {
-			return 'meta_tag_green'
-		}
-		return ''
+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 CommonTabbar from '@/components/commonTabbar.vue';
+import NoData from '@/components/no-data.vue';
+import workspace from '@/reqApi/workspace.js';
+import { useSafeArea } from '@/common/safeArea';
+
+const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
+const todoList = ref([]);
+
+const getTagStyle = (status) => {
+	if (status === '已驳回') {
+		return 'meta_tag_red'
 	}
+	if (status === '进行中' || status === "已通过") {
+		return 'meta_tag_green'
+	}
+	return ''
+}
 
-	const fetchTodoList = async () => {
-		try {
-			const res = await workspace.getTodoList();
-			todoList.value = []
-			if (res && res.data) {
-				todoList.value = res.data
-			}
-			} catch (error) {
-			console.error('获取待办事项列表失败:', error);
+const fetchTodoList = async () => {
+	try {
+		const res = await workspace.getTodoList();
+		todoList.value = []
+		if (res && res.data) {
+			todoList.value = res.data
 		}
-	};
+	} catch (error) {
+		console.error('获取待办事项列表失败:', error);
+	}
+};
 
-	onMounted(() => {
-		initSafeArea();
-	});
+onMounted(() => {
+	initSafeArea();
+});
 
-	onShow(() => {
-		fetchTodoList();
-	});
+onShow(() => {
+	fetchTodoList();
+});
 </script>
 
 <style lang="scss">
@@ -104,13 +107,16 @@
 	padding-left: 0rpx;
 	padding-right: 0rpx;
 }
+
 // 待办事项标题
 .todo_header {
 	display: flex;
 	align-items: baseline;
 	margin-bottom: 24rpx;
-	padding:0 24rpx;
-	
+	padding: 0 24rpx;
+	height: 64rpx;
+	line-height: 64rpx;
+
 	.todo_title {
 		font-weight: 500;
 		font-size: 32rpx;
@@ -127,7 +133,8 @@
 .todo_list {
 	flex: 1;
 	overflow-y: auto;
-	padding:0 24rpx;
+	padding: 0 24rpx;
+
 	.todo_item {
 		display: flex;
 		align-items: center;
@@ -147,14 +154,14 @@
 			flex: 1;
 			align-items: flex-start;
 			gap: 16rpx;
-			
+
 		}
 
 		.todo_icon {
 			flex-shrink: 0;
 			margin-top: 4rpx;
 			width: 32rpx;
-				height: 32rpx;
+			height: 32rpx;
 		}
 
 		.todo_info {
@@ -205,18 +212,22 @@
 				color: #4A6CF7;
 				border-radius: 4rpx;
 			}
+
 			.meta_tag_red {
 				background-color: rgba(245, 108, 108, 0.1);
 				color: #F56C6C;
 			}
+
 			.meta_tag_green {
 				background-color: rgba(43, 198, 68, 0.1);
 				color: #2BC644;
 			}
+
 			.meta_type {
 				font-size: 24rpx;
 				color: #999999;
 			}
+
 			.meta_time {
 				font-size: 24rpx;
 				color: #999999;

BIN
static/image/icon/file.png