吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3674|回复: 30
收起左侧

[其他原创] AI 智能教案生成器 - 手写试题生成系统 - VUE源码

  [复制链接]
唐朝 发表于 2026-1-19 09:21
本帖最后由 唐朝 于 2026-1-19 09:25 编辑

基于 Vue 3 + Vite 开发的现代化教师辅助工具,旨在提升备课与教学效率。
ops-coffee-1768785552303.png


演示地址:https://www.ytecn.com/teacher/
开源地址:https://github.com/tcshowhand/teacher

核心功能智能教案生成 (AI Lesson Planning)
基于 AI 快速生成结构化教案,涵盖教学目标、重难点、教学过程等完整环节。支持从幼儿园到研究生不同教育阶段的定制化生成,满足多样化教学需求。提供所见即所得的富文本编辑器,支持对生成内容的二次修改与优化。一键导出:支持将教案一键导出为标准 Word (.docx) 格式,保留排版,方便打印与分享。
专业试卷编辑器 (Exam Editor)
提供直观的在线试卷编辑界面,支持多种题型(单选、多选、填空、简答等)的灵活创建与管理。实时预览试卷效果,所见即所得。PDF 导出:支持将试卷生成并导出为高质量 PDF 文件,直接用于考试分发。
AI 智能助手 (AI Assistant)
内置强大的 AI 聊天功能,随时辅助解答教学过程中的疑难问题。支持对教学内容进行润色、扩充和优化,提升教学质量。根据教学上下文进行针对性互动,成为您的贴身教学顾问。
用户系统与服务 (User & Services)
个性化设置:支持配置 AI 模型参数、API 密钥等,打造专属的教学辅助环境。定制服务:提供教案模板定制等增值服务,满足个性化、专业化的教学场景需求。


快速开始
1. 安装依赖
npm install
2. 启动开发服务器
npm run dev
3. 构建生产版本
npm run build

核心代码
AIChatAssistant
[Asm] 纯文本查看 复制代码
<script setup>
import { ref, nextTick, watch } from 'vue'
import { sendToQwenAIDialogue } from '../api/qwenAPI'
import { marked } from 'marked'

const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  currentContent: {
    type: [Object, Array],
    required: true
  },
  systemContext: {
    type: String,
    default: '您是一个专业的教案编写助手。'
  }
})

const emit = defineEmits(['update:modelValue', 'update-content'])

const inputMessage = ref('')
const isLoading = ref(false)
const messages = ref([
  {
    role: 'assistant',
    content: '你好!我是您的智能助手。请告诉我您想如何调整当前内容?'
  }
])
const chatContainer = ref(null)

// Auto-scroll to bottom
const scrollToBottom = async () => {
  await nextTick()
  if (chatContainer.value) {
    chatContainer.value.scrollTop = chatContainer.value.scrollHeight
  }
}

watch(messages, scrollToBottom, { deep: true })
watch(() => props.modelValue, (val) => {
  if (val) scrollToBottom()
})

const close = () => {
  emit('update:modelValue', false)
}

const sendMessage = async () => {
  if (!inputMessage.value.trim() || isLoading.value) return

  const userMsg = inputMessage.value
  messages.value.push({ role: 'user', content: userMsg })
  inputMessage.value = ''
  isLoading.value = true

  // Determine expected type
  const isArray = Array.isArray(props.currentContent)
  const expectedFormat = isArray ? 'JSON 数组 ([...])' : 'JSON 对象 ({...})'

  // Construct System Prompt
  const prompt = `${props.systemContext}
当前内容的 JSON 数据如下:
${JSON.stringify(props.currentContent)}

用户的指令是:${userMsg}

请根据用户的指令修改上述内容。
如果需要修改,请按照以下格式回复:
1. 先回复一句简短的确认。
2. 然后在新的行中,使用 markdown 代码块输出完整的、合法的 JSON 数据。
**重要:请务必返回一个标准的 ${expectedFormat},不要改变最外层的数据结构类型。**

格式示例:
好的,我已为您增加了章节。
\`\`\`json
{ "key": "value" }
\`\`\`

如果用户的指令不是修改内容,请正常回答,不要包含 JSON。
`

  // Send to AI
  const conversation = [
    { role: 'user', content: prompt }
  ]

  let fullResponse = ''
  
  // Placeholder for streaming
  const assistantMsgIndex = messages.value.push({
    role: 'assistant',
    content: '...'
  }) - 1

  await sendToQwenAIDialogue(conversation, (text, isComplete) => {
    fullResponse = text
    
    if (isComplete) {
      isLoading.value = false
      
      // 1. Try to extract JSON code block (more robust regex)
      // Matches ```json OR ``` followed by content and ```
      const jsonBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/i
      const match = fullResponse.match(jsonBlockRegex)
      
      let jsonContent = null
      let displayText = fullResponse

      const parseAndSet = (jsonStr) => {
          try {
              // Simple comment stripper: remove lines starting with whitespace+//
              const cleanStr = jsonStr.replace(/^\s*\/\/.*$/gm, '')
              return JSON.parse(cleanStr)
          } catch (e) {
              console.error('JSON parse error', e)
              return null
          }
      }

      if (match) {
        // Found JSON block
        jsonContent = parseAndSet(match[1])
        if (jsonContent) {
           displayText = fullResponse.replace(match[0], '').trim()
           if (!displayText) displayText = '好的,已根据您的要求完成调整。'
        } else {
           displayText += '\n\n(&#10060; 自动更新失败:AI 返回的格式不正确)'
        }
      } else {
        // Fallback: Try to find standalone JSON object
        const firstBrace = fullResponse.indexOf('{')
        const lastBrace = fullResponse.lastIndexOf('}')
        if (firstBrace >= 0 && lastBrace > firstBrace) {
             const potentialJson = fullResponse.substring(firstBrace, lastBrace + 1)
             jsonContent = parseAndSet(potentialJson)
             if (jsonContent) {
                displayText = fullResponse.substring(0, firstBrace).trim()
                if (!displayText) displayText = '好的,已为您调整。'
             }
        }
      }

      // Update Chat UI
      messages.value[assistantMsgIndex].content = displayText

      // Apply Update
      if (jsonContent) {
        emit('update-content', jsonContent)
      }

    } else {
       // Streaming updates
       if (fullResponse.includes('```')) {
          const preText = fullResponse.split('```')[0].trim()
          messages.value[assistantMsgIndex].content = preText + '\n(正在生成数据...)'
       } else {
          messages.value[assistantMsgIndex].content = fullResponse
       }
    }
  })
}

// Render markdown safely
const renderMarkdown = (text) => {
  return marked.parse(text || '')
}
</script>

<template>
  <div class="chat-overlay" v-if="modelValue">
    <div class="chat-window">
      <div class="chat-header">
        <h3>&#129302; 教案助手</h3>
        <button class="close-btn" @click="close">×</button>
      </div>
      
      <div class="chat-messages" ref="chatContainer">
        <div 
          v-for="(msg, index) in messages" 
          :key="index" 
          class="message"
          :class="msg.role"
        >
          <div class="avatar">{{ msg.role === 'user' ? '&#128100;' : '&#129302;' }}</div>
          <div class="bubble" v-html="renderMarkdown(msg.content)"></div>
        </div>
      </div>
      
      <div class="chat-input-area">
        <textarea 
          v-model="inputMessage" 
          placeholder="输入修改指令..." 
          @keydown.enter.prevent="sendMessage"
        ></textarea>
        <button @click="sendMessage" :disabled="isLoading">
          {{ isLoading ? '...' : '发送' }}
        </button>
      </div>
    </div>
  </div>
</template>

<style scoped>
.chat-overlay {
  position: fixed;
  bottom: 80px; /* Above the FAB */
  right: 20px;
  width: 350px;
  height: 500px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 5px 20px rgba(0,0,0,0.2);
  z-index: 1000;
  border: 2px solid #2c3e50;
  font-family: 'Architects Daughter', cursive, sans-serif;
  animation: slideUp 0.3s ease-out;
}

.chat-window {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}


@keyframes slideUp {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

.chat-header {
  padding: 10px 15px;
  background: #2c3e50;
  color: white;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.chat-header h3 {
  margin: 0;
  font-size: 1.1em;
}

.close-btn {
  background: transparent;
  border: none;
  color: white;
  font-size: 1.5em;
  cursor: pointer;
  line-height: 1;
}

.chat-messages {
  flex: 1;
  padding: 15px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 15px;
  background: #fdfbf7;
}

.message {
  display: flex;
  gap: 10px;
  max-width: 90%;
}

.message.user {
  flex-direction: row-reverse;
  align-self: flex-end;
}

.avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #ddd;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2em;
  flex-shrink: 0;
  border: 1px solid #999;
}

.message.assistant .avatar {
  background: #e1f5fe;
  border-color: #4fc3f7;
}

.message.user .avatar {
  background: #fff9c4;
  border-color: #fbc02d;
}

.bubble {
  background: white;
  padding: 8px 12px;
  border-radius: 10px;
  border: 1px solid #ccc;
  font-size: 0.95em;
  line-height: 1.4;
  word-wrap: break-word;
  box-shadow: 2px 2px 0 rgba(0,0,0,0.05);
}

.message.user .bubble {
  background: #e8f5e9;
  border-color: #a5d6a7;
  border-top-right-radius: 2px;
}

.message.assistant .bubble {
  background: #fff;
  border-top-left-radius: 2px;
}

.chat-input-area {
  padding: 10px;
  border-top: 1px solid #eee;
  display: flex;
  gap: 10px;
  background: #fff;
}

textarea {
  flex: 1;
  resize: none;
  height: 40px;
  padding: 8px;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-family: inherit;
  outline: none;
}

textarea:focus {
  border-color: #2c3e50;
}

button {
  padding: 0 15px;
  background: #2c3e50;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: bold;
  font-family: inherit;
}

button:disabled {
  background: #ccc;
  cursor: wait;
}

/* Markdown Styles inside bubble */
.bubble :deep(p) { margin: 0 0 5px 0; }
.bubble :deep(p:last-child) { margin-bottom: 0; }
.bubble :deep(ul), .bubble :deep(ol) { margin: 5px 0; padding-left: 20px; }
</style>

免费评分

参与人数 3吾爱币 +9 热心值 +2 收起 理由
lyk1995 + 1 谢谢@Thanks!
kingc138 + 1 + 1 我很赞同!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

eyu8465 发表于 2026-1-19 10:45
老师、家长都会感谢你的,学生感谢不感谢你我就不知道了
 楼主| 唐朝 发表于 2026-1-24 22:44
qq7286590 发表于 2026-1-22 22:05
用TREA修改了代码,实现调用本地OLLAMA的API来生成教案,免费白嫖,

啊,可以贡献到git啊,独乐乐不如众乐乐。可以给权限。
jiukou 发表于 2026-1-19 10:48
chakongtan 发表于 2026-1-19 10:59
立马下下来申请试试,谢谢分享!
zt185 发表于 2026-1-19 11:10
这个工具不错,老师的福星!
gongfugao 发表于 2026-1-19 11:11
谢谢,下载来试试
zqlyc 发表于 2026-1-19 11:32
不大会使用,有点可惜了
天天哈皮 发表于 2026-1-19 11:39
老师实用工具
liyicha 发表于 2026-1-19 11:54
牛,感谢分享
buzaizheli 发表于 2026-1-19 13:21
怎么下载,谁给个绿色下载通道谢谢。帮转存一个好下载的连接
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-30 07:26

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表