组件拆分重构 App.vue

先这样拆解 不然越来越乱

css 复制代码
src/
  components/
    StudentBar.vue
    TabNav.vue
    SolvePanel.vue
    HistoryPanel.vue
    WrongPanel.vue

1)新增 src/components/StudentBar.vue

xml 复制代码
<template>
  <div class="student-bar">
    <select
      :value="currentStudentId"
      class="student-select"
      @change="onStudentChange"
    >
      <option v-for="item in studentList" :key="item.id" :value="item.id">
        {{ item.name }}
      </option>
    </select>

    <input
      :value="newStudentName"
      class="student-input"
      placeholder="输入新学生姓名"
      @input="onNameInput"
    />

    <button class="retry-btn" @click="$emit('create-student')">
      新增学生
    </button>

    <button class="wrong-btn" @click="$emit('export-report')">
      导出练习单
    </button>
  </div>
</template>

<script setup lang="ts">
import type { StudentItem } from '../api/math'

defineProps<{
  studentList: StudentItem[]
  currentStudentId: number
  newStudentName: string
}>()

const emit = defineEmits<{
  (e: 'update:currentStudentId', value: number): void
  (e: 'update:newStudentName', value: string): void
  (e: 'student-change'): void
  (e: 'create-student'): void
  (e: 'export-report'): void
}>()

const onStudentChange = (event: Event) => {
  const value = Number((event.target as HTMLSelectElement).value)
  emit('update:currentStudentId', value)
  emit('student-change')
}

const onNameInput = (event: Event) => {
  emit('update:newStudentName', (event.target as HTMLInputElement).value)
}
</script>

<style scoped>
.student-bar {
  display: flex;
  gap: 12px;
  align-items: center;
  margin-bottom: 20px;
}

.student-select,
.student-input {
  height: 40px;
  padding: 0 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  background: #fff;
  outline: none;
}

.retry-btn {
  padding: 8px 14px;
  border: none;
  background: #2080f0;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.wrong-btn {
  padding: 8px 14px;
  border: none;
  background: #f0a020;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}
</style>

2)新增 src/components/TabNav.vue

xml 复制代码
<template>
  <div class="tabs">
    <button
      v-for="item in tabList"
      :key="item.value"
      :class="['tab-btn', activeTab === item.value ? 'active' : '']"
      @click="$emit('change', item.value)"
    >
      {{ item.label }}
    </button>
  </div>
</template>

<script setup lang="ts">
defineProps<{
  activeTab: 'solve' | 'history' | 'wrong' | 'report' | 'suggestion'
}>()

defineEmits<{
  (e: 'change', value: 'solve' | 'history' | 'wrong' | 'report' | 'suggestion'): void
}>()

const tabList = [
  { label: '题目解析', value: 'solve' },
  { label: '历史记录', value: 'history' },
  { label: '错题本', value: 'wrong' },
  { label: '学习报告', value: 'report' },
  { label: '学习建议', value: 'suggestion' },
] as const
</script>

<style scoped>
.tabs {
  display: flex;
  gap: 12px;
  margin-bottom: 20px;
}

.tab-btn {
  padding: 8px 16px;
  border: 1px solid #ddd;
  background: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.tab-btn.active {
  background: #18a058;
  color: #fff;
  border-color: #18a058;
}
</style>

3)新增 src/components/SolvePanel.vue

xml 复制代码
<template>
  <div>
    <div class="upload-area">
      <label class="upload-btn">
        {{ imageLoading ? '识别中...' : '上传题目图片' }}
        <input
          type="file"
          accept="image/*"
          class="file-input"
          :disabled="imageLoading"
          @change="$emit('image-change', $event)"
        />
      </label>
    </div>

    <textarea
      :value="question"
      class="question-input"
      placeholder="请输入一道数学题,例如:解方程 3x + 5 = 11"
      @input="$emit('update:question', ($event.target as HTMLTextAreaElement).value)"
    />

    <button class="submit-btn" @click="$emit('submit')" :disabled="loading">
      {{ loading ? '解析中...' : '开始解析' }}
    </button>

    <div v-if="result" class="result-card">
      <div class="card-header">
        <h2>本次解析结果</h2>

        <div class="card-actions">
          <button class="retry-btn" @click="$emit('regenerate', result.id)">
            {{ regenerateLoadingMap[result.id] ? '生成中...' : '再练一题' }}
          </button>

          <button class="wrong-btn" @click="$emit('toggle-wrong', result)">
            {{ result.is_wrong ? '取消错题' : '加入错题本' }}
          </button>
        </div>
      </div>

      <h3>题目</h3>
      <p>{{ result.question }}</p>

      <h3>答案</h3>
      <p>{{ result.answer }}</p>

      <h3>步骤解析</h3>
      <ol>
        <li v-for="(step, index) in result.steps" :key="index">{{ step }}</li>
      </ol>

      <h3>知识点</h3>
      <ul>
        <li v-for="(kp, index) in result.knowledge_points" :key="index">{{ kp }}</li>
      </ul>

      <h3>相似题</h3>
      <p>{{ result.similar_question }}</p>

      <div v-if="regeneratedMap[result.id]" class="regenerated-box">
        <h3>再练一题</h3>
        <p>{{ regeneratedMap[result.id].question }}</p>

        <h3>答案</h3>
        <p>{{ regeneratedMap[result.id].answer }}</p>

        <h3>步骤解析</h3>
        <ol>
          <li v-for="(step, idx) in regeneratedMap[result.id].steps" :key="idx">
            {{ step }}
          </li>
        </ol>
      </div>
    </div>

    <div class="practice-panel">
      <h2>按知识点生成练习题</h2>
      <div class="practice-form">
        <input
          :value="practiceKnowledge"
          class="practice-input"
          placeholder="请输入知识点,例如:一元一次方程"
          @input="$emit('update:practiceKnowledge', ($event.target as HTMLInputElement).value)"
        />
        <button class="submit-btn" @click="$emit('generate-practice')" :disabled="practiceLoading">
          {{ practiceLoading ? '生成中...' : '生成练习题' }}
        </button>
      </div>

      <div v-if="practiceList.length" class="practice-list">
        <div v-for="(item, index) in practiceList" :key="index" class="result-card">
          <h3>练习题 {{ index + 1 }}</h3>
          <p>{{ item.question }}</p>

          <h3>答案</h3>
          <p>{{ item.answer }}</p>

          <h3>步骤解析</h3>
          <ol>
            <li v-for="(step, idx) in item.steps" :key="idx">{{ step }}</li>
          </ol>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { PracticeQuestionItem, SolveResponse } from '../api/math'

defineProps<{
  question: string
  loading: boolean
  imageLoading: boolean
  result: (SolveResponse & { question: string }) | null
  practiceKnowledge: string
  practiceLoading: boolean
  practiceList: PracticeQuestionItem[]
  regenerateLoadingMap: Record<number, boolean>
  regeneratedMap: Record<number, PracticeQuestionItem>
}>()

defineEmits<{
  (e: 'update:question', value: string): void
  (e: 'submit'): void
  (e: 'image-change', event: Event): void
  (e: 'toggle-wrong', item: SolveResponse & { question: string }): void
  (e: 'regenerate', id: number): void
  (e: 'update:practiceKnowledge', value: string): void
  (e: 'generate-practice'): void
}>()
</script>

<style scoped>
.upload-area {
  margin-bottom: 16px;
}

.upload-btn {
  display: inline-flex;
  align-items: center;
  padding: 10px 16px;
  background: #2080f0;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.file-input {
  display: none;
}

.question-input {
  width: 100%;
  min-height: 140px;
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  resize: vertical;
  font-size: 16px;
  box-sizing: border-box;
}

.submit-btn {
  margin-top: 16px;
  padding: 10px 18px;
  border: none;
  background: #18a058;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.result-card {
  margin-top: 24px;
  padding: 20px;
  background: #fafafa;
  border-radius: 8px;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.card-actions {
  display: flex;
  gap: 8px;
}

.retry-btn {
  padding: 8px 14px;
  border: none;
  background: #2080f0;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.wrong-btn {
  padding: 8px 14px;
  border: none;
  background: #f0a020;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.practice-panel {
  margin-top: 32px;
}

.practice-form {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
}

.practice-input {
  flex: 1;
  height: 40px;
  padding: 0 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  outline: none;
}

.practice-list {
  margin-top: 16px;
}

.regenerated-box {
  margin-top: 16px;
  padding: 16px;
  background: #f0f7ff;
  border-radius: 8px;
}
</style>

4)新增 src/components/HistoryPanel.vue

xml 复制代码
<template>
  <div>
    <div v-if="historyList.length === 0" class="empty">暂无历史记录</div>

    <div v-for="item in historyList" :key="item.id" class="result-card">
      <div class="card-header">
        <h2>记录 #{{ item.id }}</h2>

        <div class="card-actions">
          <button class="retry-btn" @click="$emit('regenerate', item.id)">
            {{ regenerateLoadingMap[item.id] ? '生成中...' : '再练一题' }}
          </button>

          <button class="wrong-btn" @click="$emit('toggle-wrong', item)">
            {{ item.is_wrong ? '取消错题' : '加入错题本' }}
          </button>
        </div>
      </div>

      <h3>题目</h3>
      <p>{{ item.question }}</p>

      <h3>答案</h3>
      <p>{{ item.answer }}</p>

      <h3>步骤解析</h3>
      <ol>
        <li v-for="(step, idx) in item.steps" :key="idx">{{ step }}</li>
      </ol>

      <h3>知识点</h3>
      <ul>
        <li v-for="(kp, idx) in item.knowledge_points" :key="idx">{{ kp }}</li>
      </ul>

      <h3>相似题</h3>
      <p>{{ item.similar_question }}</p>

      <div v-if="regeneratedMap[item.id]" class="regenerated-box">
        <h3>再练一题</h3>
        <p>{{ regeneratedMap[item.id].question }}</p>

        <h3>答案</h3>
        <p>{{ regeneratedMap[item.id].answer }}</p>

        <h3>步骤解析</h3>
        <ol>
          <li v-for="(step, idx) in regeneratedMap[item.id].steps" :key="idx">
            {{ step }}
          </li>
        </ol>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { HistoryItem, PracticeQuestionItem } from '../api/math'

defineProps<{
  historyList: HistoryItem[]
  regenerateLoadingMap: Record<number, boolean>
  regeneratedMap: Record<number, PracticeQuestionItem>
}>()

defineEmits<{
  (e: 'toggle-wrong', item: HistoryItem): void
  (e: 'regenerate', id: number): void
}>()
</script>

<style scoped>
.empty {
  padding: 32px 0;
  text-align: center;
  color: #999;
}

.result-card {
  margin-top: 24px;
  padding: 20px;
  background: #fafafa;
  border-radius: 8px;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.card-actions {
  display: flex;
  gap: 8px;
}

.retry-btn {
  padding: 8px 14px;
  border: none;
  background: #2080f0;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.wrong-btn {
  padding: 8px 14px;
  border: none;
  background: #f0a020;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.regenerated-box {
  margin-top: 16px;
  padding: 16px;
  background: #f0f7ff;
  border-radius: 8px;
}
</style>

5)新增 src/components/WrongPanel.vue

xml 复制代码
<template>
  <div>
    <div v-if="wrongList.length === 0" class="empty">暂无错题</div>

    <div v-for="item in wrongList" :key="item.id" class="result-card">
      <div class="card-header">
        <h2>错题 #{{ item.id }}</h2>

        <div class="card-actions">
          <button class="retry-btn" @click="$emit('regenerate', item.id)">
            {{ regenerateLoadingMap[item.id] ? '生成中...' : '再练一题' }}
          </button>

          <button class="wrong-btn" @click="$emit('toggle-wrong', item)">
            取消错题
          </button>
        </div>
      </div>

      <h3>题目</h3>
      <p>{{ item.question }}</p>

      <h3>答案</h3>
      <p>{{ item.answer }}</p>

      <h3>步骤解析</h3>
      <ol>
        <li v-for="(step, idx) in item.steps" :key="idx">{{ step }}</li>
      </ol>

      <h3>知识点</h3>
      <ul>
        <li v-for="(kp, idx) in item.knowledge_points" :key="idx">{{ kp }}</li>
      </ul>

      <h3>相似题</h3>
      <p>{{ item.similar_question }}</p>

      <div v-if="regeneratedMap[item.id]" class="regenerated-box">
        <h3>再练一题</h3>
        <p>{{ regeneratedMap[item.id].question }}</p>

        <h3>答案</h3>
        <p>{{ regeneratedMap[item.id].answer }}</p>

        <h3>步骤解析</h3>
        <ol>
          <li v-for="(step, idx) in regeneratedMap[item.id].steps" :key="idx">
            {{ step }}
          </li>
        </ol>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { HistoryItem, PracticeQuestionItem } from '../api/math'

defineProps<{
  wrongList: HistoryItem[]
  regenerateLoadingMap: Record<number, boolean>
  regeneratedMap: Record<number, PracticeQuestionItem>
}>()

defineEmits<{
  (e: 'toggle-wrong', item: HistoryItem): void
  (e: 'regenerate', id: number): void
}>()
</script>

<style scoped>
.empty {
  padding: 32px 0;
  text-align: center;
  color: #999;
}

.result-card {
  margin-top: 24px;
  padding: 20px;
  background: #fafafa;
  border-radius: 8px;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.card-actions {
  display: flex;
  gap: 8px;
}

.retry-btn {
  padding: 8px 14px;
  border: none;
  background: #2080f0;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.wrong-btn {
  padding: 8px 14px;
  border: none;
  background: #f0a020;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}

.regenerated-box {
  margin-top: 16px;
  padding: 16px;
  background: #f0f7ff;
  border-radius: 8px;
}
</style>

6)修改 src/App.vue

直接把 template 部分 替换成下面这个版本:

ini 复制代码
<template>
  <div class="page">
    <div class="container">
      <h1>AI 数学辅导老师</h1>

      <StudentBar
        v-model:currentStudentId="currentStudentId"
        v-model:newStudentName="newStudentName"
        :student-list="studentList"
        @student-change="handleStudentChange"
        @create-student="handleCreateStudent"
        @export-report="handleExportReport"
      />

      <TabNav
        :active-tab="activeTab"
        @change="handleTabChange"
      />

      <SolvePanel
        v-if="activeTab === 'solve'"
        v-model:question="question"
        v-model:practiceKnowledge="practiceKnowledge"
        :loading="loading"
        :image-loading="imageLoading"
        :result="result"
        :practice-loading="practiceLoading"
        :practice-list="practiceList"
        :regenerate-loading-map="regenerateLoadingMap"
        :regenerated-map="regeneratedMap"
        @submit="handleSubmit"
        @image-change="handleImageChange"
        @toggle-wrong="toggleWrong"
        @regenerate="handleRegenerateQuestion"
        @generate-practice="handleGeneratePractice"
      />

      <HistoryPanel
        v-else-if="activeTab === 'history'"
        :history-list="historyList"
        :regenerate-loading-map="regenerateLoadingMap"
        :regenerated-map="regeneratedMap"
        @toggle-wrong="toggleWrong"
        @regenerate="handleRegenerateQuestion"
      />

      <WrongPanel
        v-else-if="activeTab === 'wrong'"
        :wrong-list="wrongList"
        :regenerate-loading-map="regenerateLoadingMap"
        :regenerated-map="regeneratedMap"
        @toggle-wrong="toggleWrong"
        @regenerate="handleRegenerateQuestion"
      />

      <template v-else-if="activeTab === 'report'">
        <div v-if="reportLoading" class="empty">学习报告加载中...</div>

        <div v-else-if="learningReport" class="report-panel">
          <div class="report-summary">
            <div class="summary-card">
              <div class="summary-label">总题数</div>
              <div class="summary-value">{{ learningReport.total_count }}</div>
            </div>

            <div class="summary-card">
              <div class="summary-label">错题数</div>
              <div class="summary-value">{{ learningReport.wrong_count }}</div>
            </div>

            <div class="summary-card">
              <div class="summary-label">正确数</div>
              <div class="summary-value">{{ learningReport.correct_count }}</div>
            </div>

            <div class="summary-card">
              <div class="summary-label">错题率</div>
              <div class="summary-value">{{ learningReport.wrong_rate }}%</div>
            </div>
          </div>

          <div class="result-card">
            <h2>高频知识点 Top 5</h2>
            <div v-if="learningReport.top_knowledge_points.length === 0" class="empty">
              暂无知识点统计
            </div>
            <ul v-else class="stat-list">
              <li
                v-for="(item, index) in learningReport.top_knowledge_points"
                :key="index"
                class="stat-item"
              >
                <span>{{ item.name }}</span>
                <strong>{{ item.count }}</strong>
              </li>
            </ul>
          </div>

          <div class="result-card">
            <h2>最近练习</h2>
            <div v-if="learningReport.recent_records.length === 0" class="empty">
              暂无记录
            </div>

            <div
              v-for="item in learningReport.recent_records"
              :key="item.id"
              class="recent-item"
            >
              <div class="recent-header">
                <span>题目 #{{ item.id }}</span>
                <span :class="['status-tag', item.is_wrong ? 'wrong' : 'correct']">
                  {{ item.is_wrong ? '错题' : '正常' }}
                </span>
              </div>

              <div class="recent-question">{{ item.question }}</div>

              <div class="recent-kp">
                <span
                  v-for="(kp, idx) in item.knowledge_points"
                  :key="idx"
                  class="kp-tag"
                >
                  {{ kp }}
                </span>
              </div>
            </div>
          </div>
        </div>
      </template>

      <template v-else>
        <div v-if="suggestionLoading" class="empty">学习建议加载中...</div>

        <div v-else-if="studySuggestion" class="report-panel">
          <div class="result-card">
            <h2>整体学习建议</h2>
            <p>{{ studySuggestion.overall_suggestion }}</p>
          </div>

          <div class="result-card">
            <h2>薄弱知识点分析</h2>

            <div v-if="studySuggestion.weak_knowledge_points.length === 0" class="empty">
              暂无薄弱知识点
            </div>

            <div
              v-for="(item, index) in studySuggestion.weak_knowledge_points"
              :key="index"
              class="weak-item"
            >
              <div class="weak-header">
                <strong>{{ item.name }}</strong>
                <span class="weak-rate">错误率 {{ item.wrong_rate }}%</span>
              </div>

              <div class="weak-meta">
                错误 {{ item.wrong_count }} 次 / 共出现 {{ item.total_count }} 次
              </div>

              <div class="weak-suggestion">
                {{ item.suggestion }}
              </div>

              <button
                class="retry-btn"
                @click="handleGenerateWeakPractice(item.name)"
              >
                生成该知识点练习题
              </button>
            </div>
          </div>
        </div>
      </template>
    </div>
  </div>
</template>

7)src/App.vue 的 script 只补充这些 import

在顶部新增:

javascript 复制代码
import StudentBar from './components/StudentBar.vue'
import TabNav from './components/TabNav.vue'
import SolvePanel from './components/SolvePanel.vue'
import HistoryPanel from './components/HistoryPanel.vue'
import WrongPanel from './components/WrongPanel.vue'

8)src/App.vue 的 script 新增两个方法

加到 script setup 里:

ini 复制代码
const handleTabChange = async (tab: 'solve' | 'history' | 'wrong' | 'report' | 'suggestion') => {
  activeTab.value = tab

  if (tab === 'history') {
    await loadHistory()
  } else if (tab === 'wrong') {
    await loadWrongList()
  } else if (tab === 'report') {
    await loadReport()
  } else if (tab === 'suggestion') {
    await loadStudySuggestion()
  }
}

const handleGenerateWeakPractice = async (knowledgeName: string) => {
  practiceKnowledge.value = knowledgeName
  activeTab.value = 'solve'
  await handleGeneratePractice()
}

9)src/App.vue 的 style 删除这些已拆走的样式

可以从 App.vue 里删掉这些,避免重复:

lua 复制代码
.student-bar
.student-select,
.student-input
.tabs
.tab-btn
.tab-btn.active
.upload-area
.upload-btn
.file-input
.question-input
.card-header
.card-actions
.retry-btn
.wrong-btn
.practice-panel
.practice-form
.practice-input
.practice-list
.regenerated-box

保留这些全局页面级样式:

css 复制代码
.page {
  min-height: 100vh;
  background: #f5f7fa;
  padding: 40px 16px;
}
.container {
  max-width: 900px;
  margin: 0 auto;
  background: #fff;
  padding: 24px;
  border-radius: 12px;
}
.submit-btn {
  margin-top: 16px;
  padding: 10px 18px;
  border: none;
  background: #18a058;
  color: #fff;
  border-radius: 8px;
  cursor: pointer;
}
.result-card {
  margin-top: 24px;
  padding: 20px;
  background: #fafafa;
  border-radius: 8px;
}
.empty {
  padding: 32px 0;
  text-align: center;
  color: #999;
}
.report-panel {
  margin-top: 24px;
}
.report-summary {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  margin-bottom: 24px;
}
.summary-card {
  padding: 20px;
  background: #fafafa;
  border-radius: 12px;
  text-align: center;
}
.summary-label {
  color: #666;
  font-size: 14px;
  margin-bottom: 8px;
}
.summary-value {
  font-size: 28px;
  font-weight: 700;
  color: #18a058;
}
.stat-list {
  padding: 0;
  margin: 0;
  list-style: none;
}
.stat-item {
  display: flex;
  justify-content: space-between;
  padding: 12px 0;
  border-bottom: 1px solid #eee;
}
.recent-item {
  padding: 16px 0;
  border-bottom: 1px solid #eee;
}
.recent-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.recent-question {
  margin-bottom: 10px;
  color: #333;
}
.recent-kp {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.kp-tag {
  display: inline-block;
  padding: 4px 10px;
  background: #f3f3f3;
  border-radius: 999px;
  font-size: 12px;
}
.status-tag {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  color: #fff;
}
.status-tag.wrong {
  background: #d03050;
}
.status-tag.correct {
  background: #18a058;
}
.weak-item {
  padding: 16px 0;
  border-bottom: 1px solid #eee;
}
.weak-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}
.weak-rate {
  color: #d03050;
  font-weight: 600;
}
.weak-meta {
  color: #666;
  font-size: 14px;
  margin-bottom: 8px;
}
.weak-suggestion {
  margin-bottom: 12px;
  color: #333;
  line-height: 1.7;
}

10)效果

这次拆分完:

  • App.vue 只保留页面编排和状态
  • 学生切换独立
  • tab 独立
  • 解析区独立
  • 历史记录独立
  • 错题本独立

后面再拆:

  • ReportPanel.vue
  • SuggestionPanel.vue

功能都测试一遍 没问题

nice !

相关推荐
xiaofeichaichai4 小时前
Webpack
前端·webpack·node.js
Thecozzy4 小时前
线上 Bug 排查与修复实录
架构
鹏大师运维4 小时前
为什么信创电脑装软件总提示“软件包架构不匹配”?
linux·运维·架构·国产化·麒麟·deb·统信uos
问心无愧05134 小时前
ctf show web入门111
android·前端·笔记
唐某人丶4 小时前
模型越来越强,我们还需要 Agent 工程吗?—— 从价值重估到 Harness 实践
前端·agent·ai编程
智码看视界4 小时前
现代Web开发基础:全栈工程师的起航点
前端·后端·c5全栈
JS菌5 小时前
手写一个 AI Agent 全栈项目:从沙箱执行到子智能体的完整实现
前端·人工智能·后端
excel6 小时前
HLS TS 文件损坏的元凶:Git 提交与拉取
前端
Aphasia3116 小时前
https连接传输流程
前端·面试