公文流转

This commit is contained in:
hebo 2025-09-06 21:46:10 +08:00
parent e97d71f230
commit d866410866
16 changed files with 1361 additions and 303 deletions

View File

@ -41,7 +41,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref, onMounted} from "vue"; import {ref, onMounted, onActivated} from "vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout"; import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { xxtsListApi } from "@/api/base/server"; import { xxtsListApi } from "@/api/base/server";
import { getTimeAgo } from "@/utils/dateUtils"; import { getTimeAgo } from "@/utils/dateUtils";
@ -80,7 +80,7 @@ const fetchDbLxMap = async () => {
const [register, {reload, setParam}] = useLayout({ const [register, {reload, setParam}] = useLayout({
api: xxtsListApi, api: xxtsListApi,
componentProps: { componentProps: {
auto: false auto: true // true
}, },
}); });
@ -107,6 +107,12 @@ onMounted(() => {
fetchListData(currentTab.value); fetchListData(currentTab.value);
}); });
//
onActivated(() => {
console.log('页面激活,重新加载数据');
fetchListData(currentTab.value);
});
const goToDetail = (data: any) => { const goToDetail = (data: any) => {
if (data && data.id) { if (data && data.id) {
setDb(data); setDb(data);

View File

@ -29,33 +29,106 @@
<!-- 文件信息 --> <!-- 文件信息 -->
<view class="file-section"> <view class="file-section">
<view class="section-title">附件</view> <view class="section-title">附件</view>
<view class="file-list"> <view class="file-list" v-if="hasAttachments">
<!-- 处理单个附件从gwInfo直接获取 -->
<view
v-if="gwInfo.fileUrl"
class="file-item"
@click="previewSingleFile"
>
<view class="file-icon">
<text v-if="isImage(gwInfo.fileFormat || '')">🖼</text>
<text v-else-if="isVideo(gwInfo.fileFormat || '')">🎥</text>
<text v-else-if="canPreview(gwInfo.fileFormat || '')">📄</text>
<text v-else>📎</text>
</view>
<view class="file-info">
<text class="file-name">{{ gwInfo.fileName || '未知文件' }}</text>
<text class="file-type">{{ (gwInfo.fileFormat || 'unknown').toUpperCase() }}</text>
</view>
<view class="file-actions">
<u-button
v-if="canPreview(gwInfo.fileFormat || '') && !isVideo(gwInfo.fileFormat || '')"
text="预览"
size="mini"
type="primary"
@click.stop="previewSingleFile"
/>
<u-button
v-if="isVideo(gwInfo.fileFormat || '')"
text="播放"
size="mini"
type="success"
@click.stop="previewSingleFile"
/>
<u-button
v-if="isImage(gwInfo.fileFormat || '')"
text="查看"
size="mini"
type="warning"
@click.stop="previewSingleFile"
/>
<u-button
text="下载"
size="mini"
type="info"
@click.stop="downloadSingleFile"
/>
</view>
</view>
<!-- 处理多个附件从files数组获取 -->
<view <view
v-for="(file, index) in gwInfo.files" v-for="(file, index) in gwInfo.files"
:key="index" :key="index"
class="file-item" class="file-item"
@click="previewFile(file)" @click="previewFile(file)"
> >
<view class="file-icon">📎</view> <view class="file-icon">
<text v-if="isImage(getFileSuffix(file))">🖼</text>
<text v-else-if="isVideo(getFileSuffix(file))">🎥</text>
<text v-else-if="canPreview(getFileSuffix(file))">📄</text>
<text v-else>📎</text>
</view>
<view class="file-info"> <view class="file-info">
<text class="file-name">{{ file.name }}</text> <text class="file-name">{{ getFileName(file) }}</text>
<text class="file-size">{{ formatFileSize(file.size) }}</text> <text class="file-size">{{ formatFileSize(file.size) }}</text>
<text class="file-type">{{ getFileSuffix(file).toUpperCase() }}</text>
</view> </view>
<view class="file-actions"> <view class="file-actions">
<u-button <u-button
v-if="canPreview(getFileSuffix(file)) && !isVideo(getFileSuffix(file))"
text="预览" text="预览"
size="mini" size="mini"
type="primary" type="primary"
@click.stop="previewFile(file)" @click.stop="previewFile(file)"
/> />
<u-button
v-if="isVideo(getFileSuffix(file))"
text="播放"
size="mini"
type="success"
@click.stop="previewFile(file)"
/>
<u-button
v-if="isImage(getFileSuffix(file))"
text="查看"
size="mini"
type="warning"
@click.stop="previewFile(file)"
/>
<u-button <u-button
text="下载" text="下载"
size="mini" size="mini"
type="info"
@click.stop="downloadFile(file)" @click.stop="downloadFile(file)"
/> />
</view> </view>
</view> </view>
</view> </view>
<view v-else class="no-files">
<text>暂无附件</text>
</view>
</view> </view>
<!-- 当前处理人 --> <!-- 当前处理人 -->
@ -95,7 +168,7 @@
<view class="section-title">抄送人</view> <view class="section-title">抄送人</view>
<view class="cc-list"> <view class="cc-list">
<view <view
v-for="ccUser in ccUsers" v-for="ccUser in displayedCcUsers"
:key="ccUser.id" :key="ccUser.id"
class="cc-item" class="cc-item"
> >
@ -108,6 +181,17 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 更多按钮 -->
<view
v-if="ccUsers.length > 2"
class="more-button"
@click="toggleCcExpanded"
>
<text class="more-text">
{{ ccExpanded ? '收起' : `更多(${ccUsers.length - 2})` }}
</text>
<text class="more-icon" :class="{ expanded: ccExpanded }"></text>
</view>
</view> </view>
<!-- 操作记录 --> <!-- 操作记录 -->
@ -115,7 +199,7 @@
<view class="section-title">操作记录</view> <view class="section-title">操作记录</view>
<view class="log-list"> <view class="log-list">
<view <view
v-for="log in operationLogs" v-for="log in displayedOperationLogs"
:key="log.id" :key="log.id"
class="log-item" class="log-item"
> >
@ -136,12 +220,25 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 更多按钮 -->
<view
v-if="operationLogs.length > 2"
class="more-button"
@click="toggleLogExpanded"
>
<text class="more-text">
{{ logExpanded ? '收起' : `更多(${operationLogs.length - 2})` }}
</text>
<text class="more-icon" :class="{ expanded: logExpanded }"></text>
</view>
</view> </view>
</view> </view>
<!-- 底部固定按钮 --> <!-- 底部固定按钮 -->
<view class="bottom-actions"> <view class="bottom-actions" v-if="canCurrentUserOperate">
<!-- 驳回按钮暂时隐藏 --> <!-- 驳回按钮暂时隐藏 -->
<!-- <u-button <!-- <u-button
text="驳回" text="驳回"
@ -194,13 +291,23 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from "vue"; import { ref, onMounted, computed } from "vue";
import BasicLayout from "@/components/BasicLayout/Layout.vue"; import BasicLayout from "@/components/BasicLayout/Layout.vue";
import { navigateTo } from "@/utils/uniapp"; import { navigateTo } from "@/utils/uniapp";
import { getGwFlowByIdApi, gwApproveApi } from "@/api/routine/gw"; import { getGwFlowByIdApi, gwApproveApi } from "@/api/routine/gw";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useUserStore } from "@/store/modules/user"; import { useUserStore } from "@/store/modules/user";
import { imagUrl } from "@/utils";
import {
isVideo,
isImage,
canPreview,
previewFile as previewFileUtil,
previewVideo as previewVideoUtil,
previewImage as previewImageUtil,
downloadFile as downloadFileUtil
} from "@/utils/filePreview";
// //
interface GwInfo { interface GwInfo {
@ -211,7 +318,7 @@ interface GwInfo {
status: string; status: string;
createdBy: string; createdBy: string;
createdTime: Date; createdTime: Date;
files: FileInfo[]; files?: FileInfo[]; //
approvers?: Approver[]; approvers?: Approver[];
ccUsers?: CCUser[]; ccUsers?: CCUser[];
operationLogs?: OperationLog[]; operationLogs?: OperationLog[];
@ -219,12 +326,19 @@ interface GwInfo {
urgencyLevel: string; // urgencyLevel: string; //
tjrId: string; // tjrId: string; //
spRule?: string; // spRule?: string; //
//
fileUrl?: string; // URL
fileName?: string; //
fileFormat?: string; //
} }
interface FileInfo { interface FileInfo {
name: string; name: string;
size: number; size: number;
url: string; url: string;
resourName?: string; //
resourUrl?: string; // URL
resSuf?: string; //
} }
interface Approver { interface Approver {
@ -277,6 +391,62 @@ const ccUsers = ref<CCUser[]>([]);
const operationLogs = ref<OperationLog[]>([]); const operationLogs = ref<OperationLog[]>([]);
const currentLog = ref<OperationLog>({} as OperationLog); const currentLog = ref<OperationLog>({} as OperationLog);
//
const ccExpanded = ref(false);
//
const logExpanded = ref(false);
//
const hasAttachments = computed(() => {
return gwInfo.value.fileUrl || (gwInfo.value.files && gwInfo.value.files.length > 0);
});
//
const displayedCcUsers = computed(() => {
if (ccExpanded.value || ccUsers.value.length <= 2) {
return ccUsers.value;
}
return ccUsers.value.slice(0, 2);
});
//
const displayedOperationLogs = computed(() => {
if (logExpanded.value || operationLogs.value.length <= 2) {
return operationLogs.value;
}
return operationLogs.value.slice(0, 2);
});
// /
const canCurrentUserOperate = computed(() => {
const userStore = useUserStore();
const getUser = userStore.getUser;
const getJs = userStore.getJs;
const currentUserId = getJs?.id;
if (!currentUserId || !approvers.value || approvers.value.length === 0) {
return false;
}
//
const currentUserApprover = approvers.value.find(approver => {
return approver.userId === currentUserId || approver.id === currentUserId;
});
if (!currentUserApprover) {
return false;
}
// approvedrejected
const approveStatus = currentUserApprover.approveStatus;
if (approveStatus === 'approved' || approveStatus === 'rejected') {
return false;
}
return true;
});
// //
const getGwInfo = async () => { const getGwInfo = async () => {
@ -358,6 +528,16 @@ const showLogDetail = (log: OperationLog) => {
showLogDetailModal.value = true; showLogDetailModal.value = true;
}; };
//
const toggleCcExpanded = () => {
ccExpanded.value = !ccExpanded.value;
};
//
const toggleLogExpanded = () => {
logExpanded.value = !logExpanded.value;
};
// //
const handleReject = () => { const handleReject = () => {
uni.showModal({ uni.showModal({
@ -494,10 +674,20 @@ const approveGw = async () => {
uni.showToast({ uni.showToast({
title: "同意成功", title: "同意成功",
icon: "success", icon: "success",
duration: 1500
}); });
// //
await getGwInfo(); setTimeout(() => {
//
uni.navigateBack({
delta: 1,
success: () => {
// 线
uni.$emit('refreshGwList');
}
});
}, 1500);
} else { } else {
throw new Error(response.message || '同意失败'); throw new Error(response.message || '同意失败');
} }
@ -514,6 +704,23 @@ const approveGw = async () => {
} }
}; };
//
const getCurrentUserApproverStatus = () => {
const userStore = useUserStore();
const getJs = userStore.getJs;
const currentUserId = getJs?.id;
if (!currentUserId || !approvers.value || approvers.value.length === 0) {
return '未知';
}
const currentUserApprover = approvers.value.find(approver => {
return approver.userId === currentUserId || approver.id === currentUserId;
});
return currentUserApprover?.approveStatus || '未知';
};
// ID // ID
const getCurrentUserApproverId = (currentUserId: string) => { const getCurrentUserApproverId = (currentUserId: string) => {
console.log('=== getCurrentUserApproverId 函数调试 ==='); console.log('=== getCurrentUserApproverId 函数调试 ===');
@ -560,16 +767,221 @@ const getCurrentUserApproverId = (currentUserId: string) => {
return result; return result;
}; };
// gwInfo
const previewSingleFile = () => {
console.log("=== 预览单个附件 ===");
console.log("附件信息:", {
fileUrl: gwInfo.value.fileUrl,
fileName: gwInfo.value.fileName,
fileFormat: gwInfo.value.fileFormat
});
if (!gwInfo.value.fileUrl) {
console.error("没有找到附件URL");
return;
}
const fileUrl = imagUrl(gwInfo.value.fileUrl);
const fileName = gwInfo.value.fileName || '未知文件';
const fileFormat = gwInfo.value.fileFormat || '';
console.log("处理后的文件URL:", fileUrl);
console.log("文件名:", fileName);
console.log("文件格式:", fileFormat);
//
if (isVideo(fileFormat)) {
handlePreviewVideoSingle(fileUrl, fileName);
} else if (isImage(fileFormat)) {
handlePreviewImageSingle(fileUrl);
} else if (canPreview(fileFormat)) {
handlePreviewDocumentSingle(fileUrl, fileName, fileFormat);
} else {
//
downloadSingleFile();
}
};
//
const downloadSingleFile = () => {
if (!gwInfo.value.fileUrl) {
console.error("没有找到附件URL");
return;
}
const fileUrl = imagUrl(gwInfo.value.fileUrl);
const fileName = gwInfo.value.fileName || '未知文件';
const fileFormat = gwInfo.value.fileFormat || '';
const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName;
console.log("下载单个附件:", { fileUrl, fullFileName });
downloadFileUtil(fileUrl, fullFileName)
.then(() => {
console.log('文件下载成功');
uni.showToast({
title: '下载成功',
icon: 'success'
});
})
.catch((error: any) => {
console.error('文件下载失败:', error);
uni.showToast({
title: '下载失败',
icon: 'error'
});
});
};
// //
const previewFile = (file: FileInfo) => { const previewFile = (file: FileInfo) => {
// console.log("=== 处理文件预览 ===");
console.log("预览文件:", file); console.log("文件信息:", file);
// URL
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
console.log("处理后的文件URL:", fileUrl);
console.log("文件名:", fileName);
console.log("文件后缀:", fileSuf);
//
if (isVideo(fileSuf)) {
handlePreviewVideo(file);
} else if (isImage(fileSuf)) {
handlePreviewImage(file);
} else if (canPreview(fileSuf)) {
handlePreviewDocument(file);
} else {
//
downloadFileAction(file);
}
};
//
const handlePreviewDocument = (file: FileInfo) => {
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
const fileSuf = file.resSuf || file.name.split('.').pop() || '';
previewFileUtil(fileUrl, fileName, fileSuf)
.then(() => {
console.log('文档预览成功');
})
.catch((error: any) => {
console.error('文档预览失败:', error);
//
downloadFileAction(file);
});
};
//
const handlePreviewDocumentSingle = (fileUrl: string, fileName: string, fileFormat: string) => {
const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName;
previewFileUtil(fileUrl, fullFileName, fileFormat)
.then(() => {
console.log('单个附件文档预览成功');
})
.catch((error: any) => {
console.error('单个附件文档预览失败:', error);
//
downloadSingleFile();
});
};
//
const handlePreviewVideo = (file: FileInfo) => {
console.log('=== 处理视频预览 ===');
console.log('视频文件:', file);
const videoUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const videoName = file.resourName || file.name;
console.log('处理后的视频URL:', videoUrl);
previewVideoUtil(videoUrl, videoName)
.then(() => {
console.log('视频预览成功');
})
.catch((error: any) => {
console.error('视频预览失败:', error);
});
};
//
const handlePreviewVideoSingle = (videoUrl: string, videoName: string) => {
console.log('=== 处理单个附件视频预览 ===');
console.log('视频URL:', videoUrl);
console.log('视频名称:', videoName);
previewVideoUtil(videoUrl, videoName)
.then(() => {
console.log('单个附件视频预览成功');
})
.catch((error: any) => {
console.error('单个附件视频预览失败:', error);
});
};
//
const handlePreviewImage = (file: FileInfo) => {
const imageUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
previewImageUtil(imageUrl)
.then(() => {
console.log('图片预览成功');
})
.catch((error: any) => {
console.error('图片预览失败:', error);
});
};
//
const handlePreviewImageSingle = (imageUrl: string) => {
console.log('=== 处理单个附件图片预览 ===');
console.log('图片URL:', imageUrl);
previewImageUtil(imageUrl)
.then(() => {
console.log('单个附件图片预览成功');
})
.catch((error: any) => {
console.error('单个附件图片预览失败:', error);
});
}; };
// //
const downloadFile = (file: FileInfo) => { const downloadFile = (file: FileInfo) => {
// downloadFileAction(file);
};
//
const downloadFileAction = (file: FileInfo) => {
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : file.url;
const fileName = file.resourName ? `${file.resourName}.${file.resSuf}` : file.name;
console.log("下载文件:", file); console.log("下载文件:", file);
console.log("下载URL:", fileUrl);
console.log("文件名:", fileName);
downloadFileUtil(fileUrl, fileName)
.then(() => {
console.log('文件下载成功');
uni.showToast({
title: '下载成功',
icon: 'success'
});
})
.catch((error: any) => {
console.error('文件下载失败:', error);
uni.showToast({
title: '下载失败',
icon: 'error'
});
});
}; };
// //
@ -579,11 +991,42 @@ const formatTime = (time: any) => {
// //
const formatFileSize = (size: any) => { const formatFileSize = (size: any) => {
if (!size) return "0B";
if (size < 1024) return size + "B"; if (size < 1024) return size + "B";
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + "KB"; if (size < 1024 * 1024) return (size / 1024).toFixed(2) + "KB";
return (size / (1024 * 1024)).toFixed(2) + "MB"; return (size / (1024 * 1024)).toFixed(2) + "MB";
}; };
//
const getFileName = (file: FileInfo) => {
if (file.resourName) {
return file.resourName;
}
if (file.name) {
// name
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(0, lastDotIndex);
}
return file.name;
}
return '未知文件';
};
//
const getFileSuffix = (file: FileInfo) => {
if (file.resSuf) {
return file.resSuf;
}
if (file.name) {
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(lastDotIndex + 1);
}
}
return 'unknown';
};
// //
const getStatusClass = (status: any) => { const getStatusClass = (status: any) => {
const statusMap: Record<string, string> = { const statusMap: Record<string, string> = {
@ -830,6 +1273,10 @@ onMounted(() => {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
.log-section {
margin-bottom: 40px; //
}
.section-title { .section-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
@ -885,14 +1332,23 @@ onMounted(() => {
.file-item { .file-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px; padding: 12px;
border: 1px solid #eee; border: 1px solid #eee;
border-radius: 4px; border-radius: 8px;
margin-bottom: 10px; margin-bottom: 10px;
background: #fafafa;
transition: all 0.3s ease;
&:active {
transform: translateY(1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.file-icon { .file-icon {
margin-right: 10px; margin-right: 12px;
font-size: 20px; font-size: 24px;
width: 32px;
text-align: center;
} }
.file-info { .file-info {
@ -901,21 +1357,49 @@ onMounted(() => {
.file-name { .file-name {
display: block; display: block;
font-weight: 500; font-weight: 500;
margin-bottom: 2px; margin-bottom: 4px;
color: #333;
font-size: 14px;
} }
.file-size { .file-size {
font-size: 12px; font-size: 12px;
color: #666; color: #666;
margin-right: 8px;
}
.file-type {
font-size: 11px;
color: #999;
background: #f0f0f0;
padding: 2px 6px;
border-radius: 4px;
} }
} }
.file-actions { .file-actions {
display: flex; display: flex;
gap: 5px; gap: 4px;
flex-wrap: wrap;
justify-content: flex-end;
.u-button {
min-width: 20px;
height: 26px;
font-size: 12px;
border-radius: 13px;
padding: 0 6px;
}
} }
} }
.no-files {
text-align: center;
padding: 40px 20px;
color: #999;
font-size: 14px;
}
.approver-item, .approver-item,
.cc-item { .cc-item {
display: flex; display: flex;
@ -995,6 +1479,41 @@ onMounted(() => {
} }
} }
.more-button {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
margin-top: 10px;
margin-bottom: 20px; //
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
&:active {
background: #e9ecef;
transform: translateY(1px);
}
.more-text {
font-size: 14px;
color: #007aff;
margin-right: 6px;
}
.more-icon {
font-size: 12px;
color: #007aff;
transition: transform 0.3s ease;
&.expanded {
transform: rotate(180deg);
}
}
}
.log-item { .log-item {
padding: 10px; padding: 10px;
border: 1px solid #eee; border: 1px solid #eee;

View File

@ -5,8 +5,15 @@
</view> </view>
<template #bottom> <template #bottom>
<view class="flex-row items-center pb-10 pt-5"> <view class="flex-row items-center pb-10 pt-5">
<u-button text="取消" class="mx-15" @click="handleCancel" /> <u-button text="取消" class="mx-15" :disabled="isTransferring" @click="handleCancel" />
<u-button text="确认转办" class="mx-15" type="primary" @click="handleTransfer" /> <u-button
:text="isTransferring ? '转办中...' : '确认转办'"
class="mx-15"
type="primary"
:disabled="isTransferring"
:loading="isTransferring"
@click="handleTransfer"
/>
</view> </view>
</template> </template>
</BasicLayout> </BasicLayout>
@ -32,6 +39,8 @@ const xxtsInfo = ref<any>(null);
const gwInfo = ref<any>(null); const gwInfo = ref<any>(null);
const approvers = ref<any[]>([]); const approvers = ref<any[]>([]);
const ccUsers = ref<any[]>([]); const ccUsers = ref<any[]>([]);
//
const isTransferring = ref(false);
// //
const [register, { getValue, setValue }] = useForm({ const [register, { getValue, setValue }] = useForm({
@ -69,6 +78,26 @@ const [register, { getValue, setValue }] = useForm({
disabled: true, disabled: true,
}, },
}, },
{
field: "isCc",
label: "是否抄送",
component: "BasicPicker",
defaultValue: "1",
required: true,
componentProps: {
placeholder: "请选择是否抄送",
range: [
{ label: "是", value: "1" },
{ label: "否", value: "0" }
],
rangeKey: "label",
savaKey: "value",
onChange: async (value: any) => {
//
setValue({ isCc: value });
}
},
},
{ {
field: "transferTo", field: "transferTo",
label: "转办人", label: "转办人",
@ -107,9 +136,9 @@ const [register, { getValue, setValue }] = useForm({
field: "transferReason", field: "transferReason",
label: "转办描述", label: "转办描述",
component: "BasicInput", component: "BasicInput",
required: true, required: false,
componentProps: { componentProps: {
placeholder: "请输入转办原因和描述", placeholder: "请输入转办原因和描述(可选)",
type: "textarea", type: "textarea",
rows: 5, rows: 5,
}, },
@ -226,6 +255,11 @@ const handleCancel = () => {
// //
const handleTransfer = async () => { const handleTransfer = async () => {
//
if (isTransferring.value) {
return;
}
try { try {
const value = await getValue(); const value = await getValue();
@ -234,6 +268,9 @@ const handleTransfer = async () => {
return; return;
} }
//
isTransferring.value = true;
// //
uni.showLoading({ uni.showLoading({
title: '正在转办...', title: '正在转办...',
@ -260,7 +297,8 @@ const handleTransfer = async () => {
// //
transferTo: value.transferTo, transferTo: value.transferTo,
ccTo: value.ccTo || [], // isCc: value.isCc, //
ccTo: value.isCc === "1" ? (value.ccTo || []) : [], //
spRule: gwInfo.value?.spRule, // gwInfospRule spRule: gwInfo.value?.spRule, // gwInfospRule
transferReason: value.transferReason, transferReason: value.transferReason,
transferTime: new Date().toISOString(), transferTime: new Date().toISOString(),
@ -268,6 +306,7 @@ const handleTransfer = async () => {
}; };
console.log('转办数据:', transferData); console.log('转办数据:', transferData);
console.log('是否抄送:', value.isCc);
console.log('转办人数据类型:', typeof value.transferTo); console.log('转办人数据类型:', typeof value.transferTo);
console.log('转办人数据内容:', JSON.stringify(value.transferTo)); console.log('转办人数据内容:', JSON.stringify(value.transferTo));
console.log('抄送人数据类型:', typeof value.ccTo); console.log('抄送人数据类型:', typeof value.ccTo);
@ -305,11 +344,23 @@ const handleTransfer = async () => {
title: "转办失败", title: "转办失败",
icon: "error", icon: "error",
}); });
} finally {
//
isTransferring.value = false;
} }
}; };
// //
const validateForm = (value: any) => { const validateForm = (value: any) => {
//
if (!value.isCc) {
uni.showToast({
title: "请选择是否抄送",
icon: "error",
});
return false;
}
// //
if (!value.transferTo || value.transferTo.length === 0) { if (!value.transferTo || value.transferTo.length === 0) {
uni.showToast({ uni.showToast({
@ -319,14 +370,6 @@ const validateForm = (value: any) => {
return false; return false;
} }
//
if (!value.transferReason || value.transferReason.trim() === "") {
uni.showToast({
title: "请输入转办描述",
icon: "error",
});
return false;
}
// //
if (!xxtsInfo.value?.id) { if (!xxtsInfo.value?.id) {
@ -353,8 +396,6 @@ const validateForm = (value: any) => {
return false; return false;
} }
//
return true; return true;
}; };
</script> </script>

View File

@ -10,7 +10,7 @@
<!-- 搜索框 --> <!-- 搜索框 -->
<view class="search-item"> <view class="search-item">
<BasicSearch <BasicSearch
placeholder="搜索公文标题或编号" placeholder="搜索公文标题、编号或类型"
@search="handleSearch" @search="handleSearch"
class="search-input" class="search-input"
/> />
@ -46,24 +46,29 @@
<view class="info-item"> <view class="info-item">
<text class="info-label">类型</text> <text class="info-label">类型</text>
<text class="info-value">{{ data.docType }}</text> <text class="info-value">{{ data.docType }}</text>
<text class="info-label" style="margin-left: 20px;">提交时间</text>
<text class="info-value">{{ formatDate(data.tjrtime) }}</text>
</view> </view>
<view class="info-item"> </view>
<text class="info-label">紧急程度</text>
<text class="info-value urgency-tag" :class="getUrgencyClass(data.urgencyLevel)"> <!-- 附件列表 -->
{{ getUrgencyText(data.urgencyLevel) }} <view class="attachments-section" v-if="hasAttachments(data)">
</text> <view class="attachments-list">
</view> <!-- 统一处理所有附件 -->
<view class="info-item"> <view
<text class="info-label">审批进度</text> v-for="(file, index) in parseFileList(data)"
<text class="info-value">{{ getApproverProgress(data) }}</text> :key="index"
</view> class="attachment-item"
<view class="info-item"> @click="previewAttachmentFile(file)"
<text class="info-label">提交人</text> >
<text class="info-value">{{ data.tjrxm || '未知' }}</text> <view class="attachment-icon">
</view> <text v-if="isImage(getFileSuffix(file))">🖼</text>
<view class="info-item"> <text v-else-if="isVideo(getFileSuffix(file))">🎥</text>
<text class="info-label">提交时间</text> <text v-else-if="canPreview(getFileSuffix(file))">📄</text>
<text class="info-value">{{ formatTime(data.tjrtime || data.createdTime) }}</text> <text v-else>📎</text>
</view>
<text class="attachment-name">{{ getFileName(file) }}</text>
</view>
</view> </view>
</view> </view>
</view> </view>
@ -71,31 +76,19 @@
<view class="card-footer"> <view class="card-footer">
<view class="footer-actions"> <view class="footer-actions">
<u-button <u-button
text="详情" :text="getButtonText(data)"
size="mini" size="mini"
type="primary" type="primary"
:class="getButtonClass(data)"
@click="goToDetail(data)" @click="goToDetail(data)"
/> />
<u-button
v-if="data.gwStatus === 'B'"
text="编辑"
size="mini"
@click="editGw(data)"
/>
<u-button
v-if="data.gwStatus === 'B'"
text="删除"
size="mini"
type="error"
@click="deleteGw(data)"
/>
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<!-- 新建公文按钮放在bottom插槽中 --> <!-- 新建公文按钮已隐藏 -->
<template #bottom> <!-- <template #bottom>
<view class="flex-row items-center pb-10 pt-5"> <view class="flex-row items-center pb-10 pt-5">
<u-button <u-button
text="新建公文" text="新建公文"
@ -104,35 +97,73 @@
@click="createNewGw" @click="createNewGw"
/> />
</view> </view>
</template> </template> -->
</BasicListLayout> </BasicListLayout>
</view> </view>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue"; import { ref, computed, watch, onMounted, onUnmounted } from "vue";
import { onShow } from "@dcloudio/uni-app"; import { onShow } from "@dcloudio/uni-app";
import { navigateTo } from "@/utils/uniapp"; import { navigateTo } from "@/utils/uniapp";
import BasicSearch from "@/components/BasicSearch/Search.vue"; import BasicSearch from "@/components/BasicSearch/Search.vue";
import BasicLayout from "@/components/BasicLayout/Layout.vue"; import BasicLayout from "@/components/BasicLayout/Layout.vue";
import BasicListLayout from "@/components/BasicListLayout/ListLayout.vue"; import BasicListLayout from "@/components/BasicListLayout/ListLayout.vue";
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout"; import { useLayout } from "@/components/BasicListLayout/hooks/useLayout";
import { gwFindPageApi, gwLogicDeleteApi } from "@/api/routine/gw"; import { gwFindPageApi } from "@/api/routine/gw";
import { GwStatus, UrgencyLevel, ApproverStatus } from "@/types/gw";
import type { GwInfo, GwListItem } from "@/types/gw";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { imagUrl } from "@/utils";
import { useUserStore } from "@/store/modules/user";
import {
isVideo,
isImage,
canPreview,
previewFile as previewFileUtil,
previewVideo as previewVideoUtil,
previewImage as previewImageUtil,
downloadFile as downloadFileUtil
} from "@/utils/filePreview";
//
interface FileInfo {
name: string;
size: number;
url: string;
resourName?: string; //
resourUrl?: string; // URL
resSuf?: string; //
resourSuf?: string; //
}
//
interface GwListItem {
id: string;
title: string;
docType: string;
gwStatus: string;
fileUrl?: string;
fileName?: string;
fileFormat?: string;
files?: FileInfo[];
spZbqd?: string; // ID
tjrtime?: string; //
[key: string]: any;
}
// //
const filterTabs = [ const filterTabs = [
{ key: "all", label: "全部" }, { key: "all", label: "全部" },
{ key: "A", label: "已提交" }, { key: "C", label: "审批中" },
{ key: "B", label: "草稿" }, { key: "D", label: "已完结" },
]; ];
const activeTab = ref("all"); const activeTab = ref("all");
const searchKeyword = ref(""); const searchKeyword = ref("");
// store
const userStore = useUserStore();
// 使 BasicListLayout // 使 BasicListLayout
const [register, { reload, setParam }] = useLayout({ const [register, { reload, setParam }] = useLayout({
api: gwFindPageApi, api: gwFindPageApi,
@ -141,6 +172,7 @@ const [register, { reload, setParam }] = useLayout({
}, },
param: { param: {
title: "", title: "",
docType: "",
gwStatus: "", gwStatus: "",
}, },
}); });
@ -148,21 +180,28 @@ const [register, { reload, setParam }] = useLayout({
// //
const dataList = ref<GwListItem[]>([]); const dataList = ref<GwListItem[]>([]);
// ID
const getCurrentTeacherId = () => {
const jsData = userStore.getJs;
return jsData?.id || null;
};
// //
const filteredGwList = computed(() => { const filteredGwList = computed(() => {
let list = dataList.value; let list = dataList.value;
// //
if (activeTab.value !== "all") { if (activeTab.value !== "all") {
list = list.filter(item => item.gwStatus === activeTab.value); list = list.filter((item: GwListItem) => item.gwStatus === activeTab.value);
} }
// //
if (searchKeyword.value) { if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase(); const keyword = searchKeyword.value.toLowerCase();
list = list.filter(item => list = list.filter((item: GwListItem) =>
item.title.toLowerCase().includes(keyword) || item.title.toLowerCase().includes(keyword) ||
(item.gwNo && item.gwNo.toLowerCase().includes(keyword)) (item.gwNo && item.gwNo.toLowerCase().includes(keyword)) ||
(item.docType && item.docType.toLowerCase().includes(keyword))
); );
} }
@ -181,61 +220,20 @@ const switchTab = (tabKey: string) => {
// //
const handleSearch = (keyword: string) => { const handleSearch = (keyword: string) => {
searchKeyword.value = keyword; searchKeyword.value = keyword;
// //
setParam({ title: keyword }); setParam({
title: keyword,
docType: keyword //
});
reload(); reload();
}; };
// //
const goToDetail = (item: GwListItem) => { const goToDetail = (item: GwListItem) => {
navigateTo(`/pages/view/routine/gwlz/gwDetail?id=${item.id}`); navigateTo(`/pages/view/routine/gwlz/gwFlow?id=${item.id}`);
}; };
// //
const editGw = (item: GwListItem) => {
console.log('编辑公文ID:', item.id, '标题:', item.title);
const url = `/pages/view/routine/gwlz/gwAdd?id=${item.id}&mode=edit`;
//
uni.setStorageSync('gwEditMode', 'edit');
uni.setStorageSync('gwEditId', item.id);
console.log('存储编辑参数到本地存储:', { mode: 'edit', id: item.id });
console.log('跳转到编辑页面:', url);
navigateTo(url);
};
//
const deleteGw = (item: GwListItem) => {
uni.showModal({
title: "确认删除",
content: `确定要删除公文"${item.title}"吗?`,
success: async (res) => {
if (res.confirm) {
try {
// API - {ids: item.id}
await gwLogicDeleteApi({ ids: item.id });
//
reload();
uni.showToast({
title: "删除成功",
icon: "success",
});
} catch (error) {
console.error("删除公文失败:", error);
uni.showToast({
title: "删除失败",
icon: "error",
});
}
}
},
});
};
// //
const createNewGw = () => { const createNewGw = () => {
@ -253,8 +251,10 @@ const createNewGw = () => {
// //
const getStatusClass = (status: string) => { const getStatusClass = (status: string) => {
const statusMap: Record<string, string> = { const statusMap: Record<string, string> = {
'A': "status-submitted", // 'A': "status-draft", // A
'B': "status-draft", // 稿 'B': "status-submitted", // B
'C': "status-pending", // C
'D': "status-completed", // D
}; };
return statusMap[status] || "status-default"; return statusMap[status] || "status-default";
}; };
@ -262,87 +262,245 @@ const getStatusClass = (status: string) => {
// //
const getStatusText = (status: string) => { const getStatusText = (status: string) => {
const statusMap: Record<string, string> = { const statusMap: Record<string, string> = {
'A': "已提交", // 'A': "暂存", // A
'B': "草稿", // 稿 'B': "提交", // B
'C': "审批中", // C
'D': "已完结", // D
}; };
return statusMap[status] || "未知"; return statusMap[status] || "未知";
}; };
// //
const getUrgencyClass = (urgency: string) => { const getButtonText = (item: GwListItem) => {
const urgencyMap: Record<string, string> = { const currentTeacherId = getCurrentTeacherId();
'low': "urgency-low", const { gwStatus, spZbqd } = item;
'normal': "urgency-normal",
'high': "urgency-high",
'urgent': "urgency-urgent",
};
return urgencyMap[urgency] || "urgency-normal";
};
// // IDspZbqdB""
const getUrgencyText = (urgency: string) => { if (currentTeacherId && spZbqd && gwStatus === 'B') {
const urgencyMap: Record<string, string> = { const approverIds = spZbqd.split(',').map(id => id.trim());
'low': "普通", if (approverIds.includes(currentTeacherId)) {
'normal': "一般", return '审批';
'high': "紧急",
'urgent': "特急",
};
return urgencyMap[urgency] || "一般";
};
//
const getApproverProgress = (item: GwListItem) => {
let spCount = 0;
let ccCount = 0;
// -
if (item.spId) {
if (Array.isArray(item.spId)) {
spCount = item.spId.length;
} else {
//
const spIdStr = String(item.spId);
if (spIdStr.trim()) {
spCount = spIdStr.split(',').filter((id: string) => id.trim()).length;
}
} }
} }
// - // ""
if (item.ccId) { return '详情';
if (Array.isArray(item.ccId)) {
ccCount = item.ccId.length;
} else {
//
const ccIdStr = String(item.ccId);
if (ccIdStr.trim()) {
ccCount = ccIdStr.split(',').filter((id: string) => id.trim()).length;
}
}
}
if (spCount === 0 && ccCount === 0) {
return "无审批人和抄送人";
}
let result = "";
if (spCount > 0) {
result += `${spCount}个审批人`;
}
if (ccCount > 0) {
if (result) result += "";
result += `${ccCount}个抄送人`;
}
return result;
}; };
//
const getButtonClass = (item: GwListItem) => {
const buttonText = getButtonText(item);
return buttonText === '审批' ? 'action-button-approve' : 'action-button-detail';
};
//
//
// //
const formatTime = (time: string | Date | undefined) => { const formatTime = (time: string | Date | undefined) => {
if (!time) return '暂无'; if (!time) return '暂无';
return dayjs(time).format("MM-DD HH:mm"); return dayjs(time).format("MM-DD HH:mm");
}; };
//
const formatDate = (time: string | Date | undefined) => {
if (!time) return '暂无';
return dayjs(time).format("YYYY-MM-DD");
};
//
const hasAttachments = (data: any) => {
return data.fileUrl || (data.files && data.files.length > 0);
};
//
const parseFileList = (data: any) => {
const fileList: FileInfo[] = [];
// data
if (data.fileUrl && data.fileName) {
const urls = data.fileUrl.split(',').map((url: string) => url.trim());
const names = data.fileName.split(',').map((name: string) => name.trim());
urls.forEach((url: string, index: number) => {
if (url) {
const fileName = names[index] || `文件${index + 1}`;
const fileSuffix = url.split('.').pop() || '';
fileList.push({
name: fileName,
url: url,
resourName: fileName,
resourUrl: url,
resourSuf: fileSuffix,
size: 0 // URL
});
}
});
}
// files
if (data.files && data.files.length > 0) {
fileList.push(...data.files);
}
return fileList;
};
// data
const previewAttachment = (data: any) => {
if (!data.fileUrl) {
console.error("没有找到附件URL");
return;
}
const fileUrl = imagUrl(data.fileUrl);
const fileName = data.fileName || '未知文件';
const fileFormat = data.fileFormat || '';
//
if (isVideo(fileFormat)) {
previewVideoUtil(fileUrl, fileName)
.then(() => console.log('视频预览成功'))
.catch((error: any) => console.error('视频预览失败:', error));
} else if (isImage(fileFormat)) {
previewImageUtil(fileUrl)
.then(() => console.log('图片预览成功'))
.catch((error: any) => console.error('图片预览失败:', error));
} else if (canPreview(fileFormat)) {
const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName;
previewFileUtil(fileUrl, fullFileName, fileFormat)
.then(() => console.log('文档预览成功'))
.catch((error: any) => console.error('文档预览失败:', error));
} else {
//
downloadAttachment(data);
}
};
// files
const previewAttachmentFile = (file: FileInfo) => {
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : (file.url ? imagUrl(file.url) : '');
const fileName = file.resourName || file.name || '未知文件';
const fileSuf = getFileSuffix(file);
if (!fileUrl) {
console.error("没有找到文件URL");
return;
}
//
if (isVideo(fileSuf)) {
previewVideoUtil(fileUrl, fileName)
.then(() => console.log('视频预览成功'))
.catch((error: any) => console.error('视频预览失败:', error));
} else if (isImage(fileSuf)) {
previewImageUtil(fileUrl)
.then(() => console.log('图片预览成功'))
.catch((error: any) => console.error('图片预览失败:', error));
} else if (canPreview(fileSuf)) {
previewFileUtil(fileUrl, fileName, fileSuf)
.then(() => console.log('文档预览成功'))
.catch((error: any) => console.error('文档预览失败:', error));
} else {
//
downloadAttachmentFile(file);
}
};
//
const downloadAttachment = (data: any) => {
if (!data.fileUrl) {
console.error("没有找到附件URL");
return;
}
const fileUrl = imagUrl(data.fileUrl);
const fileName = data.fileName || '未知文件';
const fileFormat = data.fileFormat || '';
const fullFileName = fileFormat ? `${fileName}.${fileFormat}` : fileName;
downloadFileUtil(fileUrl, fullFileName)
.then(() => {
console.log('文件下载成功');
uni.showToast({
title: '下载成功',
icon: 'success'
});
})
.catch((error: any) => {
console.error('文件下载失败:', error);
uni.showToast({
title: '下载失败',
icon: 'error'
});
});
};
//
const downloadAttachmentFile = (file: FileInfo) => {
const fileUrl = file.resourUrl ? imagUrl(file.resourUrl) : (file.url ? imagUrl(file.url) : '');
const fileName = file.resourName || file.name || '未知文件';
if (!fileUrl) {
console.error("没有找到文件URL");
return;
}
downloadFileUtil(fileUrl, fileName)
.then(() => {
console.log('文件下载成功');
uni.showToast({
title: '下载成功',
icon: 'success'
});
})
.catch((error: any) => {
console.error('文件下载失败:', error);
uni.showToast({
title: '下载失败',
icon: 'error'
});
});
};
//
const getFileName = (file: FileInfo) => {
if (file.resourName) {
return file.resourName;
}
if (file.name) {
// name
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(0, lastDotIndex);
}
return file.name;
}
return '未知文件';
};
//
const getFileSuffix = (file: FileInfo) => {
if (file.resSuf) {
return file.resSuf;
}
if (file.url) {
const lastDotIndex = file.url.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.url.substring(lastDotIndex + 1);
}
}
if (file.name) {
const lastDotIndex = file.name.lastIndexOf('.');
if (lastDotIndex > 0) {
return file.name.substring(lastDotIndex + 1);
}
}
return 'unknown';
};
// //
watch(dataList, (val) => { watch(dataList, (val) => {
// //
@ -356,6 +514,17 @@ onShow(() => {
// //
onMounted(() => { onMounted(() => {
reload(); reload();
// gwFlow
uni.$on('refreshGwList', () => {
console.log('收到刷新事件,重新加载数据');
reload();
});
});
//
onUnmounted(() => {
uni.$off('refreshGwList');
}); });
</script> </script>
@ -459,6 +628,7 @@ onMounted(() => {
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
word-break: break-word; word-break: break-word;
} }
@ -477,6 +647,14 @@ onMounted(() => {
color: white; color: white;
} }
&.status-submitted { &.status-submitted {
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white;
}
&.status-pending {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
color: white;
}
&.status-completed {
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%); background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
color: white; color: white;
} }
@ -506,10 +684,9 @@ onMounted(() => {
} }
.info-label { .info-label {
width: 80px;
color: #666; color: #666;
font-size: 14px; font-size: 14px;
margin-right: 8px; margin-right: 0;
flex-shrink: 0; flex-shrink: 0;
} }
@ -519,28 +696,53 @@ onMounted(() => {
flex: 1; flex: 1;
} }
.urgency-tag { //
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&.urgency-low { //
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%); .attachments-section {
color: white; margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
}
//
.attachments-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.attachment-item {
display: flex;
align-items: center;
padding: 6px 10px;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
max-width: 200px;
&:active {
transform: translateY(1px);
background: #e9ecef;
border-color: #007aff;
} }
&.urgency-normal {
background: linear-gradient(135deg, #90a4ae 0%, #78909c 100%); .attachment-icon {
color: white; margin-right: 6px;
font-size: 16px;
flex-shrink: 0;
} }
&.urgency-high {
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%); .attachment-name {
color: white; font-size: 12px;
} color: #333;
&.urgency-urgent { overflow: hidden;
background: linear-gradient(135deg, #ef5350 0%, #e53935 100%); text-overflow: ellipsis;
color: white; white-space: nowrap;
flex: 1;
} }
} }
@ -557,6 +759,32 @@ onMounted(() => {
gap: 8px; gap: 8px;
} }
//
.action-button-approve,
.action-button-detail {
padding: 4px 8px !important;
border-radius: 12px !important;
font-size: 12px !important;
font-weight: 500 !important;
white-space: nowrap !important;
flex-shrink: 0 !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
height: auto !important;
min-height: 24px !important;
}
.action-button-approve {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%) !important;
color: white !important;
border: none !important;
}
.action-button-detail {
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%) !important;
color: white !important;
border: none !important;
}
// //
@media (max-width: 375px) { @media (max-width: 375px) {
.query-component { .query-component {

View File

@ -35,6 +35,10 @@
<view class="info-label">开课地点</view> <view class="info-label">开课地点</view>
<view class="info-data">{{ xkkc.kcdd }}</view> <view class="info-data">{{ xkkc.kcdd }}</view>
</view> </view>
<view class="course-info-item">
<view class="info-label">开课年级</view>
<view class="info-data">{{ xkkc.njname || '暂无' }}</view>
</view>
<view class="course-info-item"> <view class="course-info-item">
<view class="info-label">上课人数</view> <view class="info-label">上课人数</view>
<view class="info-data" <view class="info-data"

View File

@ -34,6 +34,14 @@
<view class="time-label">上课地点</view> <view class="time-label">上课地点</view>
<view class="time-value">{{ xkkc.kcdd }}</view> <view class="time-value">{{ xkkc.kcdd }}</view>
</view> </view>
<view class="time-item">
<view class="time-label">开课年级</view>
<view class="time-value">{{ xkkc.njname || '暂无' }}</view>
</view>
<view class="time-item">
<view class="time-label">上课人数</view>
<view class="time-value">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view>
</view>
</view> </view>
<!-- 巡查时间状态 --> <!-- 巡查时间状态 -->

View File

@ -71,6 +71,42 @@
</view> </view>
</view> </view>
<!-- 签到时间未开始阶段 -->
<view v-else-if="currentStep === 'timeNotStarted'" class="time-not-started-step">
<view class="time-not-started-content">
<u-icon name="clock" size="80" color="#409EFF" />
<text class="time-not-started-title">签到时间未开始</text>
<text class="time-not-started-subtitle">签到打卡时间还未开始请耐心等待</text>
<text class="time-not-started-tip">请关注签到开始时间</text>
<view class="meeting-info-card">
<text class="card-title">会议信息</text>
<view class="info-item">
<text class="info-label">会议名称</text>
<text class="info-value">{{ meetingInfo?.qdmc || '未设置' }}</text>
</view>
<view class="info-item">
<text class="info-label">签到时间</text>
<text class="info-value">{{ formatTime(meetingInfo?.qdkstime) }} - {{ formatTime(meetingInfo?.qdjstime) }}</text>
</view>
<view class="info-item">
<text class="info-label">打卡开始</text>
<text class="info-value">{{ formatTime(meetingInfo?.dkkstime) }}</text>
</view>
<view class="info-item">
<text class="info-label">会议地点</text>
<text class="info-value">{{ meetingInfo?.qdwz || '未设置' }}</text>
</view>
<view class="info-item">
<text class="info-label">当前时间</text>
<text class="info-value">{{ formatTime(new Date()) }}</text>
</view>
</view>
<button @click="goBack" class="back-btn">返回</button>
</view>
</view>
<!-- 签到时间已结束阶段 --> <!-- 签到时间已结束阶段 -->
<view v-else-if="currentStep === 'timeExpired'" class="time-expired-step"> <view v-else-if="currentStep === 'timeExpired'" class="time-expired-step">
<view class="time-expired-content"> <view class="time-expired-content">
@ -184,7 +220,7 @@ const qdzxId = ref('');
const qrExpireTime = ref(60); // 60 const qrExpireTime = ref(60); // 60
// //
const currentStep = ref<'sign' | 'confirm' | 'success' | 'notInList' | 'timeExpired' | 'qrExpired' | 'alreadySigned'>('confirm'); const currentStep = ref<'sign' | 'confirm' | 'success' | 'notInList' | 'timeNotStarted' | 'timeExpired' | 'qrExpired' | 'alreadySigned'>('confirm');
// //
const userInfo = ref<any>(null); const userInfo = ref<any>(null);
@ -286,10 +322,19 @@ const loadMeetingInfo = async () => {
if (result && result.resultCode === 1) { if (result && result.resultCode === 1) {
meetingInfo.value = result.result; meetingInfo.value = result.result;
// 1. // 1.
const currentTime = new Date(); const currentTime = new Date();
const startTime = meetingInfo.value?.dkkstime ? new Date(meetingInfo.value.dkkstime) : null;
const endTime = meetingInfo.value?.qdjstime ? new Date(meetingInfo.value.qdjstime) : null; const endTime = meetingInfo.value?.qdjstime ? new Date(meetingInfo.value.qdjstime) : null;
//
if (startTime && currentTime < startTime) {
// ""
currentStep.value = 'timeNotStarted';
return;
}
//
if (endTime && currentTime > endTime) { if (endTime && currentTime > endTime) {
// "" // ""
currentStep.value = 'timeExpired'; currentStep.value = 'timeExpired';
@ -418,18 +463,22 @@ const goBack = () => {
.info-item { .info-item {
display: flex; display: flex;
margin-bottom: 12px; margin-bottom: 16px;
align-items: flex-start;
.info-label { .info-label {
font-size: 14px; font-size: 14px;
color: #666; color: #666;
min-width: 80px; min-width: 80px;
text-align: right !important;
margin-right: 12px;
} }
.info-value { .info-value {
font-size: 14px; font-size: 14px;
color: #333; color: #333;
flex: 1; flex: 1;
text-align: left !important;
} }
} }
} }
@ -542,18 +591,22 @@ const goBack = () => {
.info-item { .info-item {
display: flex; display: flex;
margin-bottom: 8px; margin-bottom: 12px;
align-items: flex-start;
.info-label { .info-label {
font-size: 14px; font-size: 14px;
color: #666; color: #666;
min-width: 80px; min-width: 80px;
text-align: right !important;
margin-right: 12px;
} }
.info-value { .info-value {
font-size: 14px; font-size: 14px;
color: #333; color: #333;
flex: 1; flex: 1;
text-align: left !important;
} }
} }
} }
@ -568,6 +621,37 @@ const goBack = () => {
font-size: 16px; font-size: 16px;
} }
/* 签到时间未开始阶段 */
.time-not-started-step {
background: white;
border-radius: 16px;
padding: 40px 24px;
margin-top: 40px;
text-align: center;
}
.time-not-started-content {
.time-not-started-title {
display: block;
font-size: 24px;
font-weight: 600;
color: #333;
margin: 16px 0 8px;
}
.time-not-started-subtitle {
font-size: 14px;
color: #666;
margin-bottom: 16px;
}
.time-not-started-tip {
font-size: 14px;
color: #999;
margin-bottom: 24px;
}
}
/* 签到时间已结束阶段 */ /* 签到时间已结束阶段 */
.time-expired-step { .time-expired-step {
background: white; background: white;
@ -647,18 +731,22 @@ const goBack = () => {
.info-item { .info-item {
display: flex; display: flex;
margin-bottom: 8px; margin-bottom: 12px;
align-items: flex-start;
.info-label { .info-label {
font-size: 14px; font-size: 14px;
color: #666; color: #666;
min-width: 80px; min-width: 80px;
text-align: right !important;
margin-right: 12px;
} }
.info-value { .info-value {
font-size: 14px; font-size: 14px;
color: #333; color: #333;
flex: 1; flex: 1;
text-align: left !important;
} }
} }
} }

View File

@ -17,11 +17,15 @@
<text class="info-value">{{ qdInfo.qdwz || '未设置' }}</text> <text class="info-value">{{ qdInfo.qdwz || '未设置' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="info-label">开始时间</text> <text class="info-label">签到时间</text>
<text class="info-value">{{ formatTime(qdInfo.dkkstime) }}</text>
</view>
<view class="info-item">
<text class="info-label">会议开始</text>
<text class="info-value">{{ formatTime(qdInfo.qdkstime) }}</text> <text class="info-value">{{ formatTime(qdInfo.qdkstime) }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="info-label">结束时间</text> <text class="info-label">会议结束</text>
<text class="info-value">{{ formatTime(qdInfo.qdjstime) }}</text> <text class="info-value">{{ formatTime(qdInfo.qdjstime) }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
@ -49,6 +53,10 @@
<text class="stat-number signed">{{ signedCount }}</text> <text class="stat-number signed">{{ signedCount }}</text>
<text class="stat-label">已签到</text> <text class="stat-label">已签到</text>
</view> </view>
<view class="stat-item" @click="showTeacherList('late')">
<text class="stat-number late">{{ lateCount }}</text>
<text class="stat-label">迟到</text>
</view>
<view class="stat-item" @click="showTeacherList('unsigned')"> <view class="stat-item" @click="showTeacherList('unsigned')">
<text class="stat-number unsigned">{{ unsignedCount }}</text> <text class="stat-number unsigned">{{ unsignedCount }}</text>
<text class="stat-label">未签到</text> <text class="stat-label">未签到</text>
@ -141,6 +149,7 @@ interface QdInfo {
jsId: string; jsId: string;
jsxm: string; jsxm: string;
qdFbtime: string; qdFbtime: string;
dkkstime: string;
qdkstime: string; qdkstime: string;
qdjstime: string; qdjstime: string;
qdry: string; qdry: string;
@ -166,11 +175,28 @@ const currentFilter = ref('all');
const totalCount = computed(() => teacherList.value.length); const totalCount = computed(() => teacherList.value.length);
const signedCount = computed(() => teacherList.value.filter(t => t.qdStatus === '1').length); const signedCount = computed(() => teacherList.value.filter(t => t.qdStatus === '1').length);
const unsignedCount = computed(() => teacherList.value.filter(t => t.qdStatus === '0').length); const unsignedCount = computed(() => teacherList.value.filter(t => t.qdStatus === '0').length);
const lateCount = computed(() => {
if (!qdInfo.value.qdkstime) return 0;
const meetingStartTime = new Date(qdInfo.value.qdkstime);
return teacherList.value.filter(t => {
if (t.qdStatus !== '1' || !t.qdwctime) return false;
const signInTime = new Date(t.qdwctime);
return signInTime > meetingStartTime;
}).length;
});
const filteredTeacherList = computed(() => { const filteredTeacherList = computed(() => {
switch (currentFilter.value) { switch (currentFilter.value) {
case 'signed': case 'signed':
return teacherList.value.filter(t => t.qdStatus === '1'); return teacherList.value.filter(t => t.qdStatus === '1');
case 'late':
if (!qdInfo.value.qdkstime) return [];
const meetingStartTime = new Date(qdInfo.value.qdkstime);
return teacherList.value.filter(t => {
if (t.qdStatus !== '1' || !t.qdwctime) return false;
const signInTime = new Date(t.qdwctime);
return signInTime > meetingStartTime;
});
case 'unsigned': case 'unsigned':
return teacherList.value.filter(t => t.qdStatus === '0'); return teacherList.value.filter(t => t.qdStatus === '0');
default: default:
@ -225,6 +251,9 @@ const showTeacherList = (filter: string) => {
case 'signed': case 'signed':
popupTitle.value = `已签到人员 (${signedCount.value}人)`; popupTitle.value = `已签到人员 (${signedCount.value}人)`;
break; break;
case 'late':
popupTitle.value = `迟到人员 (${lateCount.value}人)`;
break;
case 'unsigned': case 'unsigned':
popupTitle.value = `未签到人员 (${unsignedCount.value}人)`; popupTitle.value = `未签到人员 (${unsignedCount.value}人)`;
break; break;
@ -421,6 +450,10 @@ const handleBack = () => {
&.unsigned { &.unsigned {
color: #dc3545; color: #dc3545;
} }
&.late {
color: #ff9800;
}
} }
.stat-label { .stat-label {

View File

@ -61,11 +61,15 @@
<text class="info-value">{{ data.qdwz || '未设置' }}</text> <text class="info-value">{{ data.qdwz || '未设置' }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="info-label">开始时间</text> <text class="info-label">签到时间</text>
<text class="info-value">{{ formatTime(data.dkkstime) }}</text>
</view>
<view class="info-item">
<text class="info-label">会议开始</text>
<text class="info-value">{{ formatTime(data.qdkstime) }}</text> <text class="info-value">{{ formatTime(data.qdkstime) }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="info-label">结束时间</text> <text class="info-label">会议结束</text>
<text class="info-value">{{ formatTime(data.qdjstime) }}</text> <text class="info-value">{{ formatTime(data.qdjstime) }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
@ -129,6 +133,7 @@ interface QdItem {
jsId: string; // ID jsId: string; // ID
jsxm: string; // jsxm: string; //
qdFbtime: string; // qdFbtime: string; //
dkkstime: string; //
qdkstime: string; // qdkstime: string; //
qdjstime: string; // qdjstime: string; //
qdry: string; // qdry: string; //
@ -396,6 +401,7 @@ onMounted(() => {
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
word-break: break-word; word-break: break-word;
} }

View File

@ -5,14 +5,18 @@
<!-- 签到名称 --> <!-- 签到名称 -->
<view class="info-card"> <view class="info-card">
<view class="form-item"> <view class="form-item">
<view class="form-label">
<text class="required-asterisk">*</text>
<text class="label-text">签到名称</text>
</view>
<uni-easyinput <uni-easyinput
type="textarea" type="textarea"
autoHeight autoHeight
v-model="formData.qdmc" v-model="formData.qdmc"
placeholder="请输入签到名称 (必填)" placeholder="请输入签到名称"
:inputBorder="false" :inputBorder="true"
placeholder-style="font-weight:bold; font-size: 18px; color: #999;" placeholder-style="font-size: 16px; color: #999;"
class="title-input" class="input-field"
></uni-easyinput> ></uni-easyinput>
</view> </view>
</view> </view>
@ -20,13 +24,16 @@
<!-- 签到地点 --> <!-- 签到地点 -->
<view class="info-card"> <view class="info-card">
<view class="form-item"> <view class="form-item">
<view class="form-label">
<text class="required-asterisk">*</text>
<text class="label-text">签到地点</text>
</view>
<uni-easyinput <uni-easyinput
type="textarea" type="text"
autoHeight
v-model="formData.qdwz" v-model="formData.qdwz"
placeholder="请输入签到地点 (必填)" placeholder="请输入签到地点"
:inputBorder="false" :inputBorder="true"
class="content-input" class="input-field single-line"
></uni-easyinput> ></uni-easyinput>
</view> </view>
</view> </view>
@ -34,7 +41,10 @@
<!-- 签到人员 --> <!-- 签到人员 -->
<view class="info-card"> <view class="info-card">
<view class="card-header picker-header" @click="showTeacherTree"> <view class="card-header picker-header" @click="showTeacherTree">
<text class="section-title">签到人员</text> <view class="section-title-container">
<text class="required-asterisk">*</text>
<text class="section-title">签到人员</text>
</view>
<view class="target-class"> <view class="target-class">
<text :class="{ placeholder: !formData.targetTeachers.length }"> <text :class="{ placeholder: !formData.targetTeachers.length }">
{{ formData.targetTeachers.length ? `已选择${formData.targetTeachers.length}` : "请选择教师" }} {{ formData.targetTeachers.length ? `已选择${formData.targetTeachers.length}` : "请选择教师" }}
@ -76,14 +86,35 @@
</picker> </picker>
</view> </view>
<!-- 签到打卡开始时间 -->
<view class="info-card list-item-card">
<uni-datetime-picker type="datetime" v-model="formData.dkkstime">
<view class="list-item-row">
<view class="list-label-container">
<text class="required-asterisk">*</text>
<text class="list-label">签到开始</text>
</view>
<view class="list-value">
<text :class="{ placeholder: !formData.dkkstime }">
{{ formData.dkkstime || '请选择签到打卡开始时间' }}
</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
</uni-datetime-picker>
</view>
<!-- 签到开始时间 --> <!-- 签到开始时间 -->
<view class="info-card list-item-card"> <view class="info-card list-item-card">
<uni-datetime-picker type="datetime" v-model="formData.qdkstime"> <uni-datetime-picker type="datetime" v-model="formData.qdkstime">
<view class="list-item-row"> <view class="list-item-row">
<text class="list-label">开始时间</text> <view class="list-label-container">
<text class="required-asterisk">*</text>
<text class="list-label">会议开始</text>
</view>
<view class="list-value"> <view class="list-value">
<text :class="{ placeholder: !formData.qdkstime }"> <text :class="{ placeholder: !formData.qdkstime }">
{{ formData.qdkstime || '请选择开始时间' }} {{ formData.qdkstime || '请选择会议开始时间' }}
</text> </text>
<uni-icons type="right" size="16" color="#999"></uni-icons> <uni-icons type="right" size="16" color="#999"></uni-icons>
</view> </view>
@ -95,10 +126,13 @@
<view class="info-card list-item-card"> <view class="info-card list-item-card">
<uni-datetime-picker type="datetime" v-model="formData.qdjstime"> <uni-datetime-picker type="datetime" v-model="formData.qdjstime">
<view class="list-item-row"> <view class="list-item-row">
<text class="list-label">结束时间</text> <view class="list-label-container">
<text class="required-asterisk">*</text>
<text class="list-label">会议结束</text>
</view>
<view class="list-value"> <view class="list-value">
<text :class="{ placeholder: !formData.qdjstime }"> <text :class="{ placeholder: !formData.qdjstime }">
{{ formData.qdjstime || '请选择结束时间' }} {{ formData.qdjstime || '请选择会议结束时间' }}
</text> </text>
<uni-icons type="right" size="16" color="#999"></uni-icons> <uni-icons type="right" size="16" color="#999"></uni-icons>
</view> </view>
@ -106,6 +140,7 @@
</uni-datetime-picker> </uni-datetime-picker>
</view> </view>
<!-- 发布人 --> <!-- 发布人 -->
<view class="info-card list-item-card"> <view class="info-card list-item-card">
<view class="list-item-row"> <view class="list-item-row">
@ -171,6 +206,7 @@ const formData = reactive({
mdqz: '0', mdqz: '0',
qdkstime: '', qdkstime: '',
qdjstime: '', qdjstime: '',
dkkstime: '',
jsxm: '', jsxm: '',
qdFbtime: '', qdFbtime: '',
targetTeachers: [] as TeacherInfo[] targetTeachers: [] as TeacherInfo[]
@ -273,12 +309,17 @@ const handlePublish = async () => {
} }
if (!formData.qdkstime) { if (!formData.qdkstime) {
uni.showToast({ title: '请选择开始时间', icon: 'none' }); uni.showToast({ title: '请选择会议开始时间', icon: 'none' });
return; return;
} }
if (!formData.qdjstime) { if (!formData.qdjstime) {
uni.showToast({ title: '请选择结束时间', icon: 'none' }); uni.showToast({ title: '请选择会议结束时间', icon: 'none' });
return;
}
if (!formData.dkkstime) {
uni.showToast({ title: '请选择签到打卡开始时间', icon: 'none' });
return; return;
} }
@ -312,6 +353,7 @@ const handlePublish = async () => {
mdqz: formData.mdqz, mdqz: formData.mdqz,
qdkstime: formatDateTime(formData.qdkstime), qdkstime: formatDateTime(formData.qdkstime),
qdjstime: formatDateTime(formData.qdjstime), qdjstime: formatDateTime(formData.qdjstime),
dkkstime: formatDateTime(formData.dkkstime),
jsId: js.id, jsId: js.id,
jsxm: formData.jsxm, jsxm: formData.jsxm,
qdFbtime: formData.qdFbtime, qdFbtime: formData.qdFbtime,
@ -372,38 +414,72 @@ const handlePublish = async () => {
margin-bottom: 15px; margin-bottom: 15px;
} }
.title-input { .form-label {
font-size: 18px; display: flex;
font-weight: bold; align-items: center;
color: #333; margin-bottom: 8px;
:deep(.uni-easyinput__content) {
background: transparent;
}
:deep(.uni-easyinput__content-input) {
color: #333;
}
:deep(.uni-easyinput__placeholder-class) {
color: #999;
}
} }
.content-input { .required-asterisk {
color: #ff4757;
font-size: 16px;
font-weight: bold;
margin-right: 4px;
}
.label-text {
font-size: 16px;
font-weight: 600;
color: #333;
}
.section-title-container {
display: flex;
align-items: center;
}
.list-label-container {
display: flex;
align-items: center;
}
.input-field {
font-size: 16px; font-size: 16px;
color: #333; color: #333;
:deep(.uni-easyinput__content) { :deep(.uni-easyinput__content) {
background: transparent; background: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 12px;
min-height: 44px;
} }
:deep(.uni-easyinput__content-input) { :deep(.uni-easyinput__content-input) {
color: #333; color: #333;
font-size: 16px;
} }
:deep(.uni-easyinput__placeholder-class) { :deep(.uni-easyinput__placeholder-class) {
color: #999; color: #999;
font-size: 16px;
}
:deep(.uni-easyinput__content):focus-within {
border-color: #007aff;
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
}
&.single-line {
:deep(.uni-easyinput__content) {
min-height: 44px;
height: 44px;
}
:deep(.uni-easyinput__content-input) {
line-height: 44px;
height: 44px;
}
} }
} }

View File

@ -26,6 +26,10 @@
</view> </view>
<view class="qr-info"> <view class="qr-info">
<view class="info-item">
<text class="info-label">签到时间</text>
<text class="info-value">{{ formatTime(meetingInfo?.dkkstime) }}</text>
</view>
<view class="info-item"> <view class="info-item">
<text class="info-label">会议时间</text> <text class="info-label">会议时间</text>
<text class="info-value">{{ formatTime(meetingInfo?.qdkstime) }} - {{ formatTime(meetingInfo?.qdjstime) }}</text> <text class="info-value">{{ formatTime(meetingInfo?.qdkstime) }} - {{ formatTime(meetingInfo?.qdjstime) }}</text>

View File

@ -43,7 +43,7 @@
</view> </view>
<!-- 具体选择 --> <!-- 具体选择 -->
<view class="filter-row" v-if="selectType && datas.length > 0 && selectType !== 1"> <view class="filter-row" v-if="selectType && selectType !== 1">
<view class="filter-label">{{ getSecondSelectLabel() }}</view> <view class="filter-label">{{ getSecondSelectLabel() }}</view>
<view class="multi-select-container"> <view class="multi-select-container">
<view class="selected-items-display" @click="showMultiSelectModal"> <view class="selected-items-display" @click="showMultiSelectModal">
@ -256,7 +256,8 @@ const onSelectTypeChange = async (e: any) => {
// //
await loadKmData(); await loadKmData();
} else if (selectType.value === 4) { } else if (selectType.value === 4) {
// //
zwType.value = 1; //
await loadZwData(); await loadZwData();
} }
}; };
@ -329,20 +330,20 @@ const loadAllTeachersFromStorage = async () => {
console.log('parsedData是否为对象:', typeof parsedData === 'object'); console.log('parsedData是否为对象:', typeof parsedData === 'object');
console.log('parsedData的所有属性:', Object.keys(parsedData || {})); console.log('parsedData的所有属性:', Object.keys(parsedData || {}));
console.log('parsedData的data属性:', parsedData?.data); console.log('parsedData的data属性:', parsedData?.data);
console.log('parsedData的allJs属性:', parsedData?.allJs); console.log('parsedData的allJsBasicInfoVo属性:', parsedData?.allJsBasicInfoVo);
// allJsparsedData // allJsBasicInfoVoparsedData
let allJsData; let allJsData;
if (parsedData.data && parsedData.data.allJs) { if (parsedData.data && parsedData.data.allJsBasicInfoVo) {
allJsData = parsedData.data.allJs; allJsData = parsedData.data.allJsBasicInfoVo;
console.log('从parsedData.data.allJs获取数据'); console.log('从parsedData.data.allJsBasicInfoVo获取数据');
} else if (parsedData.allJs) { } else if (parsedData.allJsBasicInfoVo) {
allJsData = parsedData.allJs; allJsData = parsedData.allJsBasicInfoVo;
console.log('从parsedData.allJs获取数据'); console.log('从parsedData.allJsBasicInfoVo获取数据');
} else { } else {
console.warn('localStorage中没有找到allJs数据'); console.warn('localStorage中没有找到allJsBasicInfoVo数据');
console.warn('可用的属性:', Object.keys(parsedData)); console.warn('可用的属性:', Object.keys(parsedData));
uni.showToast({ title: '未找到教师数据(allJs)', icon: 'none' }); uni.showToast({ title: '未找到教师数据(allJsBasicInfoVo)', icon: 'none' });
return; return;
} }
@ -440,18 +441,18 @@ const loadTeachersByNjIdFromStorage = async () => {
parsedData = storageData; parsedData = storageData;
} }
// allJsparsedData // allJsBasicInfoVoparsedData
let allJsData; let allJsData;
if (parsedData.data && parsedData.data.allJs) { if (parsedData.data && parsedData.data.allJsBasicInfoVo) {
allJsData = parsedData.data.allJs; allJsData = parsedData.data.allJsBasicInfoVo;
console.log('从parsedData.data.allJs获取数据'); console.log('从parsedData.data.allJsBasicInfoVo获取数据');
} else if (parsedData.allJs) { } else if (parsedData.allJsBasicInfoVo) {
allJsData = parsedData.allJs; allJsData = parsedData.allJsBasicInfoVo;
console.log('从parsedData.allJs获取数据'); console.log('从parsedData.allJsBasicInfoVo获取数据');
} else { } else {
console.warn('localStorage中没有找到allJs数据'); console.warn('localStorage中没有找到allJsBasicInfoVo数据');
console.warn('可用的属性:', Object.keys(parsedData)); console.warn('可用的属性:', Object.keys(parsedData));
uni.showToast({ title: '未找到教师数据(allJs)', icon: 'none' }); uni.showToast({ title: '未找到教师数据(allJsBasicInfoVo)', icon: 'none' });
return; return;
} }
@ -509,6 +510,8 @@ const loadTeachersByZwIdFromStorage = async () => {
console.log('=== 开始从localStorage按职务加载教师数据 ==='); console.log('=== 开始从localStorage按职务加载教师数据 ===');
console.log('选中的职务ID:', selectTwoType.value); console.log('选中的职务ID:', selectTwoType.value);
console.log('职务类型:', zwType.value === 1 ? '党政职务' : '其他职务'); console.log('职务类型:', zwType.value === 1 ? '党政职务' : '其他职务');
console.log('selectTwoType.value类型:', typeof selectTwoType.value);
console.log('selectTwoType.value是否为数组:', Array.isArray(selectTwoType.value));
// localStorage // localStorage
const storageData = uni.getStorageSync('app-common'); const storageData = uni.getStorageSync('app-common');
@ -535,15 +538,15 @@ const loadTeachersByZwIdFromStorage = async () => {
parsedData = storageData; parsedData = storageData;
} }
// parsedData.data.allJs // parsedData.data.allJsBasicInfoVo
if (!parsedData.data || !parsedData.data.allJs) { if (!parsedData.data || !parsedData.data.allJsBasicInfoVo) {
console.warn('localStorage中没有找到教师数据'); console.warn('localStorage中没有找到教师数据');
console.warn('可用的属性:', Object.keys(parsedData || {})); console.warn('可用的属性:', Object.keys(parsedData || {}));
uni.showToast({ title: '未找到教师数据', icon: 'none' }); uni.showToast({ title: '未找到教师数据', icon: 'none' });
return; return;
} }
const allJsData = parsedData.data.allJs; const allJsData = parsedData.data.allJsBasicInfoVo;
console.log('allJs数据结构:', allJsData); console.log('allJs数据结构:', allJsData);
// allJsresult // allJsresult
@ -557,25 +560,41 @@ const loadTeachersByZwIdFromStorage = async () => {
console.log('教师数组数据:', teacherArray); console.log('教师数组数据:', teacherArray);
console.log('教师数组长度:', teacherArray.length); console.log('教师数组长度:', teacherArray.length);
//
if (teacherArray.length > 0) {
console.log('第一个教师完整数据:', teacherArray[0]);
console.log('第一个教师的dzzw字段:', teacherArray[0].dzzw);
console.log('第一个教师的qtzw字段:', teacherArray[0].qtzw);
if (teacherArray.length > 1) {
console.log('第二个教师完整数据:', teacherArray[1]);
console.log('第二个教师的dzzw字段:', teacherArray[1].dzzw);
console.log('第二个教师的qtzw字段:', teacherArray[1].qtzw);
}
}
// ID // ID
const selectedZwId = selectTwoType.value[0]; // const selectedZwIds = selectTwoType.value; //
console.log('选中的职务ID列表:', selectedZwIds);
const filteredTeachers = teacherArray.filter((teacher: any) => { const filteredTeachers = teacherArray.filter((teacher: any) => {
let hasZwId = false; let hasZwId = false;
if (zwType.value === 1) { if (zwType.value === 1) {
// dzzw // dzzw
if (teacher.dzzw) { if (teacher.dzzw) {
const dzzwArray = teacher.dzzw.split(','); const dzzwArray = teacher.dzzw.split(',').map((id: string) => id.trim());
hasZwId = dzzwArray.includes(selectedZwId); //
hasZwId = selectedZwIds.some(selectedId => dzzwArray.includes(selectedId));
} }
console.log(`教师${teacher.jsxm || teacher.name}的党政职务:`, teacher.dzzw, '是否匹配职务ID:', selectedZwId, '结果:', hasZwId); console.log(`教师${teacher.jsxm || teacher.name}的党政职务:`, teacher.dzzw, '是否匹配职务ID列表:', selectedZwIds, '结果:', hasZwId);
} else if (zwType.value === 2) { } else if (zwType.value === 2) {
// qtzw // qtzw
if (teacher.qtzw) { if (teacher.qtzw) {
const qtzwArray = teacher.qtzw.split(','); const qtzwArray = teacher.qtzw.split(',').map((id: string) => id.trim());
hasZwId = qtzwArray.includes(selectedZwId); //
hasZwId = selectedZwIds.some(selectedId => qtzwArray.includes(selectedId));
} }
console.log(`教师${teacher.jsxm || teacher.name}的其他职务:`, teacher.qtzw, '是否匹配职务ID:', selectedZwId, '结果:', hasZwId); console.log(`教师${teacher.jsxm || teacher.name}的其他职务:`, teacher.qtzw, '是否匹配职务ID列表:', selectedZwIds, '结果:', hasZwId);
} }
return hasZwId; return hasZwId;
@ -725,9 +744,9 @@ const loadZwData = async () => {
} }
} }
// getZwListByLx // getZwListByLx
if (!zwData) { if (!zwData || !zwData['党政职务'] || !zwData['其他职务']) {
console.log('localStorage中没有职务数据开始调用getZwListByLx获取并缓存'); console.log('localStorage中没有完整职务数据开始调用getZwListByLx获取并缓存');
try { try {
// getZwListByLx // getZwListByLx
@ -773,7 +792,10 @@ const loadZwData = async () => {
if (filteredData.length === 0) { if (filteredData.length === 0) {
console.warn('没有找到对应类型的职务数据'); console.warn('没有找到对应类型的职务数据');
uni.showToast({ title: '未找到对应类型的职务数据', icon: 'none' }); //
// uni.showToast({ title: '', icon: 'none' });
//
datas.value = [];
return; return;
} }
@ -971,15 +993,19 @@ const ensureTeacherDataCached = async () => {
} }
// //
if (parsedData?.data?.allJs?.result && parsedData.data.allJs.result.length > 0) { if (parsedData?.data?.allJsBasicInfoVo?.result && parsedData.data.allJsBasicInfoVo.result.length > 0) {
hasTeacherData = true; hasTeacherData = true;
console.log('localStorage中已有教师数据数量:', parsedData.data.allJs.result.length); console.log('localStorage中已有教师数据数量:', parsedData.data.allJsBasicInfoVo.result.length);
} }
// //
if (parsedData?.data?.zw) { if (parsedData?.data?.zw &&
parsedData.data.zw['党政职务'] &&
parsedData.data.zw['其他职务']) {
hasZwData = true; hasZwData = true;
console.log('localStorage中已有职务数据'); console.log('localStorage中已有完整的职务数据');
} else if (parsedData?.data?.zw) {
console.log('localStorage中只有部分职务数据需要重新加载');
} }
} }

View File

@ -51,7 +51,7 @@
</view> </view>
<view class="student-info"> <view class="student-info">
<text class="student-name">{{ xs.xsXm || xs.xsxm }}</text> <text class="student-name">{{ xs.xsXm || xs.xsxm }}</text>
<text class="student-class">{{ xs.bjmc }}</text> <text class="student-class">{{ xs.njmcName || xs.njmc }}{{ xs.bjmc ? ' ' + xs.bjmc : '' }}</text>
</view> </view>
</view> </view>
@ -258,6 +258,7 @@ const loadXsList = async () => {
tx: dmXs.tx || dmXs.xstx || dmXs.avatar, tx: dmXs.tx || dmXs.xstx || dmXs.avatar,
bjmc: dmXs.bjmc, bjmc: dmXs.bjmc,
njmc: dmXs.njmc, njmc: dmXs.njmc,
njmcName: dmXs.njmcName || dmXs.njmc,
jzxm: dmXs.jzxm, jzxm: dmXs.jzxm,
jzdh: dmXs.jzdh, jzdh: dmXs.jzdh,
xsxm: dmXs.xsxm || dmXs.xm, xsxm: dmXs.xsxm || dmXs.xm,

View File

@ -32,6 +32,10 @@
<view class="info-label">开课地点</view> <view class="info-label">开课地点</view>
<view class="info-data">{{ xkkc.kcdd }}</view> <view class="info-data">{{ xkkc.kcdd }}</view>
</view> </view>
<view class="course-info-item">
<view class="info-label">开课年级</view>
<view class="info-data">{{ xkkc.njname || '暂无' }}</view>
</view>
<view class="course-info-item"> <view class="course-info-item">
<view class="info-label">上课人数</view> <view class="info-label">上课人数</view>
<view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view> <view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view>
@ -240,7 +244,6 @@ const goDm = (xkkc: any) => {
} else { } else {
msg = "上课时间未到,无法点名"; msg = "上课时间未到,无法点名";
} }
// TODO:
dmFlag = true; dmFlag = true;
if (dmFlag) { if (dmFlag) {
setData(xkkc); setData(xkkc);

View File

@ -35,6 +35,10 @@
<view class="info-label">开课地点</view> <view class="info-label">开课地点</view>
<view class="info-data">{{ xkkc.kcdd }}</view> <view class="info-data">{{ xkkc.kcdd }}</view>
</view> </view>
<view class="course-info-item">
<view class="info-label">开课年级</view>
<view class="info-data">{{ xkkc.njname || '暂无' }}</view>
</view>
<view class="course-info-item"> <view class="course-info-item">
<view class="info-label">上课人数</view> <view class="info-label">上课人数</view>
<view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view> <view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view>

View File

@ -19,6 +19,10 @@
<view class="info-label">上课周期</view> <view class="info-label">上课周期</view>
<view class="info-data">{{ xkkc.skzqmc }}</view> <view class="info-data">{{ xkkc.skzqmc }}</view>
</view> </view>
<view class="course-info-item">
<view class="info-label">开课年级</view>
<view class="info-data">{{ xkkc.njname || '暂无' }}</view>
</view>
<view class="course-info-item"> <view class="course-info-item">
<view class="info-label">上课时间</view> <view class="info-label">上课时间</view>
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view> <view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
@ -204,8 +208,13 @@ const schema = reactive<FormsSchema[]>([
{ {
field: "jhsj", field: "jhsj",
label: "计划时间", label: "计划时间",
component: "BasicDateTimes", component: "BasicInput",
componentProps: {}, componentProps: {
type: "date",
placeholder: "请选择计划日期",
//
style: "position: relative; z-index: 1000;",
},
}, },
{ {
field: "jhdd", field: "jhdd",
@ -222,6 +231,8 @@ const schema = reactive<FormsSchema[]>([
}, },
componentProps: { componentProps: {
type: "textarea", type: "textarea",
maxlength: -1, //
showCount: false, //
}, },
}, },
]) ])