hookehuyr

feat(打卡): 移除留言必填校验并支持富文本显示

- 移除打卡留言的必填校验,改为可选输入
- 修改界面文案"打卡内容"为"打卡留言"
- 为作业描述添加v-html支持以显示富文本内容
......@@ -105,7 +105,7 @@ export function useCheckin() {
if (tokenResult.token) {
const suffix = /.[^.]+$/.exec(file.file.name) || ''
let fileName = ''
if (activeType.value === 'image') {
fileName = `mlaj/upload/checkin/${currentUser.value.mobile}/img/${md5}${suffix}`
} else {
......@@ -164,7 +164,7 @@ export function useCheckin() {
// 检查文件类型和大小
for (const item of files) {
const fileType = item.type.toLowerCase()
// 文件大小检查
if ((item.size / 1024 / 1024).toFixed(2) > 20) {
flag = false
......@@ -209,7 +209,7 @@ export function useCheckin() {
for (const item of files) {
item.status = 'uploading'
item.message = '上传中...'
const result = await handleUpload(item)
if (result) {
item.status = 'done'
......@@ -264,10 +264,10 @@ export function useCheckin() {
showToast('请先上传文件')
return
}
if (message.value.trim() === '') {
showToast('请输入打卡留言')
return
}
// if (message.value.trim() === '') {
// showToast('请输入打卡留言')
// return
// }
}
uploading.value = true
......@@ -350,7 +350,7 @@ export function useCheckin() {
if (code) {
message.value = data.note || ''
activeType.value = data.file_type || 'text'
// 如果有文件数据,初始化文件列表 - 使用data.files而不是data.meta
if (data.files && data.files.length > 0) {
fileList.value = data.files.map(item => {
......@@ -361,17 +361,17 @@ export function useCheckin() {
meta_id: item.meta_id,
name: item.name || ''
}
// 对于图片类型,添加isImage标记确保正确显示
if (activeType.value === 'image') {
fileItem.isImage = true
}
// 为了支持文件名显示,创建一个File对象
if (item.name) {
fileItem.file = new File([], item.name, { type: item.type || '' })
}
return fileItem
})
}
......@@ -391,7 +391,7 @@ export function useCheckin() {
activeType,
maxCount,
canSubmit,
// 方法
beforeRead,
afterRead,
......@@ -402,4 +402,4 @@ export function useCheckin() {
resetForm,
initEditData
}
}
\ No newline at end of file
}
......
......@@ -6,8 +6,7 @@
<div class="section-wrapper">
<div class="section-title">作业描述</div>
<div class="section-content">
<div v-if="taskDetail.description" class="description-text">
{{ taskDetail.description }}
<div v-if="taskDetail.description" class="description-text" v-html="taskDetail.description">
</div>
<div v-else class="no-description">
暂无作业描述
......@@ -17,7 +16,7 @@
<!-- 打卡内容区域 -->
<div class="section-wrapper">
<div class="section-title">打卡内容</div>
<div class="section-title">打卡留言</div>
<div class="section-content">
<!-- 文本输入区域 -->
<div class="text-input-area">
......@@ -26,7 +25,7 @@
rows="6"
autosize
type="textarea"
:placeholder="activeType === 'text' ? '请输入打卡内容,至少需要10个字符' : '请输入打卡留言(可选)'"
:placeholder="activeType === 'text' ? '请输入打卡留言,至少需要10个字符' : '请输入打卡留言(可选)'"
:maxlength="activeType === 'text' ? 500 : 200"
show-word-limit
/>
......@@ -229,6 +228,79 @@ const getTaskDetail = async (month) => {
const { code, data } = await getTaskDetailAPI({ i: route.query.id, month })
if (code) {
taskDetail.value = data
// TODO: 临时 mock 富文本数据,后续替换为真实数据
// if (!taskDetail.value.description) {
// taskDetail.value.description = `
// <div class="rich-content">
// <h2 style="color: #2563eb; margin-bottom: 16px; font-size: 20px; font-weight: 600;">📚 本周学习任务</h2>
// <p style="margin-bottom: 16px; line-height: 1.6; color: #374151;">
// 同学们好!本周我们将学习<strong style="color: #dc2626;">Vue3 组合式 API</strong>的核心概念。请大家认真完成以下任务:
// </p>
// <div style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); padding: 16px; border-radius: 12px; margin: 16px 0; border-left: 4px solid #f59e0b;">
// <h3 style="color: #92400e; margin: 0 0 8px 0; font-size: 16px;">⚠️ 重要提醒</h3>
// <p style="margin: 0; color: #78350f;">请在截止日期前提交作业,逾期将影响成绩评定。</p>
// </div>
// <h3 style="color: #059669; margin: 24px 0 12px 0; font-size: 18px;">📋 具体要求:</h3>
// <ol style="margin: 16px 0; padding-left: 24px; color: #374151; line-height: 1.8;">
// <li style="margin-bottom: 8px;">
// <strong>理论学习:</strong>观看课程视频,理解 <code style="background: #f3f4f6; padding: 2px 6px; border-radius: 4px; color: #dc2626;">ref</code> 和 <code style="background: #f3f4f6; padding: 2px 6px; border-radius: 4px; color: #dc2626;">reactive</code> 的区别
// </li>
// <li style="margin-bottom: 8px;">
// <strong>实践操作:</strong>完成课后练习,创建一个简单的计数器组件
// </li>
// <li style="margin-bottom: 8px;">
// <strong>拓展思考:</strong>思考组合式 API 相比选项式 API 的优势
// </li>
// </ol>
// <div style="text-align: center; margin: 24px 0;">
// <img src="https://cdn.ipadbiz.cn/mlaj/images/vue3-composition-api.jpg"
// alt="Vue3 组合式 API 示意图"
// style="max-width: 100%; height: auto; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" />
// <p style="margin-top: 8px; color: #6b7280; font-size: 14px; font-style: italic;">
// Vue3 组合式 API 架构图
// </p>
// </div>
// <div style="background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%); padding: 16px; border-radius: 12px; margin: 20px 0;">
// <h4 style="color: #1e40af; margin: 0 0 12px 0; font-size: 16px;">💡 学习提示</h4>
// <ul style="margin: 0; padding-left: 20px; color: #1e3a8a;">
// <li style="margin-bottom: 6px;">可以参考官方文档进行学习</li>
// <li style="margin-bottom: 6px;">建议先理解概念,再动手实践</li>
// <li style="margin-bottom: 6px;">遇到问题可以在群里讨论</li>
// </ul>
// </div>
// <blockquote style="border-left: 4px solid #10b981; background: #f0fdf4; padding: 16px; margin: 20px 0; border-radius: 0 8px 8px 0;">
// <p style="margin: 0; color: #065f46; font-style: italic; line-height: 1.6;">
// "学而时习之,不亦说乎?" —— 希望大家能够在实践中加深对知识的理解。
// </p>
// </blockquote>
// <div style="text-align: center; margin: 24px 0;">
// <img src="https://cdn.ipadbiz.cn/mlaj/images/coding-example.png"
// alt="代码示例"
// style="max-width: 100%; height: auto; border-radius: 8px; border: 2px solid #e5e7eb;" />
// </div>
// <div style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; padding: 16px; margin: 20px 0;">
// <h4 style="color: #dc2626; margin: 0 0 8px 0; font-size: 16px;">📅 截止时间</h4>
// <p style="margin: 0; color: #991b1b; font-weight: 500;">
// 2024年12月31日 23:59
// </p>
// </div>
// <p style="margin-top: 24px; color: #6b7280; text-align: center; font-size: 14px;">
// 祝大家学习愉快!有问题随时联系老师 👨‍🏫
// </p>
// </div>
// `
// }
}
}
......@@ -297,6 +369,121 @@ onMounted(async () => {
color: #666;
line-height: 1.6;
font-size: 0.95rem;
// 富文本内容样式
// :deep(.rich-content) {
// h1, h2, h3, h4, h5, h6 {
// margin: 16px 0 12px 0;
// font-weight: 600;
// line-height: 1.4;
// }
// h2 {
// font-size: 20px;
// color: #2563eb;
// }
// h3 {
// font-size: 18px;
// color: #059669;
// }
// h4 {
// font-size: 16px;
// }
// p {
// margin: 12px 0;
// line-height: 1.6;
// color: #374151;
// }
// strong {
// font-weight: 600;
// color: #dc2626;
// }
// code {
// background: #f3f4f6;
// padding: 2px 6px;
// border-radius: 4px;
// color: #dc2626;
// font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
// font-size: 0.9em;
// }
// ol, ul {
// margin: 16px 0;
// padding-left: 24px;
// li {
// margin-bottom: 8px;
// line-height: 1.8;
// }
// }
// blockquote {
// border-left: 4px solid #10b981;
// background: #f0fdf4;
// padding: 16px;
// margin: 20px 0;
// border-radius: 0 8px 8px 0;
// p {
// margin: 0;
// color: #065f46;
// font-style: italic;
// }
// }
// img {
// max-width: 100%;
// height: auto;
// border-radius: 8px;
// margin: 12px 0;
// display: block;
// }
// // 特殊样式容器
// .warning-box {
// background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
// padding: 16px;
// border-radius: 12px;
// margin: 16px 0;
// border-left: 4px solid #f59e0b;
// }
// .info-box {
// background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
// padding: 16px;
// border-radius: 12px;
// margin: 20px 0;
// }
// .deadline-box {
// background: #fef2f2;
// border: 1px solid #fecaca;
// border-radius: 8px;
// padding: 16px;
// margin: 20px 0;
// }
// // 图片容器居中
// div[style*="text-align: center"] {
// text-align: center;
// img {
// margin: 12px auto;
// }
// p {
// color: #6b7280;
// font-size: 14px;
// font-style: italic;
// margin-top: 8px;
// }
// }
// }
}
.no-description {
......