feat: add generate lesson dialog
This commit is contained in:
50
src/components/GenerateLessonDialog.test.ts
Normal file
50
src/components/GenerateLessonDialog.test.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import GenerateLessonDialog from './GenerateLessonDialog.vue'
|
||||||
|
|
||||||
|
describe('GenerateLessonDialog', () => {
|
||||||
|
it('disables submit until a topic is entered', async () => {
|
||||||
|
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: null } })
|
||||||
|
|
||||||
|
const submit = wrapper.findAll('button')[0]!
|
||||||
|
expect(submit.attributes('disabled')).toBeDefined()
|
||||||
|
|
||||||
|
await wrapper.get('input').setValue('CSS 弹性布局')
|
||||||
|
expect(submit.attributes('disabled')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits submit with the trimmed topic', async () => {
|
||||||
|
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: null } })
|
||||||
|
|
||||||
|
await wrapper.get('input').setValue(' CSS 弹性布局 ')
|
||||||
|
await wrapper.findAll('button')[0]!.trigger('click')
|
||||||
|
|
||||||
|
expect(wrapper.emitted('submit')).toEqual([['CSS 弹性布局']])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows a loading state and disables interaction', () => {
|
||||||
|
const wrapper = mount(GenerateLessonDialog, { props: { loading: true, error: null } })
|
||||||
|
|
||||||
|
expect(wrapper.get('input').attributes('disabled')).toBeDefined()
|
||||||
|
expect(wrapper.findAll('button')[0]!.text()).toContain('生成中')
|
||||||
|
expect(wrapper.findAll('button')[0]!.attributes('disabled')).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows an error message and allows retry without closing', async () => {
|
||||||
|
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: 'Deepseek 请求失败。' } })
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('Deepseek 请求失败。')
|
||||||
|
expect(wrapper.findAll('button')[0]!.attributes('disabled')).toBeDefined()
|
||||||
|
|
||||||
|
await wrapper.get('input').setValue('CSS 弹性布局')
|
||||||
|
expect(wrapper.findAll('button')[0]!.attributes('disabled')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits cancel', async () => {
|
||||||
|
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: null } })
|
||||||
|
|
||||||
|
await wrapper.findAll('button')[1]!.trigger('click')
|
||||||
|
|
||||||
|
expect(wrapper.emitted('cancel')).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
44
src/components/GenerateLessonDialog.vue
Normal file
44
src/components/GenerateLessonDialog.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
loading: boolean
|
||||||
|
error: string | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
submit: [topic: string]
|
||||||
|
cancel: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const topic = ref('')
|
||||||
|
|
||||||
|
function submit(): void {
|
||||||
|
const value = topic.value.trim()
|
||||||
|
if (!value || props.loading) return
|
||||||
|
emit('submit', value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="dialog-overlay" role="dialog" aria-modal="true" aria-labelledby="generate-lesson-title">
|
||||||
|
<div class="dialog">
|
||||||
|
<h2 id="generate-lesson-title">生成教案</h2>
|
||||||
|
<p>输入主题,AI 将生成一份符合模板结构的教案,加入当前整本末尾。</p>
|
||||||
|
<input
|
||||||
|
v-model="topic"
|
||||||
|
type="text"
|
||||||
|
placeholder="例如:CSS 弹性布局入门"
|
||||||
|
:disabled="loading"
|
||||||
|
@keydown.enter="submit"
|
||||||
|
/>
|
||||||
|
<p v-if="error" class="app-notice app-notice--error" role="alert">{{ error }}</p>
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<button type="button" :disabled="loading || !topic.trim()" @click="submit">
|
||||||
|
{{ loading ? '生成中…' : '生成' }}
|
||||||
|
</button>
|
||||||
|
<button type="button" :disabled="loading" @click="emit('cancel')">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user