zhxy-jzd/src/pages/base/jl/detail.vue
2025-07-07 21:10:34 +08:00

671 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- src/pages/view/notice/detail.vue -->
<template>
<BasicLayout>
<view class="notice-detail-page">
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else-if="noticeDetail" class="detail-container">
<!-- 1. 主要内容卡片 -->
<view class="info-card main-content-card">
<view v-if="noticeDetail.jlfm" class="cover-image-container">
<image
:src="imagUrl(noticeDetail.jlfm)"
mode="aspectFill"
class="cover-image"
></image>
</view>
<text class="notice-title">{{ noticeDetail.jlmc }}</text>
<!-- 发布人和结束时间 -->
<view class="notice-meta">
<text class="meta-item">发布人: {{ noticeDetail.jsxm }}</text>
<text class="meta-item">结束时间: {{ noticeDetail.jljstime || noticeDetail.endTime }}</text>
</view>
<view class="notice-content">
<view v-if="!descExpanded">
<text class="desc-preview">{{ descPreview }}</text>
<u-button type="text" size="mini" @click="descExpanded = true">更多</u-button>
</view>
<view v-else>
<rich-text :nodes="noticeDetail.jlms" class="desc-rich-text"></rich-text>
<u-button type="text" size="mini" @click="descExpanded = false">收起</u-button>
</view>
</view>
</view>
<!-- 2. 附件卡片 -->
<view v-if="noticeDetail.jlfj" class="info-card attachment-card">
<text class="attachment-title">附件</text>
<view class="attachment-list">
<view
class="attachment-item"
@click="previewAttachment(noticeDetail.jlfj)"
>
<uni-icons type="paperplane" size="16" color="#409eff"></uni-icons>
<text class="attachment-name">{{ getFileName(noticeDetail.jlfj) }}</text>
</view>
</view>
</view>
<!-- 3. 学生完成状态卡片 -->
<view class="info-card feedback-card">
<text class="feedback-title">接龙完成情况 ({{ receivedCount }}/{{ totalStudents }})</text>
<view class="name-tags">
<view
v-for="stu in studentList"
:key="stu.id || stu.xsId"
class="name-tag"
:class="{
received: stu.jlwc_status === 'A',
currentStudent: isCurrentStudent(stu)
}"
>
<text>{{ stu.xsxm || stu.name }}</text>
<view v-if="stu.jlwc_status === 'A'" class="checkmark-icon">
<uni-icons type="checkmarkempty" size="12" color="#ffffff"></uni-icons>
</view>
</view>
</view>
</view>
</view>
<view v-else class="empty-state">通知详情未找到</view>
</view>
<!-- 添加签名组件 -->
<BasicSign ref="signCompRef" :title="signTitle" @confirm="onSignConfirm"/>
<template #bottom>
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="点击接龙"
class="mr-15 mr-7"
type="primary"
@click="onRelayClick"
/>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import {ref, computed, watch} from "vue";
import {onLoad} from "@dcloudio/uni-app";
import {getByJlIdApi, jlzxFindByJlParamsApi, relayFinishApi} from "@/api/base/server";
import {imagUrl} from "@/utils";
import {BASE_IMAGE_URL} from "@/config";
import {useUserStore} from "@/store/modules/user";
import {navigateBack, showLoading, hideLoading} from "@/utils/uniapp";
const noticeId = ref<string>("");
const noticeDetail = ref<any>(null);
const isLoading = ref(false);
const studentList = ref<any[]>([]);
const descExpanded = ref(false);
// 签名相关
const signCompRef = ref<any>(null);
const signTitle = ref<string>("签名");
const sign_file = ref<string>("");
// 获取当前学生信息
const userStore = useUserStore();
const currentStudent = computed(() => userStore.curXs);
const descPreview = computed(() => {
if (!noticeDetail.value?.jlms) return '';
const div = document.createElement('div');
div.innerHTML = noticeDetail.value.jlms;
return div.innerText.replace(/\n/g, '').slice(0, 100) + (div.innerText.length > 100 ? '...' : '');
});
// 获取文件名
const getFileName = (filePath: string) => {
if (!filePath) return '';
const parts = filePath.split('/');
return parts[parts.length - 1] || filePath;
};
// Computed properties for feedback status
const receivedCount = computed(() => {
return studentList.value.filter((s) => s.jlwc_status === '1').length;
});
const totalStudents = computed(() => studentList.value.length);
// 判断是否为当前学生
const isCurrentStudent = (student: any) => {
return currentStudent.value && student.xsId === currentStudent.value.id;
};
// 新增:接龙按钮点击逻辑
async function onRelayClick() {
try {
const res = await getByJlIdApi({jlId: noticeId.value});
const detail = Array.isArray(res) ? res[0] : res;
// 判断当前学生是否已接龙
const curStu = studentList.value.find(stu => {
// 兼容驼峰和下划线
const status = stu.jlwc_status || stu.jlwcStatus;
return (stu.xsId === currentStudent.value?.id) && status === 'A';
});
if (curStu) {
uni.showToast({ title: '您已参与接龙,无须重复提交', icon: 'none' });
return;
}
if (detail && detail.mdqz == 1) {
// 需要签名
sign_file.value = '';
if (signCompRef.value) {
onSignConfirm();
}
} else {
submitRelay();
}
} catch (e) {
uni.showToast({title: '获取详情失败', icon: 'none'});
}
}
// 新增:签名确认
async function onSignConfirm() {
try {
if (!sign_file.value) {
if (signCompRef.value && signCompRef.value.getSyncSignature) {
const data = await signCompRef.value.getSyncSignature();
sign_file.value = data.base64;
}
}
if (!sign_file.value) {
uni.showToast({title: '请完成签名', icon: 'none'});
return;
}
submitRelay();
} catch (e) {
uni.showToast({title: '签名流程异常', icon: 'none'});
}
}
// 新增:提交接龙
async function submitRelay() {
const params: any = {
jl_id: noticeId.value,
xsId: currentStudent.value?.id,
};
if (sign_file.value) {
params.qm_file = sign_file.value;
}
showLoading('提交中...');
try {
const res = await relayFinishApi(params);
hideLoading();
if (res && res.resultCode === 1 ) {
uni.showToast({title: '接龙成功', icon: 'success'});
await refreshStudentList();
} else {
uni.showToast({title: res?.msg || '接龙失败', icon: 'none'});
}
} catch (e) {
hideLoading();
uni.showToast({title: '网络异常', icon: 'none'});
}
}
// 新增:刷新学生完成状态
async function refreshStudentList() {
try {
const stuRes = await jlzxFindByJlParamsApi({ jlId: noticeId.value });
studentList.value = stuRes?.rows || stuRes?.result || [];
} catch (e) {
uni.showToast({ title: "刷新学生状态失败", icon: "none" });
}
}
onLoad(async (options) => {
if (options && options.id) {
noticeId.value = options.id;
isLoading.value = true;
// 1. 获取接龙详情(新接口)
try {
const detailRes = await getByJlIdApi({jlId: noticeId.value});
noticeDetail.value = Array.isArray(detailRes) ? detailRes[0] : {};
} catch (e) {
uni.showToast({title: "加载接龙详情失败", icon: "none"});
}
// 2. 获取学生完成状态
try {
const stuRes = await jlzxFindByJlParamsApi({jlId: noticeId.value});
studentList.value = stuRes?.rows || stuRes?.result || [];
} catch (e) {
uni.showToast({title: "加载学生状态失败", icon: "none"});
}
isLoading.value = false;
uni.setNavigationBarTitle({title: "点击接龙"});
} else {
uni.showToast({title: "加载失败缺少通知ID", icon: "none"});
}
});
// 附件预览函数
const previewAttachment = (filePath: string) => {
if (!filePath) {
uni.showToast({title: "附件链接无效", icon: "none"});
return;
}
// 构建完整的文件URL
const baseUrl = BASE_IMAGE_URL; // 使用配置文件中的URL
const fullUrl = filePath.startsWith('http') ? filePath : baseUrl + filePath;
// 判断文件类型
const fileExtension = filePath.split('.').pop()?.toLowerCase();
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExtension || '');
// 如果是图片类型,直接预览
if (isImage) {
uni.previewImage({
urls: [fullUrl],
current: fullUrl
});
return;
}
uni.showLoading({title: "正在准备附件..."});
uni.downloadFile({
url: fullUrl,
success: (res) => {
if (res.statusCode === 200) {
const tempFilePath = res.tempFilePath;
uni.hideLoading();
// Attempt to open the downloaded file
uni.openDocument({
filePath: tempFilePath,
success: function (res) {
// 打开文档成功
},
fail: function (err) {
uni.showToast({title: "无法打开该文件类型", icon: "none"});
uni.hideLoading(); // Ensure loading is hidden on failure too
},
});
} else {
uni.hideLoading();
uni.showToast({title: "下载附件失败", icon: "none"});
}
},
fail: (err) => {
uni.hideLoading();
uni.showToast({title: "下载附件失败", icon: "none"});
},
});
};
watch(noticeDetail, (val) => {
// 监听数据变化
}, {immediate: true, deep: true});
// 在studentList渲染前打印每个学生的jlwc_status和姓名
watch(studentList, (list) => {
list.forEach(stu => {
// 兼容后端返回的驼峰写法
if (stu.jlwcStatus && !stu.jlwc_status) {
stu.jlwc_status = stu.jlwcStatus;
}
});
}, { immediate: true, deep: true });
</script>
<style scoped lang="scss">
.notice-detail-page {
background-color: #f4f5f7;
padding-bottom: 70px;
padding: 15px;
box-sizing: border-box;
}
.bottom-actions {
display: flex;
justify-content: space-around;
align-items: center;
padding: 12px 15px;
background-color: #ffffff;
border-top: 1px solid #e5e5e5;
.action-btn {
width: 90%;
min-width: 90px;
margin: 0 5px;
font-size: 15px;
height: 40px;
line-height: 40px;
border-radius: 20px;
padding: 0 20px;
&::after {
border: none;
}
&.draft-btn {
background-color: #f4f4f5;
color: #909399;
border: 1px solid #e9e9eb;
}
&.preview-btn {
background-color: #ecf5ff;
color: #409eff;
border: 1px solid #d9ecff;
}
&.publish-btn {
background-color: #409eff;
color: #ffffff;
border: none;
}
&.draft-btn:active {
background-color: #e0e0e0;
}
&.preview-btn:active {
background-color: #d9ecff;
}
&.publish-btn:active {
background-color: #3a8ee6;
}
}
}
.loading-indicator,
.empty-state {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.detail-container {
// 卡片之间的间距由卡片自身的 margin-bottom 控制
}
/* 通用卡片样式 */
.info-card {
background-color: #ffffff;
border-radius: 8px;
padding: 15px;
margin-bottom: 12px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
}
/* 主要内容卡片 */
.main-content-card {
.cover-placeholder {
background-color: #f0f0f0;
height: 120px;
border-radius: 6px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #ccc;
font-size: 14px;
margin-bottom: 15px;
text {
margin-top: 5px;
}
}
.cover-image-container {
margin-bottom: 15px;
.cover-image {
width: 100%;
height: 150px; // Or adjust as needed
border-radius: 6px;
display: block;
}
}
.notice-title {
font-size: 18px;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 10px;
}
.notice-meta {
margin-bottom: 15px;
.meta-item {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
}
.notice-content {
position: relative;
.desc-preview {
display: block;
font-size: 14px;
color: #666;
line-height: 1.5;
margin-bottom: 4px;
}
.desc-rich-text {
font-size: 14px;
color: #666;
line-height: 1.5;
}
}
// 设计稿中的添加附件占位
.add-attachment-placeholder {
display: flex;
align-items: center;
border: 1px dashed #dcdcdc;
border-radius: 6px;
padding: 15px;
margin-top: 15px;
.add-icon {
width: 30px;
height: 30px;
border: 1px solid #dcdcdc;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
}
.placeholder-text {
font-size: 14px;
color: #999;
}
}
}
/* 附件区域 */
.attachments-section {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #f0f0f0;
.sub-title {
font-size: 14px;
color: #666;
margin-bottom: 8px;
display: block;
}
.attachment-item {
display: flex;
align-items: center;
padding: 5px 0;
.attachment-name {
font-size: 14px;
color: #007aff;
margin-left: 5px;
}
}
}
/* 按名单填写/反馈情况卡片 */
.feedback-card {
// Optional: Add specific padding if needed
}
/* 附件卡片 */
.attachment-card {
.attachment-title {
display: block;
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.attachment-list {
.attachment-item {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.attachment-name {
font-size: 14px;
color: #409eff;
margin-left: 8px;
flex: 1;
}
}
}
}
.feedback-title {
display: block;
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.name-tags {
display: flex;
flex-wrap: wrap;
gap: 8px 10px; // Row gap, Column gap
.name-tag,
.modify-btn {
// Apply same sizing logic to modify button if it exists
position: relative;
font-size: 13px;
padding: 5px 8px; // Adjust horizontal padding slightly if needed
border-radius: 4px;
text-align: center;
flex-grow: 0;
flex-shrink: 0;
box-sizing: border-box;
// 调整为每行4个确保能显示4个汉字
flex-basis: calc((100% - 30px) / 4); // 4个学生3个间隔
min-width: 60px; // 最小宽度确保能显示4个汉字
height: 30px; // Slightly taller height
line-height: 20px; // Adjust line-height for vertical centering
border: 1px solid #f4f4f5;
// 允许文字换行
white-space: normal;
word-break: keep-all; // 保持汉字完整性
overflow: hidden;
}
.name-tag {
background-color: #f4f4f5;
color: #909399;
position: relative;
&.received {
background-color: #e1f3d8;
color: #67c23a;
border-color: #b3e19d;
}
&.currentStudent {
background-color: #e6f7ff;
color: #1890ff;
border-color: #91d5ff;
border-width: 2px;
font-weight: bold;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
}
.checkmark-icon {
position: absolute;
top: -1px;
right: -1px;
width: 16px;
height: 16px;
background-color: #67c23a;
border-bottom-left-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
padding: 1px;
box-sizing: border-box;
.uni-icons {
// Icon adjustments if needed
}
}
}
// Modify button (usually hidden in detail)
.modify-btn {
// ... styles ...
flex-basis: calc((100% - 30px) / 4); // Keep consistent basis
}
}
/* 列表项样式的卡片 */
.list-item-card {
padding: 5px 15px; // 减少上下 padding
}
.list-item-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0; // 行内上下 padding
border-bottom: 1px solid #f0f0f0;
&.no-border {
border-bottom: none;
}
.list-label {
font-size: 15px;
color: #333;
}
.list-value {
display: flex;
align-items: center;
font-size: 15px;
color: #666;
text {
margin-right: 3px;
}
}
}
</style>