zhxy-jsd/src/pages/view/notice/publish.vue
2025-07-08 22:20:22 +08:00

1137 lines
28 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.

<template>
<BasicLayout>
<!-- Default slot -->
<scroll-view scroll-y class="form-scroll-view">
<view class="form-container">
<view class="info-card main-content-card">
<view class="form-item cover-section">
<view class="cover-header">
<text class="form-label">封面</text>
<text class="cover-hint">建议尺寸xxx</text>
<text class="cover-counter"
>{{ formData.coverImage ? 1 : 0 }}/1</text
>
</view>
<view class="cover-upload-wrapper">
<CustomUpload
field="coverImage"
:value="formData.coverImage ? imagUrl(formData.coverImage) : ''"
@select="handleCoverSelected"
@close="handleCoverClosed"
>
<view class="cover-placeholder">
<uni-icons type="image" size="40" color="#b0b0b0"></uni-icons>
<text>添加封面</text>
</view>
</CustomUpload>
</view>
</view>
<view class="form-item">
<uni-easyinput
type="textarea"
autoHeight
v-model="formData.title"
placeholder="请输入通知标题 (必填)"
:inputBorder="false"
placeholder-style="font-weight:bold; font-size: 18px; color: #999;"
class="title-input"
></uni-easyinput>
</view>
<view class="form-item">
<uni-easyinput
type="textarea"
autoHeight
v-model="formData.content"
placeholder="请输入通知内容 (必填)"
:inputBorder="false"
class="content-input"
></uni-easyinput>
</view>
<view class="form-item attachments-section">
<text class="form-label">附件</text>
<view class="attachment-list">
<view
v-for="(att, index) in formData.attachments"
:key="index"
class="attachment-item"
>
<uni-icons
:type="getAttachmentIcon(att.type)"
size="20"
color="#666"
class="attachment-icon"
></uni-icons>
<text class="attachment-name" @click="previewAttachment(att)">{{
att.name
}}</text>
<uni-icons
type="closeempty"
size="18"
color="#999"
class="remove-icon"
@click="removeAttachment(index)"
></uni-icons>
</view>
</view>
<view class="add-attachment-placeholder" @click="addAttachment">
<view class="add-icon"
><uni-icons type="plusempty" size="20" color="#ccc"></uni-icons
></view>
<text class="placeholder-text">添加图文/视频/文件</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-header picker-header" @click="showClassTree">
<text class="section-title">按名单填写</text>
<view class="target-class">
<text :class="{ placeholder: !formData.targetClass }">{{
formData.targetClass || "请选择班级"
}}</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
<view class="name-tags">
<text
v-for="(name, index) in displayNames"
:key="name + index"
class="name-tag"
>{{ name }}</text
>
</view>
<view
v-if="formData.targetNames.length > maxDisplayCount"
class="more-btn-container"
>
<button size="mini" class="more-btn-full" @click="showAllStudents">
更多({{ formData.targetNames.length - maxDisplayCount }})
</button>
</view>
</view>
<view class="info-card list-item-card">
<picker
mode="selector"
:range="signatureOptions"
@change="handleSignatureChange"
>
<view class="list-item-row">
<text class="list-label">按名单签字</text>
<view class="list-value">
<text>{{ signatureStatusText }}</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
</picker>
</view>
<view class="info-card list-item-card">
<uni-datetime-picker type="datetime" v-model="formData.startTime">
<view class="list-item-row">
<text class="list-label">开始时间</text>
<view class="list-value">
<text>{{ formData.startTime || "请选择" }}</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
</uni-datetime-picker>
<uni-datetime-picker type="datetime" v-model="formData.endTime">
<view class="list-item-row no-border">
<text class="list-label">结束时间</text>
<view class="list-value">
<text>{{ formData.endTime || "请选择" }}</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
</uni-datetime-picker>
</view>
</view>
</scroll-view>
<!-- Bottom slot -->
<template #bottom>
<view class="bottom-actions">
<button class="action-btn draft-btn" @click="saveDraft">
保存草稿
</button>
<button class="action-btn preview-btn" @click="previewNotice">
预览
</button>
<button class="action-btn publish-btn" @click="publishNotice">
立即发布
</button>
</view>
</template>
<!-- 班级选择树 -->
<BasicTree
ref="treeRef"
:range="treeData"
idKey="key"
rangeKey="title"
title="选择班级"
:multiple="true"
:selectParent="false"
@confirm="onTreeConfirm"
@cancel="onTreeCancel"
/>
<!-- 学生名单弹窗 -->
<view
v-if="showStudentModal"
class="student-modal-mask"
@click="closeStudentModal"
>
<view class="student-modal" @click.stop>
<view class="student-modal-header">
<text class="modal-title">全部学生名单</text>
<text class="student-count"
>{{ formData.targetNames.length }}</text
>
<uni-icons
type="closeempty"
size="20"
color="#999"
@click="closeStudentModal"
class="close-icon"
></uni-icons>
</view>
<scroll-view scroll-y class="student-modal-content">
<view class="all-student-tags">
<text
v-for="(name, index) in formData.targetNames"
:key="name + index"
class="student-tag"
>{{ name }}</text
>
</view>
</scroll-view>
</view>
</view>
</BasicLayout>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
import BasicTree from "@/components/BasicTree/Tree.vue";
import { attachmentUpload } from "@/api/system/upload";
import { imagUrl } from "@/utils";
import { findAllNjBjTreeApi, mobilejlstudentListApi } from "@/api/base/server";
interface Attachment {
name: string;
type: string;
url: string;
size?: number;
}
interface FormData {
id?: string;
title: string;
content: string;
coverImage: string | null;
attachments: Attachment[];
targetClass: string;
targetNames: string[];
targetStudentIds: string[];
targetNjIds: string[]; // 年级ID数组
targetBjIds: string[]; // 班级ID数组
signatureRequired: boolean;
startTime: string;
endTime: string;
}
const noticeId = ref<string | null>(null);
const isLoadingStudents = ref(false);
const formData = reactive<FormData>({
title: "",
content: "",
coverImage: null,
attachments: [],
targetClass: "",
targetNames: [],
targetStudentIds: [],
targetNjIds: [],
targetBjIds: [],
signatureRequired: false,
startTime: "",
endTime: "",
});
const signatureOptions = ["不启用", "启用"];
const signatureStatusText = computed(() => {
return formData.signatureRequired ? "启用" : "不启用";
});
// 树形数据
const treeData = ref([]);
const treeRef = ref();
// 学生显示相关
const maxDisplayCount = 24; // 最多显示24个学生4行×6列
const showStudentModal = ref(false);
// 计算显示的学生名单最多显示前24个
const displayNames = computed(() => {
return formData.targetNames.slice(0, maxDisplayCount);
});
// 加载树形数据
const loadTreeData = async () => {
try {
const res = await findAllNjBjTreeApi();
if (res.resultCode === 1 && res.result) {
// 转换数据格式以适配 BasicTree 组件
treeData.value = res.result.map((item: any) => ({
key: item.key,
title: item.title,
children: item.children || [],
}));
console.log("树形数据加载成功:", treeData.value);
}
} catch (error) {
console.error("加载树形数据失败:", error);
uni.showToast({ title: "加载班级数据失败", icon: "error" });
}
};
// 在组件挂载时加载数据
loadTreeData();
const fetchStudentsByClass = async (className: string): Promise<string[]> => {
console.log(`模拟获取班级 [${className}] 的学生列表...`);
isLoadingStudents.value = true;
await new Promise((resolve) => setTimeout(resolve, 400));
const mockStudents = [
"施延兴",
"安苒溪",
"罗浩晨",
"康萌",
"范文昊",
"丁贺祥",
"韦运昊",
"萧润丽",
"谢林",
"鲍泽远",
"杨俊",
];
isLoadingStudents.value = false;
return mockStudents;
};
onLoad((options) => {});
const handleCoverSelected = async (e: any) => {
if (e.tempFilePaths && e.tempFilePaths.length > 0) {
const tempFilePath = e.tempFilePaths[0];
try {
uni.showLoading({ title: "上传中..." });
// 直接使用 tempFilePath 作为 Blob 传给接口
const uploadResult: any = await attachmentUpload(tempFilePath as any);
if (
uploadResult.resultCode === 1 &&
uploadResult.result &&
uploadResult.result.length > 0
) {
// 保存原始的 filePath用于提交到服务器
const originalPath = uploadResult.result[0].filePath;
formData.coverImage = originalPath;
uni.showToast({ title: "封面上传成功", icon: "success" });
} else {
throw new Error("上传响应格式异常");
}
} catch (error) {
uni.showToast({ title: "封面上传失败", icon: "error" });
formData.coverImage = null;
} finally {
uni.hideLoading();
}
} else {
console.error("无法从选择事件中获取封面路径:", e);
}
};
const handleCoverClosed = (field: string) => {
if (field === "coverImage") {
formData.coverImage = null;
}
};
const addAttachment = () => {
uni.chooseFile({
count: 5,
type: "all",
success: async (res) => {
const tempFiles = res.tempFiles;
if (Array.isArray(tempFiles) && tempFiles.length > 0) {
uni.showLoading({ title: "上传中..." });
try {
for (const file of tempFiles) {
const fileInfo = file as any; // 强制类型转换避免类型检查错误
let fileType = "file";
const fileName = fileInfo.name || "";
const fileExtension = fileName.split(".").pop()?.toLowerCase();
if (
["png", "jpg", "jpeg", "gif", "bmp", "webp"].includes(
fileExtension || ""
)
) {
fileType = "image";
} else if (
["mp4", "mov", "avi", "wmv", "flv"].includes(fileExtension || "")
) {
fileType = "video";
} else if (
["mp3", "wav", "aac", "ogg"].includes(fileExtension || "")
) {
fileType = "audio";
}
if (
fileInfo.type &&
typeof fileInfo.type === "string" &&
(fileInfo.type.startsWith("image/") ||
fileInfo.type.startsWith("video/") ||
fileInfo.type.startsWith("audio/"))
) {
fileType = fileInfo.type.split("/")[0];
}
// 直接使用文件路径作为 Blob 传给接口
const uploadResult: any = await attachmentUpload(
fileInfo.path as any
);
if (
uploadResult.resultCode === 1 &&
uploadResult.result &&
uploadResult.result.length > 0
) {
// 保存原始的 filePath用于提交到服务器
const originalPath = uploadResult.result[0].filePath;
formData.attachments.push({
name: fileName,
type: fileType,
url: originalPath, // 保存原始路径
size: fileInfo.size,
});
} else {
uni.showToast({ title: `${fileName} 上传失败`, icon: "error" });
}
}
uni.showToast({ title: "附件上传完成", icon: "success" });
} catch (error) {
console.error("附件上传失败:", error);
uni.showToast({ title: "附件上传失败", icon: "error" });
} finally {
uni.hideLoading();
}
} else {
console.log("未选择任何文件或返回结果异常,或 tempFiles 不是数组");
}
},
fail: (err) => {
console.error("选择附件失败:", err);
if (err.errMsg && !err.errMsg.includes("cancel")) {
uni.showToast({ title: "选择附件失败", icon: "none" });
}
},
});
};
const removeAttachment = (index: number) => {
formData.attachments.splice(index, 1);
};
const getAttachmentIcon = (type: string): string => {
if (type === "image") return "image";
if (type === "video") return "videocam";
if (type === "audio") return "mic";
return "paperclip";
};
const previewAttachment = (attachment: Attachment) => {
console.log("预览附件:", attachment);
// 如果是图片类型,可以预览
if (attachment.type === "image") {
const fullUrl = imagUrl(attachment.url);
uni.previewImage({
urls: [fullUrl],
current: fullUrl,
});
} else {
uni.showToast({
title: `预览 ${attachment.name} 功能待实现`,
icon: "none",
});
}
};
// 显示班级选择树
const showClassTree = () => {
if (treeRef.value) {
treeRef.value._show();
}
};
// 树形选择确认
const onTreeConfirm = async (selectedItems: any[]) => {
console.log("选择的班级:", selectedItems);
if (selectedItems.length > 0) {
// 处理多选情况
const classNames = selectedItems.map((item) => item.title);
formData.targetClass = classNames.join(", ");
formData.targetNames = [];
formData.targetStudentIds = [];
formData.targetNjIds = [];
formData.targetBjIds = [];
try {
uni.showLoading({ title: "获取学生列表中..." });
// 提取年级ID和班级ID
const njIds: string[] = [];
const bjIds: string[] = [];
selectedItems.forEach((item) => {
// 如果选择的是班级有parents表示是班级
if (item.parents && item.parents.length > 0) {
const njId = item.parents[0].key; // 年级IDparents[0].key
const bjId = item.key; // 班级IDitem.key
njIds.push(njId);
bjIds.push(bjId);
console.log(
`选择班级: ${item.title}, 年级ID: ${njId}, 班级ID: ${bjId}`
);
}
});
if (njIds.length > 0 && bjIds.length > 0) {
// 对年级ID去重
const uniqueNjIds = [...new Set(njIds)];
// 保存选择的年级ID和班级ID到formData中
formData.targetNjIds = uniqueNjIds;
formData.targetBjIds = bjIds;
// 调用接口获取学生列表
const params = {
njId: uniqueNjIds.join(","),
bjId: bjIds.join(","),
};
const response = await mobilejlstudentListApi(params);
if (response && response.resultCode === 1 && response.result) {
// 提取学生姓名,尝试多个可能的字段名
const studentNames = response.result.map(
(student: any) =>
student.xsxm ||
student.name ||
student.studentName ||
student.xm ||
"未知姓名"
);
formData.targetNames = studentNames;
formData.targetStudentIds = response.result.map(
(student: any) =>
student.id || student.xsId || student.studentId || ""
);
} else {
throw new Error("获取学生列表失败");
}
}
uni.hideLoading();
uni.showToast({
title: `已选择 ${selectedItems.length} 个班级`,
icon: "success",
});
} catch (error) {
console.error("获取学生列表失败:", error);
uni.hideLoading();
uni.showToast({ title: "获取学生列表失败", icon: "error" });
// 如果接口失败,使用模拟数据
formData.targetNames = await fetchStudentsByClass(formData.targetClass);
}
}
};
// 树形选择取消
const onTreeCancel = () => {
console.log("取消选择班级");
};
// 显示全部学生
const showAllStudents = () => {
showStudentModal.value = true;
};
// 关闭学生弹窗
const closeStudentModal = () => {
showStudentModal.value = false;
};
const handleSignatureChange = (e: any) => {
const index = e.detail.value;
formData.signatureRequired = index == 1;
};
const validateForm = (): boolean => {
if (!formData.title.trim()) {
uni.showToast({ title: "请输入通知标题", icon: "none" });
return false;
}
if (!formData.content.trim()) {
uni.showToast({ title: "请输入通知内容", icon: "none" });
return false;
}
if (
formData.startTime &&
formData.endTime &&
formData.startTime >= formData.endTime
) {
uni.showToast({ title: "结束时间必须晚于开始时间", icon: "none" });
return false;
}
return true;
};
const saveDraft = () => {
if (!validateForm()) return;
console.log("保存草稿", formData);
uni.showToast({ title: "草稿保存成功 (模拟)", icon: "success" });
};
const previewNotice = () => {
if (!validateForm()) return;
console.log("预览通知", formData);
uni.showToast({ title: "预览功能待实现", icon: "none" });
};
const publishNotice = () => {
if (!validateForm()) return;
// 准备发布数据包含年级ID和班级ID
const publishData = {
...formData,
njIds: formData.targetNjIds.join(","), // 年级ID字符串
bjIds: formData.targetBjIds.join(","), // 班级ID字符串
};
console.log("发布通知数据:", publishData);
console.log("年级ID:", publishData.njIds);
console.log("班级ID:", publishData.bjIds);
// TODO: 调用发布接口
// uni.showLoading({ title: "发布中..." });
// setTimeout(() => {
// uni.hideLoading();
// uni.showToast({ title: "发布成功 (模拟)", icon: "success" });
// uni.navigateBack();
// }, 1000);
};
</script>
<style scoped lang="scss">
/* Remove original page container styles */
/*
.notice-publish-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f4f5f7;
}
*/
.form-scroll-view {
/* Let BasicLayout handle height/flex */
/* flex: 1; */
/* height: 0; */
/* BasicLayout default slot might have padding, adjust if needed */
/* Or keep the scroll view if BasicLayout doesn't provide one */
height: 100%; // Assume BasicLayout's default slot needs this
box-sizing: border-box;
}
.form-container {
padding: 12px;
box-sizing: border-box;
/* Add padding-bottom if content gets hidden by bottom bar */
/* padding-bottom: 70px; */
}
.info-card {
background-color: #ffffff;
border-radius: 8px;
padding: 15px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.main-content-card {
padding: 5px 15px;
.form-item {
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
padding-bottom: 5px;
}
&:first-child {
padding-top: 10px;
}
}
.cover-section {
padding-bottom: 10px;
.cover-header {
display: flex;
align-items: baseline;
margin-bottom: 10px;
width: 100%;
}
.form-label {
width: auto;
margin-right: 8px;
color: #303133;
font-size: 15px;
font-weight: 500;
flex-shrink: 0;
}
.cover-hint {
font-size: 12px;
color: #909399;
margin-right: auto;
padding-left: 5px;
}
.cover-counter {
font-size: 14px;
color: #c0c4cc;
flex-shrink: 0;
}
.cover-upload-wrapper {
width: 100%;
height: 150px;
border-radius: 6px;
overflow: hidden;
border: 1px dashed #dcdcdc;
background-color: #f8f8f8;
}
.cover-placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: #b0b0b0;
font-size: 14px;
box-sizing: border-box;
cursor: pointer;
text {
margin-top: 8px;
}
.uni-icons {
margin-bottom: 4px;
}
}
}
.title-input {
padding-bottom: 5px;
}
.content-input {
padding-top: 5px;
}
.title-input ::v-deep .uni-easyinput__content-textarea {
font-size: 18px !important;
font-weight: bold !important;
color: #303133 !important;
padding: 0 !important;
line-height: 1.5;
}
.content-input ::v-deep .uni-easyinput__content-textarea {
font-size: 15px !important;
color: #606266 !important;
padding: 0 !important;
line-height: 1.6;
min-height: 80px;
}
.attachments-section {
padding-top: 15px;
.form-label {
display: block;
color: #333;
font-size: 15px;
margin-bottom: 12px;
}
.attachment-list {
margin-bottom: 12px;
}
.attachment-item {
display: flex;
align-items: center;
background-color: #f8f9fa;
border-radius: 6px;
padding: 8px 12px;
margin-bottom: 8px;
border: 1px solid #e9ecef;
.attachment-icon {
margin-right: 8px;
flex-shrink: 0;
}
.attachment-name {
flex-grow: 1;
font-size: 14px;
color: #495057;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
cursor: pointer;
}
.remove-icon {
flex-shrink: 0;
cursor: pointer;
opacity: 0.7;
&:hover {
opacity: 1;
color: #dc3545 !important;
}
}
}
.add-attachment-placeholder {
display: flex;
align-items: center;
border: 1px dashed #d5d8de;
border-radius: 6px;
padding: 15px;
background-color: #f8f8f8;
cursor: pointer;
transition: background-color 0.2s;
.add-icon {
width: 30px;
height: 30px;
border: 1px solid #d5d8de;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 12px;
background-color: #fff;
.uni-icons {
color: #999 !important;
}
}
.placeholder-text {
font-size: 14px;
color: #909399;
}
&:active {
background-color: #eee;
}
}
}
}
.info-card picker {
width: 100%;
}
.picker-header {
margin-bottom: 0;
padding: 14px 0;
border-bottom: 1px solid #f0f0f0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
.section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
padding-left: 15px;
}
.target-class {
display: flex;
align-items: center;
font-size: 14px;
color: #606266;
padding-right: 15px;
text {
margin-right: 5px;
&.placeholder {
color: #909399;
}
}
.uni-icons {
color: #909399 !important;
}
}
}
.name-tags {
padding: 15px;
margin-top: 0;
display: flex;
flex-wrap: wrap;
gap: 8px 10px;
position: relative;
min-height: 30px;
.name-tag {
font-size: 13px;
padding: 5px 0;
border-radius: 4px;
text-align: center;
flex-grow: 0;
flex-shrink: 0;
box-sizing: border-box;
flex-basis: calc((100% - 50px) / 6);
height: 28px;
line-height: 18px;
background-color: #f4f4f5;
color: #909399;
}
.loading-spinner {
position: absolute;
top: 5px;
left: 15px;
font-size: 12px;
color: #999;
}
}
.more-btn-container {
width: 100%;
}
.more-btn-full {
width: 100%;
height: 35px;
background-color: #fff2e8;
color: #e6a23c;
border: 1px solid #f5dab1;
border-radius: 4px;
font-size: 14px;
&::after {
border: none;
}
&:active {
background-color: #ffecd1;
}
}
.list-item-card {
padding: 0;
.uni-datetime-picker,
.picker {
width: 100%;
}
}
.list-item-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 15px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s;
&:active {
background-color: #fafafa;
}
&.no-border {
border-bottom: none;
}
.list-label {
font-size: 15px;
color: #303133;
}
.list-value {
display: flex;
align-items: center;
font-size: 15px;
color: #909399;
text {
margin-right: 5px;
color: #606266;
&.placeholder {
color: #909399;
}
}
.uni-icons {
color: #c0c4cc !important;
}
}
uni-datetime-picker .list-value text,
picker .list-value text {
color: #606266;
}
uni-datetime-picker .list-value text:empty::before,
picker .list-value text:empty::before {
content: "请选择";
color: #909399;
}
picker .list-value text {
color: #606266;
}
}
.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: auto;
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;
}
}
}
.target-class text.placeholder {
color: #909399;
}
/* 学生名单弹窗样式 */
.student-modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
.student-modal {
width: 90%;
max-height: 80%;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.student-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
background-color: #fafafa;
}
.modal-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
.student-count {
font-size: 14px;
color: #909399;
}
.close-icon {
cursor: pointer;
padding: 5px;
}
.student-modal-content {
max-height: 400px;
padding: 20px;
}
.all-student-tags {
display: flex;
flex-wrap: wrap;
gap: 8px 10px;
}
.student-tag {
font-size: 13px;
padding: 6px 12px;
border-radius: 4px;
text-align: center;
background-color: #f4f4f5;
color: #909399;
min-width: 60px;
box-sizing: border-box;
}
</style>