一师一策调整

This commit is contained in:
hebo 2025-10-15 20:21:34 +08:00
parent 05e789ebb4
commit 5580fc4c5c
17 changed files with 4967 additions and 1659 deletions

View File

@ -117,4 +117,18 @@ export function rwzxBatchCreateApi(params: any) {
*/
export function getTeacherTasksApi(params: any) {
return get('/api/rwzx/getTeacherTasks', params);
}
/**
*
*/
export function updateEvaluationApi(params: any) {
return post('/api/rwzx/updateEvaluation', params);
}
/**
*
*/
export function getScoreStatisticsApi(params: any) {
return get('/api/rwzx/getScoreStatistics', params);
}

View File

@ -13,4 +13,9 @@ export const xxtsListApi = async (params: any) => {
// 添加更新消息推送
export const xxtsSaveApi = async (params: any) => {
return await post("/api/xxts/save", params);
};
// 更新消息状态为已阅
export const xxtsUpdateDbZtApi = async (id: string) => {
return await post("/api/xxts/updateDbZt", id);
};

View File

@ -185,6 +185,13 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/yishiyice/kcxxpj",
"style": {
"navigationBarTitleText": "作品评价",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/yishiyice/zy/index",
"style": {
@ -234,6 +241,27 @@
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/yishiyice/jf/index",
"style": {
"navigationBarTitleText": "学习积分",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/yishiyice/jf/kcxxpj",
"style": {
"navigationBarTitleText": "学习详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/yishiyice/jf/pjtj",
"style": {
"navigationBarTitleText": "积分统计",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/yishiyice/success",
"style": {
@ -989,796 +1017,4 @@
}
]
}
}
{
"path": "pages/view/hr/teacherProfile/ExperienceInfo",
"style": {
"navigationBarTitleText": "学习及工作经历",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/teacherProfile/FamilyInfo",
"style": {
"navigationBarTitleText": "家庭成员情况",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/teacherProfile/EmergencyContact",
"style": {
"navigationBarTitleText": "紧急联系人",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/PersonalHonor",
"style": {
"navigationBarTitleText": "个人荣誉",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/JiFenPingJia/PublicClassAwards",
"style": {
"navigationBarTitleText": "公开课获奖",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/teacherProfile/RecordMaterials",
"style": {
"navigationBarTitleText": "备案资料",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/hr/salarySlip/detail",
"style": {
"navigationBarTitleText": "工资条详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/homeSchool/parentAddressBook/index",
"style": {
"navigationBarTitleText": "家长通讯录",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/homeSchool/parentAddressBook/detail",
"style": {
"navigationBarTitleText": "学生详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/index",
"style": {
"navigationBarTitleText": "发布接龙",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/detail",
"style": {
"navigationBarTitleText": "接龙详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/publish",
"style": {
"navigationBarTitleText": "接龙推送",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/push-list",
"style": {
"navigationBarTitleText": "推送清单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/notice/selectStudents",
"style": {
"navigationBarTitleText": "选择学生",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/RengJiaoRengZhi/index",
"style": {
"navigationBarTitleText": "任教任职",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/GongZuoLiang/index",
"style": {
"navigationBarTitleText": "工作量",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/xkList",
"style": {
"navigationBarTitleText": "选课列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/xkkcDetail",
"style": {
"navigationBarTitleText": "选课课程详情"
}
},
{
"path": "pages/view/routine/xk/dmIndex",
"style": {
"navigationBarTitleText": "点名选课列表"
}
},
{
"path": "pages/view/routine/xk/dm",
"style": {
"navigationBarTitleText": "学生点名",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/dmList",
"style": {
"navigationBarTitleText": "点名列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/dmXsList",
"style": {
"navigationBarTitleText": "点名学生列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/tf/detail",
"style": {
"navigationBarTitleText": "选课退费详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/xk/tf/sp",
"style": {
"navigationBarTitleText": "选课退费审批",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/kefuxuncha/xcRecord",
"style": {
"navigationBarTitleText": "选课巡查记录"
}
},
{
"path": "pages/view/routine/kefuxuncha/kyRecord",
"style": {
"navigationBarTitleText": "课业巡查记录"
}
},
{
"path": "pages/base/xs/qj/sp",
"style": {
"navigationBarTitleText": "学生请假审批",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/xs/qj/detail",
"style": {
"navigationBarTitleText": "学生请假详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xs/studentArchive",
"style": {
"navigationBarTitleText": "学生档案",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/xkCourse",
"style": {
"navigationBarTitleText": "课程明单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/xkList",
"style": {
"navigationBarTitleText": "选课清单",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/dmStatistics",
"style": {
"navigationBarTitleText": "点名统计",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/analysis/xk/dmXkList",
"style": {
"navigationBarTitleText": "点名选课列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/routine/qd/index",
"style": {
"navigationBarTitleText": "签到发布"
}
},
{
"path": "pages/view/routine/qd/publish",
"style": {
"navigationBarTitleText": "新增签到"
}
},
{
"path": "pages/view/routine/qd/push-list",
"style": {
"navigationBarTitleText": "推送清单"
}
},
{
"path": "pages/view/routine/qd/detail",
"style": {
"navigationBarTitleText": "签到详情"
}
},
{
"path": "pages/view/routine/qd/selectTeachers",
"style": {
"navigationBarTitleText": "选择教师"
}
},
{
"path": "pages/view/routine/qd/qr-code",
"style": {
"navigationBarTitleText": "签到二维码",
"navigationStyle": "custom"
}
},
{
"path": "pages/view/routine/qd/confirm",
"style": {
"navigationBarTitleText": "确认签到",
"navigationStyle": "custom"
}
},
{
"path": "pages/view/routine/JiaoXueZiYuan/add-resource",
"style": {
"navigationBarTitleText": "上传资源",
"enablePullDownRefresh": false,
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f4f5f7"
}
},
{
"path": "pages/view/routine/jc/bzList",
"style": {
"navigationBarTitleText": "就餐标准列表"
}
},
{
"path": "pages/view/routine/jc/index",
"style": {
"navigationBarTitleText": "就餐点名"
}
},
{
"path": "pages/view/routine/jc/detail",
"style": {
"navigationBarTitleText": "就餐点名详情"
}
},
{
"path": "pages/view/quantitativeAssessment/assessment/assessment",
"style": {
"navigationBarTitleText": "考核评价",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/casualShot/casualShot",
"style": {
"navigationBarTitleText": "随手拍",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/distribute/distribute",
"style": {
"navigationBarTitleText": "分配",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/index",
"style": {
"navigationBarTitleText": "量化考核首页",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/details",
"style": {
"navigationBarTitleText": "量化考核详情",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/quantitativeSummary/quantitativeSummary",
"style": {
"navigationBarTitleText": "量化汇总",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/noticeAnnouncement",
"style": {
"navigationBarTitleText": "通知公告",
"enablePullDownRefresh": false
}
},
{
"path": "pages/view/quantitativeAssessment/index/playPage",
"style": {
"navigationBarTitleText": "播放页面",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#fff",
"backgroundColor": "#F8F8F8",
"orientation": "portrait",
"navigationStyle": "custom",
"app-plus": {
"background": "#efeff4",
"titleView": false
}
},
"tabBar": {
"selectedColor": "#447ade",
"color": "#999999",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"text": "消息",
"pagePath": "pages/base/message/index",
"iconPath": "static/tabBar/x1.png",
"selectedIconPath": "static/tabBar/1.png"
},
{
"text": "自助服务",
"pagePath": "pages/base/service/index",
"iconPath": "static/tabBar/x2.png",
"selectedIconPath": "static/tabBar/2.png"
},
{
"text": "我的",
"pagePath": "pages/base/mine/index",
"iconPath": "static/tabBar/x3.png",
"selectedIconPath": "static/tabBar/3.png"
}
]
}
}
}

View File

@ -20,19 +20,25 @@
</view>
</template>
<template #default="{ data }">
<view class="white-bg-color r-md p-15 mb-15 flex-row no-side-margin" @click="goToDetail(data)">
<view class="card-left">
<view class="card-title">{{ data.xxbt }}</view>
<view class="card-desc" v-html="data.xxzy"></view>
<view class="card-meta">
<text>{{ data.xxtstime }}</text>
<text>{{ getTimeAgo(data.xxtstime) }}</text>
<view class="white-bg-color r-md p-15 mb-15 flex-row no-side-margin message-card-wrapper">
<view class="card-content" @click="goToDetail(data)">
<view class="card-left">
<view class="card-title">{{ data.xxbt }}</view>
<view class="card-desc" v-html="data.xxzy"></view>
<view class="card-meta">
<text>{{ data.xxtstime }}</text>
<text>{{ getTimeAgo(data.xxtstime) }}</text>
</view>
</view>
<view class="card-right">
<view class="tag" :class="getTagClass(data.xxlx)">
{{ getTagText(data.xxlx) }}
</view>
</view>
</view>
<view class="card-right">
<view class="tag" :class="getTagClass(data.xxlx)">
{{ getTagText(data.xxlx) }}
</view>
<!-- 删除图标 -->
<view class="delete-icon" @click.stop="handleDelete(data)">
<text class="delete-text">×</text>
</view>
</view>
</template>
@ -43,7 +49,7 @@
<script lang="ts" setup>
import {ref, onMounted, onActivated} from "vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { xxtsListApi } from "@/api/base/xxtsApi";
import { xxtsListApi, xxtsUpdateDbZtApi } from "@/api/base/xxtsApi";
import { getTimeAgo } from "@/utils/dateUtils";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
@ -115,11 +121,48 @@ onActivated(() => {
const goToDetail = (data: any) => {
if (data && data.id) {
setDb(data);
setXxts(data); // xxts便
uni.navigateTo({
url: data.mobileUrl
});
// mobileUrl
if (!data.mobileUrl || data.mobileUrl.trim() === '') {
//
uni.showModal({
title: '提示',
content: '信息是否已阅?',
success: async (res) => {
if (res.confirm) {
// ""
try {
const result = await xxtsUpdateDbZtApi(data.id);
if (result && result.resultCode === 1) {
uni.showToast({
title: '已标记为已阅',
icon: 'success'
});
//
fetchListData(currentTab.value);
} else {
uni.showToast({
title: result?.message || '更新失败',
icon: 'error'
});
}
} catch (error) {
console.error('更新消息状态失败:', error);
uni.showToast({
title: '更新失败',
icon: 'error'
});
}
}
}
});
} else {
// mobileUrl
setDb(data);
setXxts(data); // xxts便
uni.navigateTo({
url: data.mobileUrl
});
}
}
};
@ -146,6 +189,49 @@ const getTagText = (xxlx: string) => {
// xxlx
return xxlx || '通知';
};
//
const handleDelete = async (data: any) => {
if (!data || !data.id) {
return;
}
//
uni.showModal({
title: '确认删除',
content: '确定要删除这条消息吗?',
confirmText: '删除',
cancelText: '取消',
confirmColor: '#ef4444',
success: async (res) => {
if (res.confirm) {
try {
//
const result = await xxtsUpdateDbZtApi(data.id);
if (result && result.resultCode === 1) {
uni.showToast({
title: '删除成功',
icon: 'success'
});
//
fetchListData(currentTab.value);
} else {
uni.showToast({
title: result?.message || '删除失败',
icon: 'error'
});
}
} catch (error) {
console.error('删除消息失败:', error);
uni.showToast({
title: '删除失败',
icon: 'error'
});
}
}
}
});
};
</script>
<style scoped lang="scss">
@ -283,6 +369,54 @@ const getTagText = (xxlx: string) => {
border-radius: 0; //
}
//
.message-card-wrapper {
position: relative;
display: flex;
align-items: center;
.card-content {
flex: 1;
display: flex;
flex-direction: row;
}
.delete-icon {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
transition: all 0.3s ease;
z-index: 10;
border: 2px solid #ffffff;
.delete-text {
color: white;
font-size: 18px;
font-weight: bold;
line-height: 1;
}
&:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
&:active {
transform: scale(0.95);
}
}
}
.card-right {
display: flex;
flex-direction: column;

View File

@ -79,7 +79,7 @@
<view class="folder-background">
<view class="folder-items-grid">
<view
v-for="(folderItem, folderIdx) in (item.folderItems || []).slice(0, 9)"
v-for="(folderItem, folderIdx) in getVisibleFolderItems(item.folderItems).slice(0, 9)"
:key="folderItem.id"
class="folder-item-icon"
>
@ -118,21 +118,22 @@
</view>
<view class="modal-body">
<view class="course-options">
<view
v-for="option in courseManagementOptions"
:key="option.id"
class="course-option"
@click="handleCourseOptionClick(option)"
>
<view class="option-icon-container">
<image
class="option-icon"
:src="`/static/base/home/${option.icon}.png`"
mode="aspectFit"
></image>
<template v-for="option in courseManagementOptions" :key="option.id">
<view
v-if="option.show && hasPermissionDirect(option.permissionKey)"
class="course-option"
@click="handleCourseOptionClick(option)"
>
<view class="option-icon-container">
<image
class="option-icon"
:src="`/static/base/home/${option.icon}.png`"
mode="aspectFit"
></image>
</view>
<text class="option-text">{{ option.text }}</text>
</view>
<text class="option-text">{{ option.text }}</text>
</view>
</template>
</view>
</view>
</view>
@ -301,8 +302,16 @@ const sections = reactive<Section[]>([
icon: "xxjf",
text: "学习积分",
show: true,
permissionKey: "rysyc-xxjf",
path: "/pages/view/rw/index",
permissionKey: "ysyc-xxjf",
path: "/pages/view/routine/yishiyice/jf/index",
},
{
id: "gnyy8",
icon: "xxjf",
text: "积分统计",
show: true,
permissionKey: "ysyc-pjtj",
path: "/pages/view/routine/yishiyice/jf/pjtj",
},
],
},
@ -681,6 +690,18 @@ const hasPermissionDirect = (permissionKey: string) => {
return uniquePermissions.includes(permissionKey);
};
//
const getVisibleFolderItems = (folderItems: GridItem[] | undefined) => {
if (!folderItems || !Array.isArray(folderItems)) {
return [];
}
// showtrue
return folderItems.filter(item => {
return item.show && hasPermissionDirect(item.permissionKey);
});
};
// uni-app
defineExpose({
onShow,

View File

@ -159,8 +159,14 @@
scroll-y
class="member-scroll-view"
@scrolltolower="loadMoreMembers"
lower-threshold="100"
lower-threshold="50"
enable-back-to-top
>
<!-- 成员统计信息 -->
<view v-if="memberList.length > 0" class="member-stats">
<text class="stats-text"> {{ memberList.length }} 位成员</text>
</view>
<view v-if="isLoadingMembers && memberList.length === 0" class="loading-indicator">
<text class="loading-icon"></text>
<text class="loading-text">加载中...</text>
@ -435,9 +441,10 @@ const loadMorePosts = () => {
}
};
//
// -
const queryMembers = async (isLoadMore = false) => {
if (isLoadingMembers.value) return;
//
if (isLoadMore || isLoadingMembers.value) return;
try {
isLoadingMembers.value = true;
@ -452,49 +459,38 @@ const queryMembers = async (isLoadMore = false) => {
return;
}
// ID
if (!isLoadMore || allTeacherIds.value.length === 0) {
// jsIds JSON
let teacherIds: string[] = [];
try {
// JSON
if (jsIds.value.startsWith('[') && jsIds.value.endsWith(']')) {
teacherIds = JSON.parse(jsIds.value);
} else {
// JSON
teacherIds = jsIds.value.split(',').map(id => id.trim()).filter(id => id);
}
} catch (error) {
console.error('解析 jsIds 失败:', error);
//
// jsIds JSON
let teacherIds: string[] = [];
try {
// JSON
if (jsIds.value.startsWith('[') && jsIds.value.endsWith(']')) {
teacherIds = JSON.parse(jsIds.value);
} else {
// JSON
teacherIds = jsIds.value.split(',').map(id => id.trim()).filter(id => id);
}
if (teacherIds.length === 0) {
console.log('解析后的 teacherIds 为空');
memberList.value = [];
hasMoreMembers.value = false;
return;
}
console.log('解析后的 teacherIds:', teacherIds);
allTeacherIds.value = teacherIds;
} catch (error) {
console.error('解析 jsIds 失败:', error);
//
teacherIds = jsIds.value.split(',').map(id => id.trim()).filter(id => id);
}
//
const pageNo = isLoadMore ? currentMemberPage.value + 1 : 1;
const startIndex = (pageNo - 1) * memberPageSize.value;
const endIndex = startIndex + memberPageSize.value;
const pageTeacherIds = allTeacherIds.value.slice(startIndex, endIndex);
if (pageTeacherIds.length === 0) {
console.log('当前页没有数据');
if (teacherIds.length === 0) {
console.log('解析后的 teacherIds 为空');
memberList.value = [];
hasMoreMembers.value = false;
return;
}
console.log('解析后的 teacherIds:', teacherIds);
console.log('总成员数:', teacherIds.length);
allTeacherIds.value = teacherIds;
//
console.log('一次性加载全部成员');
// findByIds
const teacherRes = await findByIdsApi({ ids: pageTeacherIds.join(',') });
const teacherRes = await findByIdsApi({ ids: teacherIds.join(',') });
console.log('findByIds API 返回:', teacherRes);
@ -519,18 +515,11 @@ const queryMembers = async (isLoadMore = false) => {
console.log('转换后的成员列表:', members);
if (isLoadMore) {
//
memberList.value = [...memberList.value, ...members];
currentMemberPage.value = pageNo;
} else {
//
memberList.value = members;
currentMemberPage.value = 1;
}
//
memberList.value = members;
hasMoreMembers.value = false; //
//
hasMoreMembers.value = endIndex < allTeacherIds.value.length;
console.log(`成员加载完成, 共加载: ${memberList.value.length}/${allTeacherIds.value.length} 位成员`);
} catch (error) {
console.error('查询成员失败:', error);
@ -539,11 +528,10 @@ const queryMembers = async (isLoadMore = false) => {
}
};
//
//
const loadMoreMembers = () => {
if (hasMoreMembers.value && !isLoadingMembers.value) {
queryMembers(true);
}
//
console.log('所有成员已加载完成,共', memberList.value.length, '位成员');
};
//
@ -759,14 +747,16 @@ const goPublish = () => {
flex: 1;
padding: 0 20rpx;
box-sizing: border-box;
height: 0; // scroll-view
/* 计算高度100vh - tabs高度(约88rpx) - 筛选栏高度(约104rpx) - 底部按钮区域(约130rpx) - 其他间距(约40rpx) */
height: calc(100vh - 360rpx);
}
.member-scroll-view {
flex: 1;
padding: 0 20rpx;
box-sizing: border-box;
height: 0; // scroll-view
/* 计算高度100vh - tabs高度(约88rpx) - 底部按钮区域(约130rpx) - 其他间距(约40rpx) */
height: calc(100vh - 260rpx);
}
.post-list {
@ -892,6 +882,28 @@ const goPublish = () => {
}
}
.member-stats {
background: #fff;
padding: 24rpx;
margin-bottom: 20rpx;
border-radius: 16rpx;
text-align: center;
.stats-text {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.stats-hint {
display: block;
font-size: 24rpx;
color: #999;
}
}
.member-list {
display: flex;
flex-wrap: wrap;

View File

@ -156,9 +156,11 @@
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import { communityFindPageApi } from '@/api/forum';
import { useDicStore } from '@/store/modules/dic';
import { useUserStore } from '@/store/modules/user';
import BasicSearch from '@/components/BasicSearch/Search.vue';
const { findByPid } = useDicStore();
const { getJs, getUser } = useUserStore();
//
const searchKeyword = ref('');
@ -231,6 +233,12 @@ const queryData = async (isLoadMore = false) => {
communityName: searchKeyword.value,
status: 'A'
};
// admin ID
const empCode = getUser?.empCode || '';
if (empCode !== 'admin') {
params.dqjsId = getJs.id; // ID
}
// Tab
if (activeTab.value !== 'all' && activeTab.value !== 'my') {

View File

@ -61,6 +61,10 @@
<text class="stat-number unsigned">{{ unsignedCount }}</text>
<text class="stat-label">未签到</text>
</view>
<view class="stat-item" @click="showTeacherList('qj')">
<text class="stat-number qj">{{ qjCount }}</text>
<text class="stat-label">请假</text>
</view>
</view>
</view>
@ -78,6 +82,14 @@
<view class="teacher-info">
<text class="teacher-name">{{ teacher.jsxm }}</text>
<text class="teacher-position">{{ teacher.dzzw || '' }} {{ teacher.qtzw || '' }}</text>
<!-- 请假信息 -->
<view v-if="teacher.qjlx" class="qj-info">
<text class="qj-type">{{ teacher.qjlx }}</text>
<text class="qj-reason">{{ teacher.qjsy || '无' }}</text>
<text class="qj-status" :class="getSpResultClass(teacher.spResult)">
{{ getSpResultText(teacher.spResult) }}
</text>
</view>
</view>
<view class="teacher-status">
<text class="status-text" :class="getStatusClass(teacher.qdStatus)">
@ -119,6 +131,14 @@
<view class="popup-teacher-info">
<text class="popup-teacher-name">{{ teacher.jsxm }}</text>
<text class="popup-teacher-position">{{ teacher.dzzw || '' }} {{ teacher.qtzw || '' }}</text>
<!-- 请假信息 -->
<view v-if="teacher.qjlx" class="popup-qj-info">
<text class="popup-qj-type">{{ teacher.qjlx }}</text>
<text class="popup-qj-reason">{{ teacher.qjsy || '无' }}</text>
<text class="popup-qj-status" :class="getSpResultClass(teacher.spResult)">
{{ getSpResultText(teacher.spResult) }}
</text>
</view>
</view>
<view class="popup-teacher-status">
<text class="popup-status-text" :class="getStatusClass(teacher.qdStatus)">
@ -163,6 +183,9 @@ interface TeacherInfo {
qtzw: string;
qdStatus: string;
qdwctime: string;
qjlx?: string; //
qjsy?: string; //
spResult?: string; //
}
const qdId = ref<string>('');
@ -185,6 +208,12 @@ const lateCount = computed(() => {
}).length;
});
//
const qjCount = computed(() => teacherList.value.filter(t => t.qjlx && t.qjlx.trim() !== '').length);
const qjPendingCount = computed(() => teacherList.value.filter(t => t.spResult === 'A').length);
const qjApprovedCount = computed(() => teacherList.value.filter(t => t.spResult === 'B').length);
const qjRejectedCount = computed(() => teacherList.value.filter(t => t.spResult === 'C').length);
const filteredTeacherList = computed(() => {
switch (currentFilter.value) {
case 'signed':
@ -199,6 +228,14 @@ const filteredTeacherList = computed(() => {
});
case 'unsigned':
return teacherList.value.filter(t => t.qdStatus === '0');
case 'qj':
return teacherList.value.filter(t => t.qjlx && t.qjlx.trim() !== '');
case 'qj_pending':
return teacherList.value.filter(t => t.spResult === 'A');
case 'qj_approved':
return teacherList.value.filter(t => t.spResult === 'B');
case 'qj_rejected':
return teacherList.value.filter(t => t.spResult === 'C');
default:
return teacherList.value;
}
@ -257,6 +294,18 @@ const showTeacherList = (filter: string) => {
case 'unsigned':
popupTitle.value = `未签到人员 (${unsignedCount.value}人)`;
break;
case 'qj':
popupTitle.value = `请假人员 (${qjCount.value}人)`;
break;
case 'qj_pending':
popupTitle.value = `待审批请假 (${qjPendingCount.value}人)`;
break;
case 'qj_approved':
popupTitle.value = `已同意请假 (${qjApprovedCount.value}人)`;
break;
case 'qj_rejected':
popupTitle.value = `已驳回请假 (${qjRejectedCount.value}人)`;
break;
default:
popupTitle.value = `全部人员 (${totalCount.value}人)`;
}
@ -299,6 +348,34 @@ const getStatusText = (status: string) => {
}
};
//
const getSpResultText = (spResult: string) => {
switch (spResult) {
case 'A':
return '待审批';
case 'B':
return '同意';
case 'C':
return '驳回';
default:
return '无';
}
};
//
const getSpResultClass = (spResult: string) => {
switch (spResult) {
case 'A':
return 'sp-pending';
case 'B':
return 'sp-approved';
case 'C':
return 'sp-rejected';
default:
return 'sp-none';
}
};
const formatTime = (time: string) => {
if (!time) return '未设置';
return new Date(time).toLocaleString('zh-CN', {
@ -454,6 +531,22 @@ const handleBack = () => {
&.late {
color: #ff9800;
}
&.qj {
color: #9c27b0;
}
&.qj-pending {
color: #ff9800;
}
&.qj-approved {
color: #4caf50;
}
&.qj-rejected {
color: #f44336;
}
}
.stat-label {
@ -510,6 +603,56 @@ const handleBack = () => {
color: #666;
}
.qj-info {
margin-top: 8px;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 6px;
border-left: 3px solid #9c27b0;
}
.qj-type {
display: block;
font-size: 13px;
font-weight: 500;
color: #9c27b0;
margin-bottom: 4px;
}
.qj-reason {
display: block;
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.qj-status {
font-size: 11px;
padding: 2px 6px;
border-radius: 4px;
font-weight: 500;
&.sp-pending {
background: #fff3cd;
color: #856404;
}
&.sp-approved {
background: #d4edda;
color: #155724;
}
&.sp-rejected {
background: #f8d7da;
color: #721c24;
}
&.sp-none {
background: #e9ecef;
color: #6c757d;
}
}
.teacher-status {
display: flex;
flex-direction: column;
@ -615,6 +758,56 @@ const handleBack = () => {
color: #666;
}
.popup-qj-info {
margin-top: 6px;
padding: 6px 10px;
background: #f8f9fa;
border-radius: 4px;
border-left: 2px solid #9c27b0;
}
.popup-qj-type {
display: block;
font-size: 12px;
font-weight: 500;
color: #9c27b0;
margin-bottom: 3px;
}
.popup-qj-reason {
display: block;
font-size: 11px;
color: #666;
margin-bottom: 3px;
}
.popup-qj-status {
font-size: 10px;
padding: 1px 4px;
border-radius: 3px;
font-weight: 500;
&.sp-pending {
background: #fff3cd;
color: #856404;
}
&.sp-approved {
background: #d4edda;
color: #155724;
}
&.sp-rejected {
background: #f8d7da;
color: #721c24;
}
&.sp-none {
background: #e9ecef;
color: #6c757d;
}
}
.popup-teacher-status {
display: flex;
flex-direction: column;

View File

@ -117,7 +117,7 @@
src="/static/base/details.png"
class="task-action-icon"
/>
<view v-if="showTooltip" class="tooltip">执行情况</view>
<view v-if="showTooltip" class="tooltip">执行评价</view>
</view>
</view>
</view>
@ -603,9 +603,9 @@ const viewExecution = (task: TaskItem) => {
//
uni.setStorageSync('taskExecutionData', task);
//
// ID
uni.navigateTo({
url: `/pages/view/routine/yishiyice/kcrwzx?courseId=${courseId.value}`
url: `/pages/view/routine/yishiyice/kcrwzx?courseId=${courseId.value}&taskId=${task.id}`
});
};

View File

@ -0,0 +1,783 @@
<template>
<view class="teacher-tasks-page">
<!-- 页面标题横幅 -->
<view class="page-banner">
<view class="banner-content">
<view class="banner-title-wrapper">
<text class="banner-icon">📋</text>
<view class="banner-text">
<text class="banner-title">学习积分</text>
<text class="banner-subtitle">待评价 · 已评价</text>
</view>
</view>
<view class="task-count">
<text class="count-number">{{ totalCount }}</text>
<text class="count-label">个任务</text>
</view>
</view>
</view>
<!-- 任务统计 -->
<view class="task-summary">
<view
class="summary-item pending"
:class="{ active: taskFilter === 'pending' }"
@click="setTaskFilter('pending')"
>
<text class="summary-count">{{ pendingCount }}</text>
<text class="summary-label">待评价</text>
</view>
<view
class="summary-item completed"
:class="{ active: taskFilter === 'completed' }"
@click="setTaskFilter('completed')"
>
<text class="summary-count">{{ completedCount }}</text>
<text class="summary-label">已评价</text>
</view>
<view
class="summary-item all"
:class="{ active: taskFilter === 'all' }"
@click="setTaskFilter('all')"
>
<text class="summary-count">{{ totalCount }}</text>
<text class="summary-label">全部</text>
</view>
</view>
<!-- 任务列表 -->
<view class="task-list">
<scroll-view
scroll-y
class="list-scroll-view"
@scrolltolower="loadMore"
lower-threshold="100"
>
<!-- 加载状态 -->
<view v-if="isLoading && taskList.length === 0" class="loading-indicator">
<text class="loading-icon"></text>
<text class="loading-text">加载中...</text>
</view>
<!-- 任务列表 -->
<template v-else-if="taskList.length > 0">
<view
v-for="task in taskList"
:key="task.id"
class="task-card"
@click="handleTaskClick(task)"
>
<view class="task-header">
<view class="task-title-row">
<view
class="status-dot"
:class="getStatusDotClass(task)"
></view>
<text class="task-title">{{ task.rwmc }}</text>
<text class="arrow-icon"></text>
</view>
</view>
<view class="task-body">
<view v-if="task.rwms" class="task-description">
<text class="description-label">任务描述</text>
<text class="description-content">{{ task.rwms }}</text>
</view>
<view class="task-info">
<view class="info-item" v-if="task.rwfzrxm">
<text class="info-icon">👤</text>
<text class="info-label">负责人</text>
<text class="info-value">{{ task.rwfzrxm }}</text>
</view>
<view class="info-item" v-if="task.rwjstime">
<text class="info-icon"></text>
<text class="info-label">截止时间</text>
<text class="info-value">{{ formatDate(task.rwjstime) }}</text>
</view>
<view class="info-item" v-if="task.rwzxtime">
<text class="info-icon"></text>
<text class="info-label">完成时间</text>
<text class="info-value">{{ formatDate(task.rwzxtime) }}</text>
</view>
<view class="info-item">
<text class="info-icon">📊</text>
<text class="info-label">状态</text>
<text
class="info-value status-text"
:class="getStatusClass(task)"
>{{ getStatusText(task) }}</text>
</view>
<view class="info-item" v-if="task.ispj === 'A' && task.rwpjjf">
<text class="info-icon"></text>
<text class="info-label">积分</text>
<text class="info-value score-text">{{ task.rwpjjf }}</text>
</view>
</view>
</view>
</view>
</template>
<!-- 空状态 -->
<view v-else class="empty-state">
<text class="empty-icon">📦</text>
<text class="empty-text">{{ getEmptyStateText() }}</text>
<text class="empty-hint">{{ getEmptyStateHint() }}</text>
</view>
<!-- 加载更多 -->
<view v-if="isLoading && taskList.length > 0" class="loading-more">
<text class="loading-more-icon"></text>
<text class="loading-more-text">加载中...</text>
</view>
<!-- 没有更多数据 -->
<view v-if="!hasMore && taskList.length > 0" class="no-more">
<text class="no-more-icon"></text>
<text class="no-more-text">已加载全部任务</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { getTeacherTasksApi } from "@/api/base/rwzxApi";
import { useUserStore } from "@/store/modules/user";
const { getJs } = useUserStore();
//
interface TaskItem {
id: string;
rwId: string; // ID
rwmc: string; //
rwms: string; //
rwfzr: string; // ID
rwfzrxm: string; //
rwjstime: string; //
rwzxfzr: string; // ID
rwzxfzrxm: string; //
rwzxtime: string; //
rwzxnr: string; //
iszxwc: string; // A-B-
rwzxzt: string; // A-B-
rwlyId: string; // ID
ispj: string; // A-B-
rwpjjf?: string; //
rwpjrxm?: string; //
rwpjms?: string; //
rwpjtime?: string; //
createTime: string;
}
//
const taskFilter = ref<'all' | 'pending' | 'completed'>('pending'); //
const allTaskList = ref<TaskItem[]>([]); //
const taskList = ref<TaskItem[]>([]); //
const isLoading = ref(false);
const hasMore = ref(true);
const currentPage = ref(1);
const pageSize = ref(10);
//
const pendingCount = ref(0);
const completedCount = ref(0);
const totalCount = ref(0);
//
const loadTaskList = async (isLoadMore = false) => {
if (isLoading.value) return;
isLoading.value = true;
try {
const jsId = getJs.id;
if (!jsId) {
uni.showToast({
title: '未获取到教师信息',
icon: 'error'
});
return;
}
const params: any = {
jsId,
page: isLoadMore ? currentPage.value + 1 : 1,
rows: pageSize.value
};
// ispj
console.log('加载任务列表,参数:', params);
const response = await getTeacherTasksApi(params);
console.log('任务列表API响应:', response);
const newData = response?.rows || [];
if (isLoadMore) {
allTaskList.value.push(...newData);
currentPage.value++;
} else {
allTaskList.value = newData;
currentPage.value = 1;
}
hasMore.value = newData.length === pageSize.value;
//
updateStatistics();
//
filterTaskList();
console.log('任务列表加载完成:', {
total: allTaskList.value.length,
pending: pendingCount.value,
completed: completedCount.value,
hasMore: hasMore.value
});
} catch (error) {
console.error('加载任务列表失败:', error);
uni.showToast({
title: '加载失败',
icon: 'error'
});
allTaskList.value = [];
taskList.value = [];
} finally {
isLoading.value = false;
}
};
//
const updateStatistics = () => {
pendingCount.value = allTaskList.value.filter(task => task.ispj === 'B').length;
completedCount.value = allTaskList.value.filter(task => task.ispj === 'A').length;
totalCount.value = allTaskList.value.length;
};
//
const filterTaskList = () => {
if (taskFilter.value === 'pending') {
taskList.value = allTaskList.value.filter(task => task.ispj === 'B');
} else if (taskFilter.value === 'completed') {
taskList.value = allTaskList.value.filter(task => task.ispj === 'A');
} else {
taskList.value = allTaskList.value;
}
};
//
const setTaskFilter = (filter: 'all' | 'pending' | 'completed') => {
if (taskFilter.value === filter) return;
taskFilter.value = filter;
filterTaskList(); // 使
};
//
const getStatusDotClass = (task: TaskItem) => {
return task.iszxwc === 'A' ? 'completed' : 'pending';
};
//
const getStatusClass = (task: TaskItem) => {
// 使 ispj
return task.ispj === 'A' ? 'status-completed' : 'status-pending';
};
//
const getStatusText = (task: TaskItem) => {
// 使 ispj
return task.ispj === 'A' ? '已评价' : '未评价';
};
//
const getEmptyStateText = () => {
switch (taskFilter.value) {
case 'pending':
return '暂无待评价任务';
case 'completed':
return '暂无已评价任务';
default:
return '暂无任务';
}
};
//
const getEmptyStateHint = () => {
switch (taskFilter.value) {
case 'pending':
return '您当前没有需要评价的任务';
case 'completed':
return '您还没有评价过任务';
default:
return '当前没有任何任务';
}
};
//
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}/${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
};
//
const handleTaskClick = (task: TaskItem) => {
console.log('点击任务:', task);
//
const evaluationData = {
taskInfo: {
id: task.rwId,
rwmc: task.rwmc,
rwms: task.rwms,
rwjstime: task.rwjstime,
rwfzrxm: task.rwfzrxm,
rwfzr: task.rwfzr,
rwStatus: task.rwzxzt,
rwlyId: task.rwlyId
},
executionInfo: {
id: task.id,
rwId: task.rwId,
rwzxfzr: task.rwzxfzr,
rwzxfzrxm: task.rwzxfzrxm,
iszxwc: task.iszxwc,
rwzxzt: task.rwzxzt,
rwzxtime: task.rwzxtime,
rwzxnr: task.rwzxnr,
createTime: task.createTime,
ispj: task.ispj,
jsId: task.rwzxfzr,
rwpjjf: task.rwpjjf,
rwpjrxm: task.rwpjrxm,
rwpjms: task.rwpjms,
rwpjtime: task.rwpjtime
},
isReadOnly: true //
};
uni.setStorageSync('evaluationData', evaluationData);
uni.navigateTo({
url: `/pages/view/routine/yishiyice/jf/kcxxpj`,
success: () => {
console.log('跳转到评价页面成功');
},
fail: (error) => {
console.error('跳转到评价页面失败:', error);
uni.showToast({
title: '页面跳转失败',
icon: 'error'
});
}
});
};
//
const loadMore = () => {
if (!isLoading.value && hasMore.value) {
loadTaskList(true);
}
};
//
onMounted(() => {
console.log('页面加载,当前教师:', getJs);
loadTaskList(false); //
});
//
onShow(() => {
console.log('页面显示,刷新数据');
loadTaskList(false); //
});
</script>
<style lang="scss" scoped>
.teacher-tasks-page {
display: flex;
flex-direction: column;
height: 100vh;
background: linear-gradient(180deg, #f0f5ff 0%, #f5f7fa 100%);
}
//
.page-banner {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px 16px;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
.banner-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.banner-title-wrapper {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.banner-icon {
font-size: 32px;
line-height: 1;
}
.banner-text {
display: flex;
flex-direction: column;
gap: 4px;
}
.banner-title {
font-size: 24px;
font-weight: bold;
color: #ffffff;
line-height: 1.2;
}
.banner-subtitle {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 1px;
}
.task-count {
display: flex;
flex-direction: column;
align-items: center;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
padding: 10px 16px;
border-radius: 12px;
min-width: 60px;
}
.count-number {
font-size: 24px;
font-weight: bold;
color: #ffffff;
line-height: 1;
}
.count-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.9);
margin-top: 4px;
}
}
//
.task-summary {
display: flex;
justify-content: space-around;
margin: 0;
padding: 12px 8px;
background-color: #ffffff;
border-radius: 0;
border: none;
border-bottom: 1px solid #e5e5e5;
}
.summary-item {
text-align: center;
padding: 8px 6px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
min-width: 80px;
}
.summary-item:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.summary-item.active {
background-color: rgba(24, 144, 255, 0.1);
border-color: #1890ff;
}
.summary-item.pending.active {
background-color: rgba(250, 140, 22, 0.1);
border-color: #fa8c16;
}
.summary-item.completed.active {
background-color: rgba(82, 196, 26, 0.1);
border-color: #52c41a;
}
.summary-count {
display: block;
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.summary-item.pending .summary-count {
color: #fa8c16;
}
.summary-item.completed .summary-count {
color: #52c41a;
}
.summary-label {
font-size: 12px;
color: #666;
}
//
.task-list {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
.list-scroll-view {
flex: 1;
padding: 0;
box-sizing: border-box;
height: 0;
}
}
//
.task-card {
background-color: white;
border-radius: 0;
margin-bottom: 0;
box-shadow: none;
overflow: hidden;
transition: all 0.3s ease;
border-bottom: 1px solid #e5e5e5;
}
.task-card:active {
background-color: #f8f9fa;
}
.task-header {
padding: 14px 16px;
background: #ffffff;
border-bottom: none;
}
.task-title-row {
display: flex;
align-items: center;
gap: 0;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #999;
flex-shrink: 0;
margin-right: 10px;
}
.status-dot.completed {
background: #52c41a;
}
.status-dot.pending {
background: #fa8c16;
}
.task-title {
font-size: 15px;
font-weight: 600;
color: #1f2937;
flex: 1;
line-height: 1.3;
}
.arrow-icon {
font-size: 20px;
color: #999;
font-weight: bold;
flex-shrink: 0;
}
.task-body {
padding: 0 16px 14px 16px;
}
.task-description {
font-size: 12px;
line-height: 1.6;
color: #666;
margin-bottom: 8px;
display: flex;
.description-label {
flex-shrink: 0;
color: #999;
}
.description-content {
margin-left: 4px;
flex: 1;
color: #333;
}
}
.task-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.info-item {
font-size: 12px;
line-height: 1.8;
color: #666;
display: flex;
.info-icon {
font-size: 14px;
margin-right: 4px;
}
.info-label {
flex-shrink: 0;
color: #999;
}
.info-value {
margin-left: 4px;
flex: 1;
color: #333;
}
.status-text {
font-weight: 600;
}
.status-text.status-completed {
color: #52c41a;
}
.status-text.status-pending {
color: #fa8c16;
}
.score-text {
font-weight: 700;
color: #ff9800;
font-size: 13px;
}
}
//
.loading-indicator,
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: #666;
background-color: white;
margin: 0;
border-radius: 0;
}
.loading-icon {
font-size: 32px;
margin-bottom: 12px;
animation: pulse 1.5s ease-in-out infinite;
}
.loading-text {
font-size: 13px;
color: #667eea;
}
.empty-icon {
font-size: 64px;
margin-bottom: 16px;
opacity: 0.6;
}
.empty-text {
font-size: 16px;
color: #4a5568;
font-weight: 600;
margin-bottom: 8px;
}
.empty-hint {
font-size: 13px;
color: #a0aec0;
}
.loading-more {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 20px;
background-color: white;
.loading-more-icon {
font-size: 18px;
animation: pulse 1.5s ease-in-out infinite;
}
.loading-more-text {
color: #667eea;
font-size: 14px;
font-weight: 500;
}
}
.no-more {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 24px 20px;
margin: 8px 16px 16px 16px;
background: linear-gradient(135deg, #f7f9fc 0%, #e8f0fe 100%);
border-radius: 12px;
.no-more-icon {
font-size: 16px;
color: #52c41a;
}
.no-more-text {
color: #718096;
font-size: 13px;
font-weight: 500;
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.1);
}
}
</style>

View File

@ -0,0 +1,973 @@
<template>
<view class="evaluation-page">
<!-- 提交遮罩层 -->
<view v-if="isSubmitting" class="submit-overlay">
<view class="submit-loading">
<view class="loading-spinner"></view>
<text class="loading-text">提交评价中...</text>
</view>
</view>
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="evaluation-content">
<!-- 左侧作品预览和详情 -->
<view class="work-preview-section">
<!-- 完成成果区域 -->
<view class="work-preview">
<view class="preview-title-header">
<text class="preview-title-icon">📋</text>
<text class="preview-title">完成成果</text>
</view>
<view class="preview-content">
<!-- 任务基本信息 -->
<view class="task-info">
<view class="info-item">
<text class="info-label">任务名称</text>
<text class="info-value">{{ taskInfo.rwmc }}</text>
</view>
<view class="info-item inline">
<text class="info-label">提交人</text>
<text class="info-value">{{ executionInfo.rwzxfzrxm }}</text>
</view>
<view class="info-item inline">
<text class="info-label">提交时间</text>
<text class="info-value">{{ formatDateTime(executionInfo.rwzxtime) }}</text>
</view>
</view>
<!-- 提交内容 -->
<view class="submitted-content">
<text class="content-title">提交内容</text>
<view v-if="submittedContent.length > 0" class="content-display">
<view
v-for="(item, index) in submittedContent"
:key="index"
class="content-item"
>
<view class="content-label">{{ item.label }}</view>
<view class="content-value">
<!-- 文本内容 -->
<text v-if="item.type === 'text'" class="text-content">{{ item.value }}</text>
<!-- 图片内容 -->
<view v-else-if="item.type === 'image'" class="media-content">
<ImageVideoUpload
:enableImage="true"
:enableVideo="false"
:enableFile="false"
:imageList="item.value"
:disabled="true"
:readonly="true"
/>
</view>
<!-- 视频内容 -->
<view v-else-if="item.type === 'video'" class="media-content">
<ImageVideoUpload
:enableImage="false"
:enableVideo="true"
:enableFile="false"
:videoList="item.value"
:disabled="true"
:readonly="true"
/>
</view>
<!-- 文档内容 -->
<view v-else-if="item.type === 'file'" class="media-content">
<ImageVideoUpload
:enableImage="false"
:enableVideo="false"
:enableFile="true"
:fileList="item.value"
:disabled="true"
:readonly="true"
/>
</view>
</view>
</view>
</view>
<view v-else class="empty-content">
<text class="empty-text">暂无提交内容</text>
</view>
</view>
</view>
</view>
</view>
<!-- 右侧评价功能 -->
<view class="evaluation-section">
<!-- 成果评价 -->
<view class="rating-section">
<view class="section-header">
<text class="section-icon"></text>
<text class="section-title">成果评价</text>
</view>
<view class="rating-scale">
<view
v-for="score in ratingOptions"
:key="score.value"
class="rating-option"
:class="{ active: evaluationData.score === score.value, disabled: isReadOnly }"
@click="setRating(score.value)"
>
<view class="rating-circle">{{ score.value }}</view>
<text class="rating-label">{{ score.label }}</text>
</view>
</view>
</view>
<!-- 文字评价 -->
<view class="comment-section">
<text class="comment-label">文字评价</text>
<textarea
v-model="evaluationData.comment"
class="comment-input"
placeholder="请在此输入您的评价意见..."
:disabled="isSubmitting || isReadOnly"
></textarea>
</view>
<!-- AI智能评价 -->
<view class="ai-evaluation-section" v-if="!isReadOnly">
<view class="section-header">
<text class="section-icon">🤖</text>
<text class="section-title">AI智能评价</text>
<button
class="ai-button"
@click="getAIEvaluation"
:disabled="isSubmitting || aiLoading"
>
<text class="ai-text">{{ aiLoading ? 'AI分析中...' : '获取' }}</text>
</button>
</view>
<!-- AI评价结果 -->
<view v-if="aiEvaluation" class="ai-result">
<text class="ai-result-title">AI评价结果</text>
<text class="ai-result-content">{{ aiEvaluation }}</text>
</view>
</view>
</view>
</view>
<!-- 底部固定提交按钮 -->
<view class="bottom-submit" v-if="!isReadOnly">
<button
class="submit-button"
@click="submitEvaluation"
:disabled="isSubmitting || !evaluationData.score"
>
<text class="button-icon">📤</text>
<text class="button-text">{{ isSubmitting ? '提交评价中...' : '提交评价' }}</text>
</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from "vue";
import { onLoad as onPageLoad } from "@dcloudio/uni-app";
import { rwFindInfoByRwId } from "@/api/base/server";
import { rwzxExecutedInfoByRwIdAndJsApi } from "@/api/base/server";
import { updateEvaluationApi } from "@/api/base/rwzxApi";
import { useUserStore } from "@/store/modules/user";
import { ImageVideoUpload, type FileItem, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
//
interface TaskInfo {
id: string;
rwmc: string;
rwms: string;
rwjstime: string;
rwfzrxm: string;
rwfzr: string;
rwStatus: string;
rwlyId: string;
}
interface ExecutionInfo {
id: string;
rwId: string;
rwzxfzr: string;
rwzxfzrxm: string;
iszxwc: string;
rwzxzt: string;
rwzxtime: string;
rwzxnr: string;
createTime: string;
jsId?: string;
ispj?: string;
rwpjr?: string;
rwpjrxm?: string;
rwpjjf?: string;
rwpjms?: string;
rwpjtime?: string;
}
interface SubmittedContent {
label: string;
type: 'text' | 'image' | 'video' | 'file';
value: any;
}
//
const isLoading = ref(false);
const isSubmitting = ref(false);
const aiLoading = ref(false);
const isReadOnly = ref(false); //
const taskInfo = ref<TaskInfo>({
id: '',
rwmc: '',
rwms: '',
rwjstime: '',
rwfzrxm: '',
rwfzr: '',
rwStatus: '',
rwlyId: ''
});
const executionInfo = ref<ExecutionInfo>({
id: '',
rwId: '',
rwzxfzr: '',
rwzxfzrxm: '',
iszxwc: '',
rwzxzt: '',
rwzxtime: '',
rwzxnr: '',
createTime: ''
});
const submittedContent = ref<SubmittedContent[]>([]);
//
const evaluationData = reactive({
score: '',
comment: ''
});
const aiEvaluation = ref('');
//
const ratingOptions = [
{ value: '1', label: '很差' },
{ value: '2', label: '较差' },
{ value: '3', label: '一般' },
{ value: '4', label: '较好' },
{ value: '5', label: '优秀' }
];
const userStore = useUserStore();
const { getJs } = userStore;
//
onPageLoad(async (options: any) => {
console.log('评价页面接收到的参数:', options);
//
const storedParams = uni.getStorageSync('evaluationData');
if (storedParams) {
try {
taskInfo.value = storedParams.taskInfo;
executionInfo.value = storedParams.executionInfo;
isReadOnly.value = storedParams.isReadOnly || false; //
console.log('从全局存储解析参数成功:', {
taskInfo: taskInfo.value,
executionInfo: executionInfo.value,
isReadOnly: isReadOnly.value
});
//
uni.removeStorageSync('evaluationData');
await loadSubmittedContent();
//
if (isReadOnly.value) {
loadExistingEvaluation();
}
} catch (error) {
console.error('解析全局存储参数失败:', error);
uni.showToast({
title: '参数解析失败',
icon: 'error'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
} else {
console.error('缺少评价参数');
uni.showToast({
title: '缺少评价参数',
icon: 'error'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
});
//
const loadSubmittedContent = async () => {
isLoading.value = true;
try {
//
const taskResult = await rwFindInfoByRwId({ rwId: taskInfo.value.id });
const rwflx = taskResult.result.rwlxes;
//
const executionResult = await rwzxExecutedInfoByRwIdAndJsApi({
rwId: taskInfo.value.id,
jsId: executionInfo.value.rwzxfzr
});
if (executionResult && executionResult.result && executionResult.result.length) {
const rwzxqds = executionResult.result;
const content: SubmittedContent[] = [];
for (let i = 0; i < rwzxqds.length; i++) {
const record = rwzxqds[i];
const rwlxId = record.rwlxId;
const rwzxqdtx = record.rwzxqdtx;
//
const taskType = rwflx.find((item: any) => item.id === rwlxId);
if (taskType && rwzxqdtx) {
if (taskType.rwfl === "sctp") {
//
const urls = rwzxqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const images = urls.map((url: string, index: number) => ({
url: url.trim(),
name: names[index] || url.split('/').pop() || 'image.jpg'
}));
content.push({
label: taskType.rwbt,
type: 'image',
value: images
});
} else if (taskType.rwfl === "scsp") {
//
const urls = rwzxqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const videos = urls.map((url: string, index: number) => ({
url: url.trim(),
name: names[index] || url.split('/').pop() || 'video.mp4'
}));
content.push({
label: taskType.rwbt,
type: 'video',
value: videos
});
} else if (taskType.rwfl === "scwd") {
//
const urls = rwzxqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const files = urls.map((url: string, index: number) => ({
url: url.trim(),
name: names[index] || url.split('/').pop() || 'document'
}));
content.push({
label: taskType.rwbt,
type: 'file',
value: files
});
} else {
//
content.push({
label: taskType.rwbt,
type: 'text',
value: rwzxqdtx
});
}
}
}
submittedContent.value = content;
console.log('加载提交内容成功:', submittedContent.value);
}
} catch (error) {
console.error('加载提交内容失败:', error);
uni.showToast({
title: '加载内容失败',
icon: 'error'
});
} finally {
isLoading.value = false;
}
};
//
const loadExistingEvaluation = () => {
if (executionInfo.value.rwpjjf) {
evaluationData.score = executionInfo.value.rwpjjf;
}
if (executionInfo.value.rwpjms) {
evaluationData.comment = executionInfo.value.rwpjms;
}
console.log('加载已有评价信息:', {
score: evaluationData.score,
comment: evaluationData.comment
});
};
//
const setRating = (score: string) => {
if (isReadOnly.value) {
return; //
}
evaluationData.score = score;
};
// AI
const getAIEvaluation = async () => {
aiLoading.value = true;
try {
// TODO: AI
// AI
await new Promise(resolve => setTimeout(resolve, 2000));
aiEvaluation.value = `正在接入AI评价敬请期待...`;
} catch (error) {
console.error('获取AI评价失败:', error);
uni.showToast({
title: 'AI评价失败',
icon: 'error'
});
} finally {
aiLoading.value = false;
}
};
//
const submitEvaluation = async () => {
if (isSubmitting.value) {
return;
}
if (!evaluationData.score) {
uni.showToast({
title: '请选择评分',
icon: 'none'
});
return;
}
isSubmitting.value = true;
try {
//
console.log('教师信息:', getJs);
const evaluationParams = {
rwzxId: executionInfo.value.id,
rwpjr: getJs.id,
rwpjrxm: getJs.jsxm,
rwpjjf: evaluationData.score,
rwpjms: evaluationData.comment || ''
};
console.log('提交评价参数:', evaluationParams);
const result = await updateEvaluationApi(evaluationParams);
if (result && result.resultCode === 1) {
uni.showToast({
title: '评价提交成功',
icon: 'success'
});
//
uni.$emit('refreshTaskExecution');
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
throw new Error(result?.message || result?.msg || '提交失败');
}
} catch (error) {
console.error('提交评价失败:', error);
uni.showToast({
title: '提交失败,请重试',
icon: 'error'
});
} finally {
isSubmitting.value = false;
}
};
//
const formatDateTime = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
};
</script>
<style lang="scss" scoped>
.evaluation-page {
min-height: 100vh;
background-color: #f5f7fa;
padding: 16px;
box-sizing: border-box;
&:has(.bottom-submit) {
padding-bottom: 80px; /* 有提交按钮时留出空间 */
}
}
.loading-indicator {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.evaluation-content {
display: flex;
gap: 16px;
max-width: 1200px;
margin: 0 auto;
}
/* 左侧:作品预览和详情 */
.work-preview-section {
flex: 1;
background-color: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.work-preview {
margin-bottom: 24px;
.preview-title-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.preview-title-icon {
font-size: 18px;
}
.preview-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.preview-content {
min-height: 200px;
background-color: #fff;
border: none;
border-radius: 8px;
padding: 0;
}
.task-info {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.info-item {
display: flex;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
&.inline {
display: inline-flex;
margin-right: 24px;
margin-bottom: 8px;
}
}
.info-label {
font-size: 14px;
color: #666;
min-width: 80px;
flex-shrink: 0;
}
.info-value {
font-size: 14px;
color: #333;
flex: 1;
}
.submitted-content {
.content-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 12px;
display: block;
}
}
.content-display {
width: 100%;
}
.content-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.content-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
display: block;
}
.content-value {
color: #333;
}
.text-content {
font-size: 14px;
line-height: 1.6;
word-break: break-word;
}
.media-content {
width: 100%;
}
.empty-content {
text-align: center;
color: #999;
}
.empty-text {
font-size: 14px;
}
}
/* 右侧:评价功能 */
.evaluation-section {
flex: 1;
background-color: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.section-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
.section-icon {
font-size: 18px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
flex: 1;
}
.ai-button {
padding: 6px 12px;
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(:disabled) {
background-color: #1976d2;
}
&:disabled {
background-color: #d9d9d9;
cursor: not-allowed;
opacity: 0.6;
}
}
.ai-text {
font-size: 12px;
}
}
.rating-section {
margin-bottom: 24px;
.rating-scale {
display: flex;
justify-content: space-between;
gap: 8px;
flex-wrap: nowrap;
}
.rating-option {
flex: 1;
text-align: center;
padding: 8px 4px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
min-width: 0;
&.active {
background-color: rgba(24, 144, 255, 0.1);
border-color: #1890ff;
}
&:hover:not(.disabled) {
background-color: rgba(0, 0, 0, 0.02);
}
&.disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
.rating-circle {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
color: #666;
margin: 0 auto 6px;
transition: all 0.3s ease;
}
.rating-option.active .rating-circle {
background-color: #1890ff;
color: #fff;
}
.rating-label {
font-size: 11px;
color: #666;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rating-option.active .rating-label {
color: #1890ff;
font-weight: 500;
}
}
.comment-section {
margin-bottom: 24px;
.comment-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
display: block;
}
.comment-input {
width: 100%;
min-height: 120px;
padding: 12px;
border: 1px solid #d9d9d9;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
box-sizing: border-box;
&:focus {
border-color: #1890ff;
outline: none;
}
&:disabled {
background-color: #f5f5f5;
color: #999;
}
}
}
.ai-evaluation-section {
margin-bottom: 24px;
.ai-result {
margin-top: 16px;
padding: 16px;
background-color: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #1890ff;
.ai-result-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
display: block;
}
.ai-result-content {
font-size: 14px;
color: #555;
line-height: 1.6;
word-break: break-word;
}
}
}
/* 底部固定提交按钮 */
.bottom-submit {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 16px;
border-top: 1px solid #e5e5e5;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
z-index: 1000;
.submit-button {
width: 100%;
height: 44px;
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(:disabled) {
background-color: #1976d2;
transform: translateY(-1px);
}
&:disabled {
background-color: #d9d9d9;
color: #999;
cursor: not-allowed;
opacity: 0.6;
}
}
.button-icon {
font-size: 16px;
}
.button-text {
font-size: 14px;
}
}
/* 提交遮罩层样式 */
.submit-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.submit-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 16px;
color: #333;
font-weight: 500;
}
/* 响应式设计 */
@media screen and (max-width: 768px) {
.evaluation-content {
flex-direction: column;
}
.rating-scale {
flex-wrap: nowrap;
}
.rating-option {
min-width: 0;
padding: 6px 2px;
}
.rating-circle {
width: 28px;
height: 28px;
font-size: 12px;
margin: 0 auto 4px;
}
.rating-label {
font-size: 10px;
}
}
</style>

View File

@ -0,0 +1,716 @@
<template>
<view class="score-statistics-page">
<!-- 页面标题横幅 -->
<view class="page-banner">
<view class="banner-content">
<view class="banner-title-wrapper">
<text class="banner-icon">🏆</text>
<view class="banner-text">
<text class="banner-title">积分统计</text>
<text class="banner-subtitle">教师积分汇总</text>
</view>
</view>
<view class="total-score">
<text class="score-number">{{ totalScore }}</text>
<text class="score-label">总积分</text>
</view>
</view>
</view>
<!-- 学期选择 -->
<view class="filter-bar">
<view class="filter-left">
<text class="filter-icon">📅</text>
<text class="filter-text">学期筛选</text>
</view>
<view class="filter-right">
<uni-data-select
v-model="selectedXqId"
:localdata="xqList"
placeholder="选择学期"
@change="handleXqChange"
class="xq-select"
></uni-data-select>
</view>
</view>
<!-- 统计摘要 -->
<view class="summary-section">
<view class="summary-item">
<text class="summary-number">{{ teacherCount }}</text>
<text class="summary-label">教师数</text>
</view>
<view class="summary-item">
<text class="summary-number">{{ totalTasks }}</text>
<text class="summary-label">任务总数</text>
</view>
<view class="summary-item">
<text class="summary-number">{{ evaluatedTasks }}</text>
<text class="summary-label">已评价</text>
</view>
<view class="summary-item">
<text class="summary-number">{{ unevaluatedTasks }}</text>
<text class="summary-label">未评价</text>
</view>
</view>
<!-- 教师积分列表 -->
<view class="teacher-list">
<scroll-view
scroll-y
class="list-scroll-view"
@scrolltolower="loadMore"
lower-threshold="100"
>
<!-- 加载状态 -->
<view v-if="isLoading && teacherScoreList.length === 0" class="loading-indicator">
<text class="loading-icon"></text>
<text class="loading-text">加载中...</text>
</view>
<!-- 教师积分列表 -->
<template v-else-if="teacherScoreList.length > 0">
<view
v-for="(teacher, index) in teacherScoreList"
:key="teacher.rwzxfzr"
class="teacher-card"
>
<view class="card-main">
<!-- 排名标识 -->
<view class="rank-badge" :class="getRankClass(index)">
<text class="rank-number">{{ index + 1 }}</text>
</view>
<!-- 教师姓名 -->
<text class="teacher-name">{{ teacher.rwzxfzrxm || '未知' }}</text>
<!-- 积分徽章 -->
<view class="score-badge">
<text class="score-value">{{ teacher.totalScore || 0 }}</text>
<text class="score-unit"></text>
</view>
</view>
<view class="teacher-info">
<view class="teacher-stats">
<view class="stat-item">
<text class="stat-icon">📋</text>
<text class="stat-text">任务{{ teacher.taskCount || 0 }}</text>
</view>
<view class="stat-item">
<text class="stat-icon"></text>
<text class="stat-text">已评{{ teacher.evaluatedCount || 0 }}</text>
</view>
<view class="stat-item">
<text class="stat-icon"></text>
<text class="stat-text">待评{{ teacher.unevaluatedCount || 0 }}</text>
</view>
</view>
</view>
</view>
</template>
<!-- 空状态 -->
<view v-else class="empty-state">
<text class="empty-icon">📊</text>
<text class="empty-text">暂无积分数据</text>
<text class="empty-hint">当前学期还没有教师积分记录</text>
</view>
<!-- 加载更多 -->
<view v-if="isLoading && teacherScoreList.length > 0" class="loading-more">
<text class="loading-more-icon"></text>
<text class="loading-more-text">加载中...</text>
</view>
</scroll-view>
</view>
<!-- 底部返回按钮 -->
<view class="bottom-actions">
<button class="back-btn" @click="goBack">返回</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { getScoreStatisticsApi } from "@/api/base/rwzxApi";
import { getXqList } from "@/api/base/kcjbApi";
//
interface TeacherScore {
rwzxfzr: string; // ID
rwzxfzrxm: string; //
totalScore: number; //
taskCount: number; //
evaluatedCount: number; //
unevaluatedCount: number; //
}
//
const selectedXqId = ref('');
const xqList = ref<Array<{ value: string; text: string }>>([]);
const teacherScoreList = ref<TeacherScore[]>([]);
const isLoading = ref(false);
// -
const teacherCount = computed(() => teacherScoreList.value.length);
const totalScore = computed(() => {
return teacherScoreList.value.reduce((sum, teacher) => sum + (Number(teacher.totalScore) || 0), 0);
});
const totalTasks = computed(() => {
return teacherScoreList.value.reduce((sum, teacher) => sum + (Number(teacher.taskCount) || 0), 0);
});
const evaluatedTasks = computed(() => {
return teacherScoreList.value.reduce((sum, teacher) => sum + (Number(teacher.evaluatedCount) || 0), 0);
});
const unevaluatedTasks = computed(() => {
return teacherScoreList.value.reduce((sum, teacher) => sum + (Number(teacher.unevaluatedCount) || 0), 0);
});
//
const loadXqList = async () => {
try {
const response: any = await getXqList();
console.log('学期列表原始数据:', response);
// uni-data-select
const data = Array.isArray(response) ? response : (response?.result || response?.data || response?.rows || []);
console.log('提取的学期数据:', data);
xqList.value = data.map((item: any) => ({
value: item.id,
text: item.xqmc || item.name
}));
console.log('转换后的下拉选项:', xqList.value);
// dqxq = ""
const currentXq = data.find((item: any) => item.dqxq === '是');
if (currentXq) {
selectedXqId.value = currentXq.id;
console.log('默认选择当前学期:', selectedXqId.value);
} else if (xqList.value.length > 0) {
//
selectedXqId.value = xqList.value[0].value;
console.log('默认选择第一个学期:', selectedXqId.value);
}
//
if (selectedXqId.value) {
loadScoreStatistics();
}
} catch (error) {
console.error('获取学期列表失败:', error);
xqList.value = [];
}
};
//
const handleXqChange = (value: string) => {
console.log('选择的学期ID:', value);
loadScoreStatistics();
};
//
const loadScoreStatistics = async () => {
if (isLoading.value) return;
isLoading.value = true;
try {
console.log('加载积分统计学期ID:', selectedXqId.value);
const params: any = {
xqId: selectedXqId.value
};
const response = await getScoreStatisticsApi(params);
console.log('积分统计API响应:', response);
if (response && response.resultCode === 1) {
teacherScoreList.value = response.result || [];
console.log('积分统计数据:', teacherScoreList.value);
} else {
teacherScoreList.value = [];
uni.showToast({
title: response?.message || '查询失败',
icon: 'none'
});
}
} catch (error) {
console.error('加载积分统计失败:', error);
uni.showToast({
title: '加载失败',
icon: 'error'
});
teacherScoreList.value = [];
} finally {
isLoading.value = false;
}
};
//
const getRankClass = (index: number) => {
if (index === 0) return 'rank-gold';
if (index === 1) return 'rank-silver';
if (index === 2) return 'rank-bronze';
return 'rank-normal';
};
//
const loadMore = () => {
//
};
//
const goBack = () => {
uni.navigateBack();
};
//
onMounted(() => {
console.log('积分统计页面加载');
loadXqList();
});
//
onShow(() => {
console.log('页面显示,刷新数据');
if (selectedXqId.value) {
loadScoreStatistics();
}
});
</script>
<style lang="scss" scoped>
.score-statistics-page {
display: flex;
flex-direction: column;
height: 100vh;
background: linear-gradient(180deg, #f0f5ff 0%, #f5f7fa 100%);
}
//
.page-banner {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px 16px;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
.banner-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.banner-title-wrapper {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.banner-icon {
font-size: 32px;
line-height: 1;
}
.banner-text {
display: flex;
flex-direction: column;
gap: 4px;
}
.banner-title {
font-size: 24px;
font-weight: bold;
color: #ffffff;
line-height: 1.2;
}
.banner-subtitle {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 1px;
}
.total-score {
display: flex;
flex-direction: column;
align-items: center;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
padding: 10px 16px;
border-radius: 12px;
min-width: 70px;
}
.score-number {
font-size: 26px;
font-weight: bold;
color: #ffffff;
line-height: 1;
}
.score-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.9);
margin-top: 4px;
}
}
//
.filter-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
background-color: #ffffff;
border-bottom: 1px solid #e9ecef;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
gap: 12px;
.filter-left {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
.filter-icon {
font-size: 20px;
}
.filter-text {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
}
}
.filter-right {
flex: 1;
max-width: 200px;
.xq-select {
width: 100%;
}
}
}
//
.summary-section {
display: flex;
justify-content: space-around;
padding: 16px;
background-color: #ffffff;
margin-bottom: 8px;
border-bottom: 1px solid #e9ecef;
}
.summary-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
.summary-number {
font-size: 22px;
font-weight: bold;
color: #667eea;
}
.summary-label {
font-size: 12px;
color: #666;
}
}
//
.teacher-list {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
.list-scroll-view {
flex: 1;
padding: 12px 16px 80px 16px;
box-sizing: border-box;
height: 0;
}
}
//
.teacher-card {
background: linear-gradient(135deg, #ffffff 0%, #f5f7ff 100%);
border-radius: 16px;
margin-bottom: 16px;
box-shadow:
0 2px 16px rgba(0, 0, 0, 0.08),
0 0 0 1px rgba(102, 126, 234, 0.1);
position: relative;
overflow: hidden;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:active {
transform: translateY(2px) scale(0.98);
box-shadow:
0 1px 8px rgba(0, 0, 0, 0.12),
0 0 0 1px rgba(102, 126, 234, 0.15);
}
//
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 5px;
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
border-radius: 16px 0 0 16px;
}
.card-main {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
padding-left: 20px;
border-bottom: 1px solid #f0f0f0;
}
.rank-badge {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
flex-shrink: 0;
border: 2px solid #ffffff;
.rank-number {
font-size: 14px;
font-weight: bold;
color: #ffffff;
}
&.rank-gold {
background: linear-gradient(135deg, #ffd700 0%, #ffb300 100%);
}
&.rank-silver {
background: linear-gradient(135deg, #c0c0c0 0%, #a8a8a8 100%);
}
&.rank-bronze {
background: linear-gradient(135deg, #cd7f32 0%, #b87333 100%);
}
&.rank-normal {
background: linear-gradient(135deg, #78909c 0%, #607d8b 100%);
}
}
.teacher-name {
font-size: 18px;
font-weight: 700;
color: #1a202c;
flex: 1;
}
.score-badge {
display: flex;
align-items: baseline;
gap: 2px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 8px 14px;
border-radius: 20px;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
flex-shrink: 0;
.score-value {
font-size: 20px;
font-weight: bold;
color: #ffffff;
}
.score-unit {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
}
}
.teacher-info {
padding: 12px 16px 16px 16px;
.teacher-stats {
display: flex;
gap: 12px;
flex-wrap: wrap;
.stat-item {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 10px;
background: rgba(102, 126, 234, 0.05);
border-radius: 8px;
border: 1px solid rgba(102, 126, 234, 0.1);
.stat-icon {
font-size: 14px;
}
.stat-text {
font-size: 12px;
color: #4a5568;
font-weight: 500;
}
}
}
}
}
//
.loading-indicator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
.loading-icon {
font-size: 32px;
margin-bottom: 12px;
animation: pulse 1.5s ease-in-out infinite;
}
.loading-text {
color: #667eea;
font-size: 14px;
}
}
//
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 20px;
.empty-icon {
font-size: 64px;
margin-bottom: 16px;
opacity: 0.6;
}
.empty-text {
font-size: 16px;
color: #4a5568;
font-weight: 600;
margin-bottom: 8px;
}
.empty-hint {
font-size: 13px;
color: #a0aec0;
}
}
//
.loading-more {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 20px;
.loading-more-icon {
font-size: 18px;
animation: pulse 1.5s ease-in-out infinite;
}
.loading-more-text {
color: #667eea;
font-size: 14px;
font-weight: 500;
}
}
//
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12px 16px;
background: #ffffff;
border-top: 1px solid #e9ecef;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
.back-btn {
width: 100%;
height: 44px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 22px;
font-size: 16px;
font-weight: 500;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
&:active {
transform: translateY(1px);
box-shadow: 0 1px 4px rgba(102, 126, 234, 0.3);
}
}
}
//
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.1);
}
}
//
:deep(.filter-right .uni-data-select) {
width: 100%;
.uni-select {
background-color: #f5f7ff;
border-radius: 8px;
border: 1px solid #d4dbff;
height: 36px;
min-height: 36px;
}
.uni-select__input-text {
color: #212529;
font-size: 14px;
}
.uni-select__input-placeholder {
color: #adb5bd;
font-size: 13px;
}
.uni-select__selector {
padding: 0 12px;
}
}
</style>

View File

@ -9,83 +9,88 @@
</view>
</view>
<!-- 完成情况统计 -->
<view class="completion-summary">
<view class="summary-header">
<text class="summary-title">完成情况{{ completionStats.completed }} | {{ completionStats.total }}</text>
<!-- 执行情况统计 -->
<view class="task-summary">
<view class="summary-item pending-evaluate" :class="{ active: taskFilter === 'pendingEvaluate' }" @click="setTaskFilter('pendingEvaluate')">
<text class="summary-count">{{ executionStats.pendingEvaluateNum }}</text>
<text class="summary-label">待评价</text>
</view>
<!-- Tab切换 -->
<view class="tab-container">
<view
class="tab-item"
:class="{ active: activeTab === 'submitted' }"
@click="switchTab('submitted')"
>
<text class="tab-text">已提交</text>
<text class="tab-count">({{ completionStats.completed }})</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'pending' }"
@click="switchTab('pending')"
>
<text class="tab-text">未提交</text>
<text class="tab-count">({{ completionStats.pending }})</text>
</view>
<view class="summary-item evaluated" :class="{ active: taskFilter === 'evaluated' }" @click="setTaskFilter('evaluated')">
<text class="summary-count">{{ executionStats.evaluatedNum }}</text>
<text class="summary-label">已评价</text>
</view>
<view class="summary-item not-submitted" :class="{ active: taskFilter === 'notSubmitted' }" @click="setTaskFilter('notSubmitted')">
<text class="summary-count">{{ executionStats.notSubmittedNum }}</text>
<text class="summary-label">未提交</text>
</view>
<view class="summary-item submitted" :class="{ active: taskFilter === 'submitted' }" @click="setTaskFilter('submitted')">
<text class="summary-count">{{ executionStats.submittedNum }}</text>
<text class="summary-label">已提交</text>
</view>
<view class="summary-item" :class="{ active: taskFilter === 'all' }" @click="setTaskFilter('all')">
<text class="summary-count">{{ executionStats.allNum }}</text>
<text class="summary-label">全部</text>
</view>
</view>
<!-- 执行人员列表 -->
<view class="execution-list">
<scroll-view scroll-y class="execution-scroll">
<view v-if="loading" class="loading-container">
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="filteredExecutionList.length === 0" class="empty-state">
<text class="empty-text">{{ activeTab === 'submitted' ? '暂无已提交记录' : '暂无未提交记录' }}</text>
</view>
<view v-if="loading" class="loading-container">
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="filteredExecutionList.length === 0" class="empty-state">
<text class="empty-icon">📦</text>
<text class="empty-text">{{ getEmptyStateText() }}</text>
</view>
<view v-else>
<view
v-for="execution in filteredExecutionList"
:key="execution.id"
class="execution-item"
@click="viewExecutionDetail(execution)"
>
<!-- 执行人信息 -->
<view class="executor-info">
<view class="executor-avatar">
<text class="avatar-text">{{ execution.rwzxfzrxm?.charAt(0) || '?' }}</text>
</view>
<view class="executor-details">
<text class="executor-name">{{ execution.rwzxfzrxm }}</text>
</view>
<view v-else>
<view
v-for="execution in filteredExecutionList"
:key="execution.id"
class="execution-item"
@click="viewExecutionDetail(execution)"
>
<view class="execution-header">
<view class="execution-title-row">
<view
class="status-dot"
:class="getStatusDotClass(execution)"
></view>
<text class="execution-title">{{ execution.rwzxfzrxm }}</text>
<text class="arrow-icon"></text>
</view>
</view>
<!-- 执行状态 -->
<view class="execution-status">
<view :class="['status-badge', execution.rwzxzt === 'A' ? 'completed' : 'pending']">
<text class="status-text">{{ execution.rwzxzt === 'A' ? '已完成' : '未完成' }}</text>
</view>
<!-- 完成时间 -->
<view v-if="execution.rwzxzt === 'A' && execution.rwzxtime" class="completion-time">
<text class="time-text">{{ formatDateTime(execution.rwzxtime) }}</text>
</view>
<view class="execution-body">
<view class="execution-detail">
<text class="detail-label">提交状态</text>
<text class="detail-content">{{ getSubmitStatusText(execution) }}</text>
</view>
<view class="execution-detail">
<text class="detail-label">评价状态</text>
<text class="detail-content">{{ getEvaluateStatusText(execution) }}</text>
</view>
<view v-if="execution.rwzxzt === 'A' && execution.rwzxtime" class="execution-detail">
<text class="detail-label">提交时间</text>
<text class="detail-content">{{ formatDateTime(execution.rwzxtime) }}</text>
</view>
<view v-if="execution.rwzxnr" class="execution-detail">
<text class="detail-label">执行内容</text>
<text class="detail-content">{{ execution.rwzxnr }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onLoad, onUnmounted } from "vue";
import { ref, reactive, computed, onUnmounted } from "vue";
import { onLoad as onPageLoad, onUnload } from "@dcloudio/uni-app";
import { executedInfoByRwIdApi } from "@/api/base/rwzxApi";
import { rwFindInfoByRwId } from "@/api/base/server";
//
interface TaskInfo {
@ -110,6 +115,12 @@ interface ExecutionItem {
rwzxnr: string;
createTime: string;
jsId?: string; // ID
ispj?: string; // A: , B:
rwpjr?: string; // id
rwpjrxm?: string; //
rwpjjf?: string; //
rwpjms?: string; //
rwpjtime?: string; //
}
//
@ -125,37 +136,41 @@ const taskInfo = ref<TaskInfo>({
});
const courseId = ref('');
const activeTab = ref('submitted'); // submitted: , pending:
const taskFilter = ref('pendingEvaluate'); // 'all', 'pendingEvaluate', 'evaluated', 'notSubmitted', 'submitted'
const loading = ref(false);
const executionList = ref<ExecutionItem[]>([]);
const completedCount = ref(0);
const pendingCount = ref(0);
//
const completionStats = computed(() => {
const total = completedCount.value + pendingCount.value;
return {
total,
completed: completedCount.value,
pending: pendingCount.value
};
//
const executionStats = ref({
allNum: 0,
pendingEvaluateNum: 0,
evaluatedNum: 0,
notSubmittedNum: 0,
submittedNum: 0
});
// TabiszxwcA=B=
//
const filteredExecutionList = computed(() => {
return executionList.value.filter(item => {
if (activeTab.value === 'submitted') {
return item.iszxwc === 'A'; //
} else {
return item.iszxwc === 'B'; //
}
});
if (taskFilter.value === 'all') {
return executionList.value;
} else if (taskFilter.value === 'pendingEvaluate') {
//
return executionList.value.filter(item => item.iszxwc === 'A' && item.ispj === 'B');
} else if (taskFilter.value === 'evaluated') {
//
return executionList.value.filter(item => item.iszxwc === 'A' && item.ispj === 'A');
} else if (taskFilter.value === 'notSubmitted') {
//
return executionList.value.filter(item => item.iszxwc === 'B');
} else if (taskFilter.value === 'submitted') {
//
return executionList.value.filter(item => item.iszxwc === 'A');
}
return executionList.value;
});
//
onPageLoad((options: any) => {
onPageLoad(async (options: any) => {
console.log('任务执行页面接收到的参数:', options);
//
@ -171,11 +186,7 @@ onPageLoad((options: any) => {
//
uni.removeStorageSync('taskExecutionData');
loadExecutionList();
//
setTimeout(() => {
loadStatistics();
}, 100);
await loadExecutionList();
//
setupRefreshListener();
@ -190,48 +201,99 @@ onPageLoad((options: any) => {
}, 1500);
}
} else {
console.error('缺少任务信息参数');
uni.showToast({
title: '缺少任务信息',
icon: 'error'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
// URLAPI
console.log('未找到存储的任务数据尝试从URL参数获取任务ID');
const taskId = options.taskId || options.id;
const courseIdParam = options.courseId;
if (taskId) {
console.log('从URL参数获取到任务ID:', taskId);
try {
// API
await loadTaskInfoById(taskId, courseIdParam);
await loadExecutionList();
setupRefreshListener();
} catch (error) {
console.error('通过API获取任务信息失败:', error);
uni.showToast({
title: '获取任务信息失败',
icon: 'error'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
} else {
console.error('缺少任务信息参数');
uni.showToast({
title: '缺少任务信息',
icon: 'error'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}
});
// iszxwc
// ID
const loadTaskInfoById = async (taskId: string, courseIdParam?: string) => {
try {
console.log('开始通过API获取任务信息任务ID:', taskId);
const response = await rwFindInfoByRwId({ rwId: taskId });
console.log('任务信息API响应:', response);
if (response && response.result) {
taskInfo.value = response.result;
courseId.value = courseIdParam || taskInfo.value.rwlyId || '';
console.log('成功获取任务信息:', taskInfo.value);
console.log('课程ID:', courseId.value);
} else {
throw new Error('API返回数据格式错误');
}
} catch (error) {
console.error('通过API获取任务信息失败:', error);
throw error;
}
};
//
const loadStatistics = async () => {
try {
console.log('开始加载统计数据任务ID:', taskInfo.value.id);
console.log('当前执行列表数据:', executionList.value);
// executedInfoByRwId
const response = await executedInfoByRwIdApi({ rwId: taskInfo.value.id });
console.log('执行情况API响应:', response);
const allData = executionList.value;
// API
let allData = [];
if (response && response.resultCode === 1) {
allData = response.result || response.rows || response.data || [];
} else if (Array.isArray(response)) {
allData = response;
}
// 5
executionStats.value = {
allNum: allData.length,
pendingEvaluateNum: allData.filter(item => item.iszxwc === 'A' && item.ispj === 'B').length,
evaluatedNum: allData.filter(item => item.iszxwc === 'A' && item.ispj === 'A').length,
notSubmittedNum: allData.filter(item => item.iszxwc === 'B').length,
submittedNum: allData.filter(item => item.iszxwc === 'A').length
};
// iszxwcA=B=
const submittedData = allData.filter(item => item.iszxwc === 'A');
const pendingData = allData.filter(item => item.iszxwc === 'B');
completedCount.value = submittedData.length;
pendingCount.value = pendingData.length;
console.log('统计结果 - 已提交:', completedCount.value, '未提交:', pendingCount.value);
console.log('统计结果:', executionStats.value);
console.log('详细统计计算:');
console.log('- 全部数量:', allData.length);
console.log('- 未提交数量:', allData.filter(item => item.iszxwc === 'B').length);
console.log('- 已提交数量:', allData.filter(item => item.iszxwc === 'A').length);
console.log('- 待评价数量:', allData.filter(item => item.iszxwc === 'A' && item.ispj === 'B').length);
console.log('- 已评价数量:', allData.filter(item => item.iszxwc === 'A' && item.ispj === 'A').length);
} catch (error) {
console.error('加载统计数据失败:', error);
// 使
completedCount.value = 0;
pendingCount.value = 0;
executionStats.value = {
allNum: 0,
pendingEvaluateNum: 0,
evaluatedNum: 0,
notSubmittedNum: 0,
submittedNum: 0
};
}
};
@ -250,19 +312,19 @@ const loadExecutionList = async () => {
if (response) {
if (response.hasOwnProperty('resultCode')) {
if (response.resultCode === 1 || response.resultCode === 0) {
executionData = response.result || response.rows || response.data || [];
executionData = response.result || response.rows || (response as any).data || [];
} else {
throw new Error(response?.message || response?.msg || '获取执行情况失败');
throw new Error((response as any)?.message || (response as any)?.msg || '获取执行情况失败');
}
} else if (response.rows || response.data) {
executionData = response.rows || response.data || [];
} else if ((response as any).rows || (response as any).data) {
executionData = (response as any).rows || (response as any).data || [];
} else if (Array.isArray(response)) {
executionData = response;
}
}
// iszxwc
executionList.value = executionData.map(item => ({
executionList.value = executionData.map((item: any) => ({
...item,
// iszxwcrwzxztA=()B=()
rwzxzt: item.iszxwc === 'A' ? 'A' : 'B'
@ -270,6 +332,9 @@ const loadExecutionList = async () => {
console.log('解析后的执行情况列表:', executionList.value);
//
loadStatistics();
} catch (error) {
console.error('加载任务执行情况失败:', error);
uni.showToast({
@ -282,44 +347,113 @@ const loadExecutionList = async () => {
}
};
// Tab
const switchTab = (tab: string) => {
activeTab.value = tab;
// TabcomputedfilteredExecutionList
//
const setTaskFilter = (filter: string) => {
taskFilter.value = filter;
};
//
const getEmptyStateText = () => {
switch (taskFilter.value) {
case 'pendingEvaluate':
return '暂无待评价记录';
case 'evaluated':
return '暂无已评价记录';
case 'notSubmitted':
return '暂无未提交记录';
case 'submitted':
return '暂无已提交记录';
default:
return '暂无执行记录';
}
};
//
const getStatusDotClass = (execution: ExecutionItem) => {
if (execution.iszxwc === 'A' && execution.ispj === 'A') {
return 'evaluated'; //
} else if (execution.iszxwc === 'A') {
return 'pending-evaluate'; //
} else {
return 'not-submitted'; //
}
};
//
const getSubmitStatusText = (execution: ExecutionItem) => {
return execution.iszxwc === 'A' ? '已提交' : '未提交';
};
//
const getEvaluateStatusText = (execution: ExecutionItem) => {
if (execution.iszxwc === 'B') {
return '未提交';
}
return execution.ispj === 'A' ? '已评价' : '待评价';
};
//
const viewExecutionDetail = (execution: ExecutionItem) => {
//
const params = {
id: taskInfo.value.id,
jsId: execution.rwzxfzr,
executionId: execution.id,
isReadOnly: execution.rwzxzt === 'A' //
};
console.log('点击执行记录:', execution);
uni.setStorageSync('taskSubmitData', params);
//
uni.navigateTo({
url: `/pages/view/routine/yishiyice/kcrwzxtj`,
success: () => {
console.log('跳转到任务提交页面成功', {
taskId: taskInfo.value.id,
jsId: execution.rwzxfzr,
executionId: execution.id,
isReadOnly: params.isReadOnly,
executionData: execution
});
},
fail: (error) => {
console.error('跳转到任务提交页面失败:', error);
uni.showToast({
title: '页面跳转失败',
icon: 'error'
});
}
});
//
if (execution.iszxwc === 'A' && execution.ispj === 'B') {
//
const evaluationData = {
taskInfo: taskInfo.value,
executionInfo: execution
};
uni.setStorageSync('evaluationData', evaluationData);
uni.navigateTo({
url: '/pages/view/routine/yishiyice/kcxxpj',
success: () => {
console.log('跳转到评价页面成功', {
taskId: taskInfo.value.id,
executionId: execution.id,
executionData: execution
});
},
fail: (error) => {
console.error('跳转到评价页面失败:', error);
uni.showToast({
title: '页面跳转失败',
icon: 'error'
});
}
});
} else {
//
const params = {
id: taskInfo.value.id,
jsId: execution.rwzxfzr,
executionId: execution.id,
isReadOnly: execution.rwzxzt === 'A' //
};
uni.setStorageSync('taskSubmitData', params);
uni.navigateTo({
url: `/pages/view/routine/yishiyice/kcrwzxtj`,
success: () => {
console.log('跳转到任务提交页面成功', {
taskId: taskInfo.value.id,
jsId: execution.rwzxfzr,
executionId: execution.id,
isReadOnly: params.isReadOnly,
executionData: execution
});
},
fail: (error) => {
console.error('跳转到任务提交页面失败:', error);
uni.showToast({
title: '页面跳转失败',
icon: 'error'
});
}
});
}
};
//
@ -340,11 +474,8 @@ const setupRefreshListener = () => {
//
uni.$on('refreshTaskExecution', () => {
console.log('收到刷新任务执行事件');
//
// loadExecutionList
loadExecutionList();
setTimeout(() => {
loadStatistics();
}, 100);
});
};
@ -357,9 +488,7 @@ onUnload(() => {
<style lang="scss" scoped>
.task-execution-page {
min-height: 100vh;
background-color: #f5f7fa;
display: flex;
flex-direction: column;
background-color: #ffffff;
}
.task-info-card {
@ -369,7 +498,7 @@ onUnload(() => {
border-radius: 0;
box-shadow: none;
border: none;
border-bottom: 1px solid #f0f0f0;
border-bottom: 1px solid #e5e5e5;
.task-title {
font-size: 16px;
@ -390,175 +519,182 @@ onUnload(() => {
}
}
.completion-summary {
/* 任务统计 */
.task-summary {
display: flex;
justify-content: space-around;
margin: 0;
background-color: #fff;
padding: 12px 8px;
background-color: #ffffff;
border-radius: 0;
box-shadow: none;
border: none;
border-bottom: 1px solid #f0f0f0;
overflow: hidden;
.summary-header {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
background-color: #f8f9fa;
.summary-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
}
.tab-container {
display: flex;
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
background-color: #fff;
border-right: 1px solid #f0f0f0;
transition: all 0.3s ease;
&:last-child {
border-right: none;
}
&.active {
background-color: #1890ff;
.tab-text, .tab-count {
color: #fff;
}
}
.tab-text {
font-size: 14px;
font-weight: 500;
color: #666;
margin-right: 4px;
}
.tab-count {
font-size: 12px;
color: #999;
}
}
}
border-bottom: 1px solid #e5e5e5;
}
.summary-item {
text-align: center;
padding: 8px 6px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
min-width: 60px;
}
.summary-item:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.summary-item.active {
background-color: rgba(24, 144, 255, 0.1);
border-color: #1890ff;
}
.summary-item.pending-evaluate.active {
background-color: rgba(250, 140, 22, 0.1);
border-color: #fa8c16;
}
.summary-item.evaluated.active {
background-color: rgba(82, 196, 26, 0.1);
border-color: #52c41a;
}
.summary-item.not-submitted.active {
background-color: rgba(255, 77, 79, 0.1);
border-color: #ff4d4f;
}
.summary-item.submitted.active {
background-color: rgba(24, 144, 255, 0.1);
border-color: #1890ff;
}
.summary-count {
display: block;
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.summary-item.pending-evaluate .summary-count {
color: #fa8c16;
}
.summary-item.evaluated .summary-count {
color: #52c41a;
}
.summary-item.not-submitted .summary-count {
color: #ff4d4f;
}
.summary-item.submitted .summary-count {
color: #1890ff;
}
.summary-label {
font-size: 12px;
color: #666;
}
/* 执行列表 */
.execution-list {
flex: 1;
margin: 0;
background-color: #fff;
border-radius: 0;
box-shadow: none;
border: none;
overflow: hidden;
}
.execution-scroll {
max-height: 60vh;
min-height: 200px;
padding: 0;
margin: 0;
}
.execution-item {
display: flex;
align-items: center;
padding: 16px;
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
transition: all 0.2s ease;
&:active {
background-color: #f8f9fa;
}
&:last-child {
border-bottom: none;
}
background-color: white;
border-radius: 0;
margin-bottom: 0;
box-shadow: none;
overflow: hidden;
transition: all 0.3s ease;
border-bottom: 1px solid #e5e5e5;
}
.executor-info {
.execution-item:active {
background-color: #f8f9fa;
}
.execution-header {
padding: 14px 16px;
background: #ffffff;
border-bottom: none;
}
.execution-title-row {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
.executor-avatar {
width: 40px;
height: 40px;
gap: 0;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #1890ff;
display: flex;
align-items: center;
justify-content: center;
.avatar-text {
color: #fff;
font-size: 16px;
font-weight: 500;
}
background: #999;
flex-shrink: 0;
margin-right: 10px;
}
.executor-details {
.status-dot.evaluated {
background: #52c41a;
}
.status-dot.pending-evaluate {
background: #fa8c16;
}
.status-dot.not-submitted {
background: #ff4d4f;
}
.execution-title {
font-size: 15px;
font-weight: 600;
color: #1f2937;
flex: 1;
.executor-name {
display: block;
font-size: 16px;
color: #333;
font-weight: 500;
}
}
line-height: 1.3;
}
.execution-status {
.arrow-icon {
font-size: 20px;
color: #999;
font-weight: bold;
flex-shrink: 0;
}
.execution-body {
padding: 0 16px 14px 16px;
}
.execution-detail {
font-size: 12px;
line-height: 1.8;
color: #666666;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
.status-badge {
padding: 6px 12px;
border-radius: 16px;
&.completed {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
.status-text {
color: #52c41a;
font-size: 14px;
font-weight: 500;
}
}
&.pending {
background-color: #fff2e8;
border: 1px solid #ffbb96;
.status-text {
color: #fa8c16;
font-size: 14px;
font-weight: 500;
}
}
}
.completion-time {
.time-text {
font-size: 12px;
color: #999;
}
}
margin-bottom: 4px;
}
.execution-detail:last-child {
margin-bottom: 0;
}
.detail-label {
flex-shrink: 0;
color: #999;
}
.detail-content {
margin-left: 4px;
flex: 1;
color: #333;
}
/* 通用样式 */
.loading-container,
.empty-state {
display: flex;
@ -567,26 +703,25 @@ onUnload(() => {
justify-content: center;
padding: 40px 20px;
color: #666;
.loading-text,
.empty-text {
font-size: 14px;
}
background-color: white;
margin: 0;
border-radius: 0;
}
//
@media screen and (max-width: 375px) {
.execution-item {
padding: 10px;
.executor-avatar {
width: 32px;
height: 32px;
.avatar-text {
font-size: 12px;
}
}
}
.loading-text {
font-size: 13px;
color: #1890ff;
}
.empty-icon {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.6;
}
.empty-text {
font-size: 14px;
color: #909399;
margin-bottom: 8px;
}
</style>

View File

@ -1,6 +1,6 @@
<!-- src/pages/base/message/detail.vue -->
<!-- 任务执行提交页面 -->
<template>
<view class="message-detail-page" :class="{ 'readonly-mode': isReadOnly }">
<view class="rw-detail-page" :class="{ 'readonly-mode': isReadOnly }">
<!-- 提交遮罩层 -->
<view v-if="isSubmitting" class="submit-overlay">
<view class="submit-loading">
@ -10,30 +10,62 @@
</view>
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="detail-content">
<view class="detail-header">
<view class="title-tag-row">
<text class="detail-title">{{ rw.rwmc }}</text>
<view v-else class="content-wrapper">
<view class="p-15">
<!-- 第一部分任务要求 -->
<view class="rw-info-section">
<view class="section-title">任务要求</view>
<!-- 只读模式提示 -->
<view v-if="isReadOnly" class="readonly-tip">
<text class="tip-text">📖 只读模式 - 查看已提交的内容</text>
</view>
<!-- 任务名称 -->
<view class="info-item">
<text class="label">任务名称</text>
<text class="value title-bold">{{ rw.rwmc }}</text>
</view>
<!-- 任务描述 -->
<view v-if="rw.rwms" class="info-item">
<text class="label">任务描述</text>
<text class="value">{{ rw.rwms }}</text>
</view>
<!-- 任务时间 -->
<view v-if="rw.rwkstime" class="info-item">
<text class="label">任务时间</text>
<text class="value">{{ rw.rwkstime }}</text>
</view>
<!-- 附件预览 -->
<BasicFilePreview
v-if="rw.rwfj && rw.fjmx"
:file-url="rw.rwfj"
:file-name="rw.fjmx"
:file-format="getFileFormat(rw.fjmx)"
class="mt-15"
/>
</view>
<view v-if="isReadOnly" class="readonly-tip">
<text class="tip-text">📖 只读模式 - 查看已提交的内容</text>
<!-- 第二部分任务执行 -->
<view class="rw-execute-section">
<view class="section-title">任务执行</view>
<view class="execute-form">
<BasicForm :schema="schema" v-model="formData" :disabled="isReadOnly">
</BasicForm>
</view>
</view>
<view class="detail-meta">
<text>{{ rw.rwkstime }}</text>
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
</view>
</view>
<view class="detail-body">
<BasicForm :schema="schema" v-model="formData" :disabled="isReadOnly">
</BasicForm>
</view>
<view v-if="!isReadOnly" class="detail-footer">
<button class="action-button" @click="saveRwZx" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : '提交' }}
</button>
</view>
</view>
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
<!-- 提交按钮 - 固定在底部 -->
<view v-if="!isReadOnly" class="submit-button-fixed">
<button class="action-button" @click="saveRwZx" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : '提交' }}
</button>
</view>
</view>
</template>
@ -46,6 +78,7 @@ import {navigateBack, showToast} from "@/utils/uniapp";
import {useUserStore} from "@/store/modules/user";
import { ImageVideoUpload, type FileItem, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
import { attachmentUpload } from "@/api/system/upload";
import BasicFilePreview from "@/components/BasicFile/preview.vue";
interface MessageDetail {
id: string; // Assuming an ID is passed or can be derived
@ -55,9 +88,23 @@ interface MessageDetail {
timeAgo: string;
tagText: string;
tagType: string;
likes?: number;
comments?: number;
// Add other fields as necessary
}
interface RwInfo {
id?: string;
rwmc?: string; //
rwms?: string; //
rwkstime?: string; //
rwfj?: string; // URL
fjmx?: string; //
rwlxes?: any[]; //
rwzxqds?: any[]; //
[key: string]: any; //
}
const formData: any = ref({})
const messageId = ref<string>('');
const jsId = ref<string>(''); // ID
@ -79,7 +126,7 @@ const messageDetail = ref<MessageDetail | null>({
});
const isLoading = ref(false);
const rwflx: any = ref([])
const rw = ref<any>({})
const rw = ref<RwInfo>({})
const schema = ref<FormsSchema[]>([])
const {getUser} = useUserStore()
@ -202,20 +249,13 @@ async function performSubmit() {
})
showToast("操作成功!");
//
uni.$emit('refreshTaskExecution');
//
setTimeout(() => {
//
uni.redirectTo({
url: `/pages/view/routine/yishiyice/detail?kcjbId=${rw.value.rwlyId}&kcmc=${encodeURIComponent(rw.value.rwmc)}`,
success: () => {
console.log('跳转到课程详情页面成功');
},
fail: (error) => {
console.error('跳转到课程详情页面失败:', error);
// 退
uni.navigateBack({ delta: 1 });
}
});
//
uni.navigateBack();
}, 1500);
} catch (error) {
@ -226,6 +266,13 @@ async function performSubmit() {
}
}
//
const getFileFormat = (fileName: string) => {
if (!fileName) return '';
const parts = fileName.split('.');
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
};
const rwzxqds = ref<any[]>([])
onLoad(async (options: any) => {
console.log('页面加载参数:', options);
@ -492,79 +539,49 @@ onLoad(async (options: any) => {
</script>
<style scoped lang="scss">
.message-detail-page {
.rw-detail-page {
background-color: #f4f5f7;
min-height: 100vh;
padding: 15px;
padding-bottom: 80px; //
box-sizing: border-box;
}
.loading-indicator,
.empty-state {
.loading-indicator {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.detail-content {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.content-wrapper {
.p-15 {
padding: 15px;
}
.mt-15 {
margin-top: 15px;
}
}
.detail-header {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 15px;
//
.rw-info-section {
margin-bottom: 20px;
.title-tag-row {
display: flex;
justify-content: space-between;
align-items: flex-start; // Align items to the top
margin-bottom: 10px;
}
.detail-title {
font-size: 18px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
flex: 1; // Allow title to take available space
margin-right: 10px; // Space between title and tag
line-height: 1.4;
text-align: center; //
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.tag { // Reuse tag styles from index page if possible, or define here
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
color: #ffffff;
white-space: nowrap;
flex-shrink: 0; // Prevent tag from shrinking
&.notice {
background-color: #447ade;
}
&.task {
background-color: #19be6b;
}
&.approval {
background-color: #ff9f0a;
}
&.submit {
background-color: #8e8e93;
}
}
.readonly-tip {
margin: 8px 0;
margin-bottom: 15px;
padding: 8px 12px;
background-color: #e6f7ff;
border: 1px solid #91d5ff;
@ -575,59 +592,96 @@ onLoad(async (options: any) => {
color: #1890ff;
}
}
.detail-meta {
font-size: 12px;
color: #999;
text {
margin-right: 15px;
.info-item {
display: flex;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
.label {
width: 80px;
color: #666;
font-size: 14px;
flex-shrink: 0;
}
.value {
flex: 1;
color: #333;
font-size: 14px;
word-break: break-word;
&.title-bold {
font-weight: bold;
font-size: 16px;
}
}
}
}
.detail-body {
margin-bottom: 40px;
.detail-desc {
font-size: 15px;
color: #555;
line-height: 1.7;
word-break: break-word;
}
}
.detail-footer {
// text-align: center; // Removed center alignment
// Add margin if needed, e.g., margin-top: 20px;
}
// Style for the action button
.action-button {
width: 100%; // Make button full width
height: 44px; // Standard button height
line-height: 44px; // Match height for vertical centering
font-size: 16px; // Slightly larger font
font-weight: 500; // Medium weight
border-radius: 8px; // Consistent border radius
margin-top: 20px; // Add space above the button
background-color: #1890ff; //
color: #ffffff; //
border: none; //
//
.rw-execute-section {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
&:active:not(:disabled) {
background-color: #1976d2; //
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
&:disabled {
background-color: #d9d9d9;
color: #999;
cursor: not-allowed;
opacity: 0.6;
.execute-form {
padding-top: 5px;
}
}
// textFiled
// -
.submit-button-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 15px;
background-color: #fff;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 10;
border-top: 1px solid #eee;
.action-button {
width: 100%;
height: 44px;
line-height: 44px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
background-color: #409eff;
color: #ffffff;
border: none;
&:active:not(:disabled) {
background-color: #3a8ee6;
transform: translateY(1px);
}
&:disabled {
background-color: #d9d9d9 !important;
color: #999 !important;
cursor: not-allowed;
opacity: 0.6;
}
}
}
//
:deep(.uni-input),
:deep(.uni-textarea) {
width: 100% !important;
@ -639,21 +693,6 @@ onLoad(async (options: any) => {
box-sizing: border-box !important;
}
// BasicForm
:deep(.basic-form) {
.uni-input,
.uni-textarea {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
}
// input textarea
:deep(input),
:deep(textarea) {
width: 100% !important;
@ -665,6 +704,32 @@ onLoad(async (options: any) => {
box-sizing: border-box !important;
}
// BasicForm label
:deep(.basic-form) {
.form-label,
.label,
.uni-form-item__label {
width: 100% !important;
white-space: normal !important;
word-break: break-word !important;
font-size: 15px !important;
font-weight: bold !important;
color: #333 !important;
text-align: left !important;
display: block !important;
line-height: 1.4 !important;
max-width: 100% !important;
overflow-wrap: break-word !important;
}
}
// view
:deep(view) {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
}
//
.readonly-mode {
:deep(input),
@ -712,7 +777,7 @@ onLoad(async (options: any) => {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #1890ff;
border-top: 3px solid #409eff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
@ -728,5 +793,4 @@ onLoad(async (options: any) => {
color: #333;
font-weight: 500;
}
</style>

View File

@ -0,0 +1,973 @@
<template>
<view class="evaluation-page">
<!-- 提交遮罩层 -->
<view v-if="isSubmitting" class="submit-overlay">
<view class="submit-loading">
<view class="loading-spinner"></view>
<text class="loading-text">提交评价中...</text>
</view>
</view>
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="evaluation-content">
<!-- 左侧作品预览和详情 -->
<view class="work-preview-section">
<!-- 完成成果区域 -->
<view class="work-preview">
<view class="preview-title-header">
<text class="preview-title-icon">📋</text>
<text class="preview-title">完成成果</text>
</view>
<view class="preview-content">
<!-- 任务基本信息 -->
<view class="task-info">
<view class="info-item">
<text class="info-label">任务名称</text>
<text class="info-value">{{ taskInfo.rwmc }}</text>
</view>
<view class="info-item inline">
<text class="info-label">提交人</text>
<text class="info-value">{{ executionInfo.rwzxfzrxm }}</text>
</view>
<view class="info-item inline">
<text class="info-label">提交时间</text>
<text class="info-value">{{ formatDateTime(executionInfo.rwzxtime) }}</text>
</view>
</view>
<!-- 提交内容 -->
<view class="submitted-content">
<text class="content-title">提交内容</text>
<view v-if="submittedContent.length > 0" class="content-display">
<view
v-for="(item, index) in submittedContent"
:key="index"
class="content-item"
>
<view class="content-label">{{ item.label }}</view>
<view class="content-value">
<!-- 文本内容 -->
<text v-if="item.type === 'text'" class="text-content">{{ item.value }}</text>
<!-- 图片内容 -->
<view v-else-if="item.type === 'image'" class="media-content">
<ImageVideoUpload
:enableImage="true"
:enableVideo="false"
:enableFile="false"
:imageList="item.value"
:disabled="true"
:readonly="true"
/>
</view>
<!-- 视频内容 -->
<view v-else-if="item.type === 'video'" class="media-content">
<ImageVideoUpload
:enableImage="false"
:enableVideo="true"
:enableFile="false"
:videoList="item.value"
:disabled="true"
:readonly="true"
/>
</view>
<!-- 文档内容 -->
<view v-else-if="item.type === 'file'" class="media-content">
<ImageVideoUpload
:enableImage="false"
:enableVideo="false"
:enableFile="true"
:fileList="item.value"
:disabled="true"
:readonly="true"
/>
</view>
</view>
</view>
</view>
<view v-else class="empty-content">
<text class="empty-text">暂无提交内容</text>
</view>
</view>
</view>
</view>
</view>
<!-- 右侧评价功能 -->
<view class="evaluation-section">
<!-- 成果评价 -->
<view class="rating-section">
<view class="section-header">
<text class="section-icon"></text>
<text class="section-title">成果评价</text>
</view>
<view class="rating-scale">
<view
v-for="score in ratingOptions"
:key="score.value"
class="rating-option"
:class="{ active: evaluationData.score === score.value, disabled: isReadOnly }"
@click="setRating(score.value)"
>
<view class="rating-circle">{{ score.value }}</view>
<text class="rating-label">{{ score.label }}</text>
</view>
</view>
</view>
<!-- 文字评价 -->
<view class="comment-section">
<text class="comment-label">文字评价</text>
<textarea
v-model="evaluationData.comment"
class="comment-input"
placeholder="请在此输入您的评价意见..."
:disabled="isSubmitting || isReadOnly"
></textarea>
</view>
<!-- AI智能评价 -->
<view class="ai-evaluation-section" v-if="!isReadOnly">
<view class="section-header">
<text class="section-icon">🤖</text>
<text class="section-title">AI智能评价</text>
<button
class="ai-button"
@click="getAIEvaluation"
:disabled="isSubmitting || aiLoading"
>
<text class="ai-text">{{ aiLoading ? 'AI分析中...' : '获取' }}</text>
</button>
</view>
<!-- AI评价结果 -->
<view v-if="aiEvaluation" class="ai-result">
<text class="ai-result-title">AI评价结果</text>
<text class="ai-result-content">{{ aiEvaluation }}</text>
</view>
</view>
</view>
</view>
<!-- 底部固定提交按钮 -->
<view class="bottom-submit" v-if="!isReadOnly">
<button
class="submit-button"
@click="submitEvaluation"
:disabled="isSubmitting || !evaluationData.score"
>
<text class="button-icon">📤</text>
<text class="button-text">{{ isSubmitting ? '提交评价中...' : '提交评价' }}</text>
</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from "vue";
import { onLoad as onPageLoad } from "@dcloudio/uni-app";
import { rwFindInfoByRwId } from "@/api/base/server";
import { rwzxExecutedInfoByRwIdAndJsApi } from "@/api/base/server";
import { updateEvaluationApi } from "@/api/base/rwzxApi";
import { useUserStore } from "@/store/modules/user";
import { ImageVideoUpload, type FileItem, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
//
interface TaskInfo {
id: string;
rwmc: string;
rwms: string;
rwjstime: string;
rwfzrxm: string;
rwfzr: string;
rwStatus: string;
rwlyId: string;
}
interface ExecutionInfo {
id: string;
rwId: string;
rwzxfzr: string;
rwzxfzrxm: string;
iszxwc: string;
rwzxzt: string;
rwzxtime: string;
rwzxnr: string;
createTime: string;
jsId?: string;
ispj?: string;
rwpjr?: string;
rwpjrxm?: string;
rwpjjf?: string;
rwpjms?: string;
rwpjtime?: string;
}
interface SubmittedContent {
label: string;
type: 'text' | 'image' | 'video' | 'file';
value: any;
}
//
const isLoading = ref(false);
const isSubmitting = ref(false);
const aiLoading = ref(false);
const isReadOnly = ref(false); //
const taskInfo = ref<TaskInfo>({
id: '',
rwmc: '',
rwms: '',
rwjstime: '',
rwfzrxm: '',
rwfzr: '',
rwStatus: '',
rwlyId: ''
});
const executionInfo = ref<ExecutionInfo>({
id: '',
rwId: '',
rwzxfzr: '',
rwzxfzrxm: '',
iszxwc: '',
rwzxzt: '',
rwzxtime: '',
rwzxnr: '',
createTime: ''
});
const submittedContent = ref<SubmittedContent[]>([]);
//
const evaluationData = reactive({
score: '',
comment: ''
});
const aiEvaluation = ref('');
//
const ratingOptions = [
{ value: '1', label: '很差' },
{ value: '2', label: '较差' },
{ value: '3', label: '一般' },
{ value: '4', label: '较好' },
{ value: '5', label: '优秀' }
];
const userStore = useUserStore();
const { getJs } = userStore;
//
onPageLoad(async (options: any) => {
console.log('评价页面接收到的参数:', options);
//
const storedParams = uni.getStorageSync('evaluationData');
if (storedParams) {
try {
taskInfo.value = storedParams.taskInfo;
executionInfo.value = storedParams.executionInfo;
isReadOnly.value = storedParams.isReadOnly || false; //
console.log('从全局存储解析参数成功:', {
taskInfo: taskInfo.value,
executionInfo: executionInfo.value,
isReadOnly: isReadOnly.value
});
//
uni.removeStorageSync('evaluationData');
await loadSubmittedContent();
//
if (isReadOnly.value) {
loadExistingEvaluation();
}
} catch (error) {
console.error('解析全局存储参数失败:', error);
uni.showToast({
title: '参数解析失败',
icon: 'error'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
} else {
console.error('缺少评价参数');
uni.showToast({
title: '缺少评价参数',
icon: 'error'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
});
//
const loadSubmittedContent = async () => {
isLoading.value = true;
try {
//
const taskResult = await rwFindInfoByRwId({ rwId: taskInfo.value.id });
const rwflx = taskResult.result.rwlxes;
//
const executionResult = await rwzxExecutedInfoByRwIdAndJsApi({
rwId: taskInfo.value.id,
jsId: executionInfo.value.rwzxfzr
});
if (executionResult && executionResult.result && executionResult.result.length) {
const rwzxqds = executionResult.result;
const content: SubmittedContent[] = [];
for (let i = 0; i < rwzxqds.length; i++) {
const record = rwzxqds[i];
const rwlxId = record.rwlxId;
const rwzxqdtx = record.rwzxqdtx;
//
const taskType = rwflx.find((item: any) => item.id === rwlxId);
if (taskType && rwzxqdtx) {
if (taskType.rwfl === "sctp") {
//
const urls = rwzxqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const images = urls.map((url: string, index: number) => ({
url: url.trim(),
name: names[index] || url.split('/').pop() || 'image.jpg'
}));
content.push({
label: taskType.rwbt,
type: 'image',
value: images
});
} else if (taskType.rwfl === "scsp") {
//
const urls = rwzxqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const videos = urls.map((url: string, index: number) => ({
url: url.trim(),
name: names[index] || url.split('/').pop() || 'video.mp4'
}));
content.push({
label: taskType.rwbt,
type: 'video',
value: videos
});
} else if (taskType.rwfl === "scwd") {
//
const urls = rwzxqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const files = urls.map((url: string, index: number) => ({
url: url.trim(),
name: names[index] || url.split('/').pop() || 'document'
}));
content.push({
label: taskType.rwbt,
type: 'file',
value: files
});
} else {
//
content.push({
label: taskType.rwbt,
type: 'text',
value: rwzxqdtx
});
}
}
}
submittedContent.value = content;
console.log('加载提交内容成功:', submittedContent.value);
}
} catch (error) {
console.error('加载提交内容失败:', error);
uni.showToast({
title: '加载内容失败',
icon: 'error'
});
} finally {
isLoading.value = false;
}
};
//
const loadExistingEvaluation = () => {
if (executionInfo.value.rwpjjf) {
evaluationData.score = executionInfo.value.rwpjjf;
}
if (executionInfo.value.rwpjms) {
evaluationData.comment = executionInfo.value.rwpjms;
}
console.log('加载已有评价信息:', {
score: evaluationData.score,
comment: evaluationData.comment
});
};
//
const setRating = (score: string) => {
if (isReadOnly.value) {
return; //
}
evaluationData.score = score;
};
// AI
const getAIEvaluation = async () => {
aiLoading.value = true;
try {
// TODO: AI
// AI
await new Promise(resolve => setTimeout(resolve, 2000));
aiEvaluation.value = `正在接入AI评价敬请期待...`;
} catch (error) {
console.error('获取AI评价失败:', error);
uni.showToast({
title: 'AI评价失败',
icon: 'error'
});
} finally {
aiLoading.value = false;
}
};
//
const submitEvaluation = async () => {
if (isSubmitting.value) {
return;
}
if (!evaluationData.score) {
uni.showToast({
title: '请选择评分',
icon: 'none'
});
return;
}
isSubmitting.value = true;
try {
//
console.log('教师信息:', getJs);
const evaluationParams = {
rwzxId: executionInfo.value.id,
rwpjr: getJs.id,
rwpjrxm: getJs.jsxm,
rwpjjf: evaluationData.score,
rwpjms: evaluationData.comment || ''
};
console.log('提交评价参数:', evaluationParams);
const result = await updateEvaluationApi(evaluationParams);
if (result && result.resultCode === 1) {
uni.showToast({
title: '评价提交成功',
icon: 'success'
});
//
uni.$emit('refreshTaskExecution');
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
throw new Error(result?.message || result?.msg || '提交失败');
}
} catch (error) {
console.error('提交评价失败:', error);
uni.showToast({
title: '提交失败,请重试',
icon: 'error'
});
} finally {
isSubmitting.value = false;
}
};
//
const formatDateTime = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
};
</script>
<style lang="scss" scoped>
.evaluation-page {
min-height: 100vh;
background-color: #f5f7fa;
padding: 16px;
box-sizing: border-box;
&:has(.bottom-submit) {
padding-bottom: 80px; /* 有提交按钮时留出空间 */
}
}
.loading-indicator {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.evaluation-content {
display: flex;
gap: 16px;
max-width: 1200px;
margin: 0 auto;
}
/* 左侧:作品预览和详情 */
.work-preview-section {
flex: 1;
background-color: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.work-preview {
margin-bottom: 24px;
.preview-title-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.preview-title-icon {
font-size: 18px;
}
.preview-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.preview-content {
min-height: 200px;
background-color: #fff;
border: none;
border-radius: 8px;
padding: 0;
}
.task-info {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #f0f0f0;
}
.info-item {
display: flex;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
&.inline {
display: inline-flex;
margin-right: 24px;
margin-bottom: 8px;
}
}
.info-label {
font-size: 14px;
color: #666;
min-width: 80px;
flex-shrink: 0;
}
.info-value {
font-size: 14px;
color: #333;
flex: 1;
}
.submitted-content {
.content-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 12px;
display: block;
}
}
.content-display {
width: 100%;
}
.content-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.content-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
display: block;
}
.content-value {
color: #333;
}
.text-content {
font-size: 14px;
line-height: 1.6;
word-break: break-word;
}
.media-content {
width: 100%;
}
.empty-content {
text-align: center;
color: #999;
}
.empty-text {
font-size: 14px;
}
}
/* 右侧:评价功能 */
.evaluation-section {
flex: 1;
background-color: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.section-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
.section-icon {
font-size: 18px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
flex: 1;
}
.ai-button {
padding: 6px 12px;
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(:disabled) {
background-color: #1976d2;
}
&:disabled {
background-color: #d9d9d9;
cursor: not-allowed;
opacity: 0.6;
}
}
.ai-text {
font-size: 12px;
}
}
.rating-section {
margin-bottom: 24px;
.rating-scale {
display: flex;
justify-content: space-between;
gap: 8px;
flex-wrap: nowrap;
}
.rating-option {
flex: 1;
text-align: center;
padding: 8px 4px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
min-width: 0;
&.active {
background-color: rgba(24, 144, 255, 0.1);
border-color: #1890ff;
}
&:hover:not(.disabled) {
background-color: rgba(0, 0, 0, 0.02);
}
&.disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
.rating-circle {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
color: #666;
margin: 0 auto 6px;
transition: all 0.3s ease;
}
.rating-option.active .rating-circle {
background-color: #1890ff;
color: #fff;
}
.rating-label {
font-size: 11px;
color: #666;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rating-option.active .rating-label {
color: #1890ff;
font-weight: 500;
}
}
.comment-section {
margin-bottom: 24px;
.comment-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
display: block;
}
.comment-input {
width: 100%;
min-height: 120px;
padding: 12px;
border: 1px solid #d9d9d9;
border-radius: 8px;
font-size: 14px;
line-height: 1.6;
resize: vertical;
box-sizing: border-box;
&:focus {
border-color: #1890ff;
outline: none;
}
&:disabled {
background-color: #f5f5f5;
color: #999;
}
}
}
.ai-evaluation-section {
margin-bottom: 24px;
.ai-result {
margin-top: 16px;
padding: 16px;
background-color: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #1890ff;
.ai-result-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
display: block;
}
.ai-result-content {
font-size: 14px;
color: #555;
line-height: 1.6;
word-break: break-word;
}
}
}
/* 底部固定提交按钮 */
.bottom-submit {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 16px;
border-top: 1px solid #e5e5e5;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
z-index: 1000;
.submit-button {
width: 100%;
height: 44px;
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(:disabled) {
background-color: #1976d2;
transform: translateY(-1px);
}
&:disabled {
background-color: #d9d9d9;
color: #999;
cursor: not-allowed;
opacity: 0.6;
}
}
.button-icon {
font-size: 16px;
}
.button-text {
font-size: 14px;
}
}
/* 提交遮罩层样式 */
.submit-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.submit-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 16px;
color: #333;
font-weight: 500;
}
/* 响应式设计 */
@media screen and (max-width: 768px) {
.evaluation-content {
flex-direction: column;
}
.rating-scale {
flex-wrap: nowrap;
}
.rating-option {
min-width: 0;
padding: 6px 2px;
}
.rating-circle {
width: 28px;
height: 28px;
font-size: 12px;
margin: 0 auto 4px;
}
.rating-label {
font-size: 10px;
}
}
</style>

View File

@ -1,6 +1,6 @@
<!-- src/pages/base/message/detail.vue -->
<!-- 任务执行提交页面 -->
<template>
<view class="message-detail-page">
<view class="rw-detail-page">
<!-- 提交遮罩层 -->
<view v-if="isSubmitting" class="submit-overlay">
<view class="submit-loading">
@ -10,20 +10,49 @@
</view>
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="detail-content">
<view class="detail-header">
<view class="title-tag-row">
<view class="detail-title">{{ rw.rwmc }}</view>
<view v-else class="content-wrapper">
<view class="p-15">
<!-- 第一部分任务要求 -->
<view class="rw-info-section">
<view class="section-title">任务要求</view>
<!-- 任务名称 -->
<view class="info-item">
<text class="label">任务名称</text>
<text class="value title-bold">{{ rw.rwmc }}</text>
</view>
<!-- 任务描述 -->
<view v-if="rw.rwms" class="info-item">
<text class="label">任务描述</text>
<text class="value">{{ rw.rwms }}</text>
</view>
<!-- 任务时间 -->
<view v-if="rw.rwkstime" class="info-item">
<text class="label">任务时间</text>
<text class="value">{{ rw.rwkstime }}</text>
</view>
<!-- 附件预览 -->
<BasicFilePreview
v-if="rw.rwfj && rw.fjmx"
:file-url="rw.rwfj"
:file-name="rw.fjmx"
:file-format="getFileFormat(rw.fjmx)"
class="mt-15"
/>
</view>
<view class="detail-meta">
<text>{{ rw.rwkstime }}</text>
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
<!-- 第二部分任务执行 -->
<view class="rw-execute-section">
<view class="section-title">任务执行</view>
<view class="execute-form">
<BasicForm :schema="schema" v-model="formData">
</BasicForm>
</view>
</view>
</view>
<view class="detail-body">
<BasicForm :schema="schema" v-model="formData">
</BasicForm>
</view>
</view>
<!-- 提交按钮 - 固定在底部 -->
@ -32,7 +61,6 @@
{{ isSubmitting ? '提交中...' : '提交' }}
</button>
</view>
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
</view>
</template>
@ -45,6 +73,7 @@ import {navigateBack, showToast} from "@/utils/uniapp";
import {useUserStore} from "@/store/modules/user";
import { ImageVideoUpload, type FileItem, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
import { attachmentUpload } from "@/api/system/upload";
import BasicFilePreview from "@/components/BasicFile/preview.vue";
interface MessageDetail {
id: string; // Assuming an ID is passed or can be derived
@ -54,9 +83,23 @@ interface MessageDetail {
timeAgo: string;
tagText: string;
tagType: string;
likes?: number;
comments?: number;
// Add other fields as necessary
}
interface RwInfo {
id?: string;
rwmc?: string; //
rwms?: string; //
rwkstime?: string; //
rwfj?: string; // URL
fjmx?: string; //
rwlxes?: any[]; //
rwzxqds?: any[]; //
[key: string]: any; //
}
const formData: any = ref({})
const messageId = ref<string>('');
const jsId = ref<string>(''); // ID
@ -78,7 +121,7 @@ const messageDetail = ref<MessageDetail | null>({
});
const isLoading = ref(false);
const rwflx: any = ref([])
const rw = ref({})
const rw = ref<RwInfo>({})
const schema = ref<FormsSchema[]>([])
const {getUser} = useUserStore()
@ -209,6 +252,13 @@ async function performSubmit() {
}
}
//
const getFileFormat = (fileName: string) => {
if (!fileName) return '';
const parts = fileName.split('.');
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
};
const rwzxqds = ref([])
onLoad(async (options) => {
console.log('页面加载参数:', options);
@ -536,98 +586,95 @@ onLoad(async (options) => {
</script>
<style scoped lang="scss">
.message-detail-page {
.rw-detail-page {
background-color: #f4f5f7;
min-height: 100vh;
padding: 15px;
padding-bottom: 80px; //
box-sizing: border-box;
}
.loading-indicator,
.empty-state {
.loading-indicator {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.detail-content {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.content-wrapper {
.p-15 {
padding: 15px;
}
.mt-15 {
margin-top: 15px;
}
}
.detail-header {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 15px;
//
.rw-info-section {
margin-bottom: 20px;
.title-tag-row {
display: flex;
justify-content: center; //
align-items: flex-start; // Align items to the top
margin-bottom: 10px;
}
.detail-title {
font-size: 18px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
line-height: 1.4;
text-align: center; //
white-space: normal; //
word-break: break-word; //
width: 100%; // 使
display: block; //
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.tag { // Reuse tag styles from index page if possible, or define here
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
color: #ffffff;
white-space: nowrap;
flex-shrink: 0; // Prevent tag from shrinking
&.notice {
background-color: #447ade;
.info-item {
display: flex;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
&.task {
background-color: #19be6b;
.label {
width: 80px;
color: #666;
font-size: 14px;
flex-shrink: 0;
}
&.approval {
background-color: #ff9f0a;
}
&.submit {
background-color: #8e8e93;
}
}
.detail-meta {
font-size: 12px;
color: #999;
text {
margin-right: 15px;
.value {
flex: 1;
color: #333;
font-size: 14px;
word-break: break-word;
&.title-bold {
font-weight: bold;
font-size: 16px;
}
}
}
}
.detail-body {
margin-bottom: 20px; //
.detail-desc {
font-size: 15px;
color: #555;
line-height: 1.7;
word-break: break-word;
//
.rw-execute-section {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.execute-form {
padding-top: 5px; //
}
}
@ -644,18 +691,18 @@ onLoad(async (options) => {
border-top: 1px solid #eee;
.action-button {
width: 100%; // Make button full width
height: 44px; // Standard button height
line-height: 44px; // Match height for vertical centering
font-size: 16px; // Slightly larger font
font-weight: 500; // Medium weight
border-radius: 8px; // Consistent border radius
background-color: #409eff; //
color: #ffffff; //
border: none; //
width: 100%;
height: 44px;
line-height: 44px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
background-color: #409eff;
color: #ffffff;
border: none;
&:active:not(:disabled) {
background-color: #3a8ee6; //
background-color: #3a8ee6;
transform: translateY(1px);
}
@ -668,95 +715,6 @@ onLoad(async (options) => {
}
}
// textFiled
:deep(.uni-input),
:deep(.uni-textarea) {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
// BasicForm
:deep(.basic-form) {
.uni-input,
.uni-textarea {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
}
// input textarea
:deep(input),
:deep(textarea) {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
// BasicForm label
:deep(.basic-form) {
.form-label,
.label,
.uni-form-item__label {
width: 100% !important;
white-space: normal !important;
word-break: break-word !important;
font-size: 15px !important;
font-weight: bold !important;
color: #333 !important;
text-align: left !important;
display: block !important;
line-height: 1.4 !important;
max-width: 100% !important;
overflow-wrap: break-word !important;
}
}
//
:deep(.basic-form) {
.form-item {
.form-label {
white-space: pre-wrap !important;
word-break: keep-all !important;
overflow-wrap: break-word !important;
}
}
}
// - FormsItem
:deep(.forms-item-row) {
.flex-row {
view:last-child {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
max-width: 100% !important;
display: block !important;
line-height: 1.4 !important;
}
}
}
// view
:deep(view) {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
}
//
.submit-overlay {
position: fixed;
@ -803,6 +761,52 @@ onLoad(async (options) => {
font-weight: 500;
}
//
//
:deep(.uni-input),
:deep(.uni-textarea) {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
:deep(input),
:deep(textarea) {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
// BasicForm label
:deep(.basic-form) {
.form-label,
.label,
.uni-form-item__label {
width: 100% !important;
white-space: normal !important;
word-break: break-word !important;
font-size: 15px !important;
font-weight: bold !important;
color: #333 !important;
text-align: left !important;
display: block !important;
line-height: 1.4 !important;
max-width: 100% !important;
overflow-wrap: break-word !important;
}
}
// view
:deep(view) {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
}
</style>

View File

@ -1,6 +1,6 @@
<!-- src/pages/base/message/detail.vue -->
<!-- 任务执行提交页面 -->
<template>
<view class="message-detail-page">
<view class="rw-detail-page">
<!-- 提交遮罩层 -->
<view v-if="isSubmitting" class="submit-overlay">
<view class="submit-loading">
@ -10,20 +10,49 @@
</view>
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="detail-content">
<view class="detail-header">
<view class="title-tag-row">
<view class="detail-title">{{ rw.rwmc }}</view>
<view v-else class="content-wrapper">
<view class="p-15">
<!-- 第一部分任务要求 -->
<view class="rw-info-section">
<view class="section-title">任务要求</view>
<!-- 任务名称 -->
<view class="info-item">
<text class="label">任务名称</text>
<text class="value title-bold">{{ rw.rwmc }}</text>
</view>
<!-- 任务描述 -->
<view v-if="rw.rwms" class="info-item">
<text class="label">任务描述</text>
<text class="value">{{ rw.rwms }}</text>
</view>
<!-- 任务时间 -->
<view v-if="rw.rwkstime" class="info-item">
<text class="label">任务时间</text>
<text class="value">{{ rw.rwkstime }}</text>
</view>
<!-- 附件预览 -->
<BasicFilePreview
v-if="rw.rwfj && rw.fjmx"
:file-url="rw.rwfj"
:file-name="rw.fjmx"
:file-format="getFileFormat(rw.fjmx)"
class="mt-15"
/>
</view>
<view class="detail-meta">
<text>{{ rw.rwkstime }}</text>
<!-- <text>{{ messageDetail.timeAgo }}</text>-->
<!-- 第二部分任务执行 -->
<view class="rw-execute-section">
<view class="section-title">任务执行</view>
<view class="execute-form">
<BasicForm :schema="schema" v-model="formData">
</BasicForm>
</view>
</view>
</view>
<view class="detail-body">
<BasicForm :schema="schema" v-model="formData">
</BasicForm>
</view>
</view>
<!-- 提交按钮 - 固定在底部 -->
@ -32,7 +61,6 @@
{{ isSubmitting ? '提交中...' : '提交' }}
</button>
</view>
<!-- <view v-else class="empty-state">消息详情未找到</view>-->
</view>
</template>
@ -45,6 +73,7 @@ import {navigateBack, showToast} from "@/utils/uniapp";
import {useUserStore} from "@/store/modules/user";
import { ImageVideoUpload, type FileItem, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
import { attachmentUpload } from "@/api/system/upload";
import BasicFilePreview from "@/components/BasicFile/preview.vue";
interface MessageDetail {
id: string; // Assuming an ID is passed or can be derived
@ -54,9 +83,23 @@ interface MessageDetail {
timeAgo: string;
tagText: string;
tagType: string;
likes?: number;
comments?: number;
// Add other fields as necessary
}
interface RwInfo {
id?: string;
rwmc?: string; //
rwms?: string; //
rwkstime?: string; //
rwfj?: string; // URL
fjmx?: string; //
rwlxes?: any[]; //
rwzxqds?: any[]; //
[key: string]: any; //
}
const formData: any = ref({})
const messageId = ref<string>('');
const jsId = ref<string>(''); // ID
@ -78,7 +121,7 @@ const messageDetail = ref<MessageDetail | null>({
});
const isLoading = ref(false);
const rwflx: any = ref([])
const rw = ref({})
const rw = ref<RwInfo>({})
const schema = ref<FormsSchema[]>([])
const {getUser} = useUserStore()
@ -209,6 +252,13 @@ async function performSubmit() {
}
}
//
const getFileFormat = (fileName: string) => {
if (!fileName) return '';
const parts = fileName.split('.');
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
};
const rwzxqds = ref([])
onLoad(async (options) => {
console.log('页面加载参数:', options);
@ -536,99 +586,95 @@ onLoad(async (options) => {
</script>
<style scoped lang="scss">
.message-detail-page {
.rw-detail-page {
background-color: #f4f5f7;
min-height: 100vh;
padding: 15px;
padding-bottom: 80px; //
box-sizing: border-box;
}
.loading-indicator,
.empty-state {
.loading-indicator {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.detail-content {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.content-wrapper {
.p-15 {
padding: 15px;
}
.mt-15 {
margin-top: 15px;
}
}
.detail-header {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 15px;
//
.rw-info-section {
margin-bottom: 20px;
.title-tag-row {
display: flex;
justify-content: center; //
align-items: flex-start; // Align items to the top
margin-bottom: 10px;
}
.detail-title {
font-size: 18px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
line-height: 1.4;
text-align: center; //
white-space: nowrap; //
overflow: hidden; //
text-overflow: ellipsis; //
width: 100%; // 使
display: block; //
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.tag { // Reuse tag styles from index page if possible, or define here
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
color: #ffffff;
white-space: nowrap;
flex-shrink: 0; // Prevent tag from shrinking
&.notice {
background-color: #447ade;
.info-item {
display: flex;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
&.task {
background-color: #19be6b;
.label {
width: 80px;
color: #666;
font-size: 14px;
flex-shrink: 0;
}
&.approval {
background-color: #ff9f0a;
}
&.submit {
background-color: #8e8e93;
}
}
.detail-meta {
font-size: 12px;
color: #999;
text {
margin-right: 15px;
.value {
flex: 1;
color: #333;
font-size: 14px;
word-break: break-word;
&.title-bold {
font-weight: bold;
font-size: 16px;
}
}
}
}
.detail-body {
margin-bottom: 20px; //
.detail-desc {
font-size: 15px;
color: #555;
line-height: 1.7;
word-break: break-word;
//
.rw-execute-section {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #007aff;
padding-bottom: 5px;
}
.execute-form {
padding-top: 5px;
}
}
@ -645,18 +691,18 @@ onLoad(async (options) => {
border-top: 1px solid #eee;
.action-button {
width: 100%; // Make button full width
height: 44px; // Standard button height
line-height: 44px; // Match height for vertical centering
font-size: 16px; // Slightly larger font
font-weight: 500; // Medium weight
border-radius: 8px; // Consistent border radius
background-color: #409eff; //
color: #ffffff; //
border: none; //
width: 100%;
height: 44px;
line-height: 44px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
background-color: #409eff;
color: #ffffff;
border: none;
&:active:not(:disabled) {
background-color: #3a8ee6; //
background-color: #3a8ee6;
transform: translateY(1px);
}
@ -669,7 +715,7 @@ onLoad(async (options) => {
}
}
// textFiled
//
:deep(.uni-input),
:deep(.uni-textarea) {
width: 100% !important;
@ -681,21 +727,6 @@ onLoad(async (options) => {
box-sizing: border-box !important;
}
// BasicForm
:deep(.basic-form) {
.uni-input,
.uni-textarea {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
}
// input textarea
:deep(input),
:deep(textarea) {
width: 100% !important;
@ -713,17 +744,26 @@ onLoad(async (options) => {
.label,
.uni-form-item__label {
width: 100% !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: normal !important;
word-break: break-word !important;
font-size: 15px !important;
font-weight: bold !important;
color: #333 !important;
text-align: left !important;
display: block !important;
line-height: 1.4 !important;
max-width: 100% !important;
overflow-wrap: break-word !important;
}
}
// view
:deep(view) {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
}
//
.submit-overlay {
position: fixed;
@ -769,7 +809,4 @@ onLoad(async (options) => {
color: #333;
font-weight: 500;
}
//
</style>