feat(layout): 为多页面添加侧边栏布局组件

- 在admid页面和class页面包裹el-container,插入Sidebar侧边栏组件
- 在LearningPlan和PlanTTS页面同样新增侧边栏布局
- 重构Header组件样式,采用fluent风格透明卡片和按钮样式
- 增加main.css中panel-shell的样式定义以支持新布局视觉效果
- 优化部分按钮及菜单交互样式,提升整体一致性与视觉体验
This commit is contained in:
lbw
2026-01-04 11:10:29 +08:00
parent 1184ea7895
commit fe7128dd4e
9 changed files with 556 additions and 445 deletions

View File

@@ -1,3 +1,19 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer components {
.panel-shell {
border-radius: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(8px);
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
.dark .panel-shell {
border-color: rgba(255, 255, 255, 0.1);
background: rgba(55, 65, 81, 0.4);
}
}

View File

@@ -1,41 +1,36 @@
<template> <template>
<header> <header>
<nav class="bg-white border-gray-200 px-4 lg:px-6 py-2.5 dark:bg-gray-800"> <div class="p-2">
<div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl"> <div class="panel-shell">
<nav class="fluent-nav px-4 lg:px-6 py-2.5">
<div class="flex flex-wrap justify-between items-center">
<a href="#" class="flex items-center"> <a href="#" class="flex items-center">
<img src="https://flowbite.com/docs/images/logo.svg" class="mr-3 h-6 sm:h-9" alt="Flowbite Logo" /> <img src="https://flowbite.com/docs/images/logo.svg" class="mr-3 h-6 sm:h-9" alt="Flowbite Logo" />
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">Flowbite</span> <span class="self-center text-xl font-semibold whitespace-nowrap">Flowbite</span>
</a> </a>
<div class="flex items-center lg:order-2"> <div class="flex items-center lg:order-2">
<template v-if="userName"> <template v-if="userName">
<div class="relative" ref="menuRef"> <div class="relative" ref="menuRef">
<button <button @click="menuOpen = !menuOpen"
@click="menuOpen = !menuOpen" class="fluent-btn font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 flex items-center">
class="text-gray-800 dark:text-white font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 flex items-center">
<span class="mr-2">{{ userName }}</span> <span class="mr-2">{{ userName }}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg> </svg>
</button> </button>
<div <div v-if="menuOpen" class="fluent-card absolute right-0 mt-2 z-50">
v-if="menuOpen" <router-link to="/admid" @click="menuOpen = false" class="block px-4 py-2 fluent-link">
class="absolute right-0 mt-2 w-40 bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded shadow z-50">
<router-link
to="/admid"
@click="menuOpen = false"
class="block px-4 py-2 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600">
后台 后台
</router-link> </router-link>
<button <button @click="handleLogout" class="w-full text-left block px-4 py-2 fluent-link">
@click="handleLogout"
class="w-full text-left block px-4 py-2 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600">
登出 登出
</button> </button>
</div> </div>
</div> </div>
</template> </template>
<button data-collapse-toggle="mobile-menu-2" type="button" <button data-collapse-toggle="mobile-menu-2" type="button"
class="inline-flex items-center p-2 ml-1 text-sm text-gray-500 rounded-lg lg:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" class="inline-flex items-center p-2 ml-1 text-sm rounded-lg lg:hidden fluent-btn"
aria-controls="mobile-menu-2" aria-expanded="false"> aria-controls="mobile-menu-2" aria-expanded="false">
<span class="sr-only">Open main menu</span> <span class="sr-only">Open main menu</span>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
@@ -52,34 +47,12 @@
</button> </button>
</div> </div>
<div class="hidden justify-between items-center w-full lg:flex lg:w-auto lg:order-1" id="mobile-menu-2"> <div class="hidden justify-between items-center w-full lg:flex lg:w-auto lg:order-1" id="mobile-menu-2">
<ul class="flex flex-col mt-4 font-medium lg:flex-row lg:space-x-8 lg:mt-0">
<li>
<a href="#"
class="block py-2 pr-4 pl-3 text-white rounded bg-primary-700 lg:bg-transparent lg:text-primary-700 lg:p-0 dark:text-white"
aria-current="page">Home</a>
</li>
<li>
<router-link to="/"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
班级
</router-link>
</li>
<li>
<router-link to="/learningplan"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
学案
</router-link>
</li>
<li>
<router-link to="/uploadpng"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
上传图片
</router-link>
</li>
</ul>
</div> </div>
</div> </div>
</nav> </nav>
</div>
</div>
<LoginDialog v-model="showLogin" @success="refreshUser" /> <LoginDialog v-model="showLogin" @success="refreshUser" />
</header> </header>
</template> </template>
@@ -131,3 +104,68 @@ onBeforeUnmount(() => {
document.removeEventListener('click', onDocClick) document.removeEventListener('click', onDocClick)
}) })
</script> </script>
<style scoped>
.fluent-nav {
background: transparent;
border-bottom: 0;
backdrop-filter: none;
}
:global(.dark) .fluent-nav {
background: transparent;
border-bottom: 0;
}
.fluent-card {
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
backdrop-filter: blur(16px);
transition: box-shadow 200ms ease, transform 200ms ease;
}
:global(.dark) .fluent-card {
background: rgba(55, 65, 81, 0.4);
border-color: rgba(148, 163, 184, 0.25);
}
.fluent-card:hover {
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.16);
transform: translateY(-1px);
}
.fluent-btn {
color: #0f172a;
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 10px;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
transition: background 200ms ease, box-shadow 200ms ease, transform 200ms ease;
}
.fluent-btn:hover {
background: rgba(255, 255, 255, 0.7);
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12);
}
:global(.dark) .fluent-btn {
color: #e5e7eb;
background: rgba(55, 65, 81, 0.4);
border-color: rgba(148, 163, 184, 0.25);
}
.fluent-link {
color: #2563eb;
border-radius: 10px;
transition: color 200ms ease, background 200ms ease, box-shadow 200ms ease;
}
.fluent-link:hover {
color: #1d4ed8;
background: rgba(255, 255, 255, 0.35);
box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.25);
}
:global(.dark) .fluent-link:hover {
background: rgba(55, 65, 81, 0.35);
box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.25);
}
.fluent-card {
overflow: visible;
}
:global(.el-header) {
overflow: visible;
padding: 0;
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<div class="h-full p-2">
<div class="h-full panel-shell">
<el-menu router :default-active="activePath" class="h-full rounded-xl bg-transparent" :collapse="false">
<el-menu-item index="/">
<span>班级列表</span>
</el-menu-item>
<el-menu-item index="/learningplan">
<span>学案</span>
</el-menu-item>
<el-menu-item index="/uploadpng">
<span>上传图片</span>
</el-menu-item>
</el-menu>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import router from '@/router'
const route = useRoute()
const activePath = computed(() => route.path)
const allRoutes = router.getRoutes()
const menuItems = computed(() => {
return allRoutes
.filter(r => r.meta && r.meta.title)
.filter(r => !r.path.includes(':'))
.filter(r => r.path !== '/login')
.map(r => ({ path: r.path, title: r.meta.title }))
.sort((a, b) => a.title.localeCompare(b.title, 'zh-CN'))
})
</script>
<style scoped>
/* Fluent 2 气质的轻盈质感:柔和玻璃、圆角、细描边 */
.el-menu {
--el-menu-bg-color: transparent;
--el-menu-hover-bg-color: rgba(255, 255, 255, 0.35);
--el-menu-active-color: #2563eb;
}
.el-menu-item {
border-radius: 10px;
transition: all 0.2s ease;
}
.el-menu-item:hover {
backdrop-filter: saturate(1.2);
}
</style>

View File

@@ -5,61 +5,63 @@
<Header></Header> <Header></Header>
</el-header> </el-header>
<el-main class="p-4"> <el-container>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">学案查询</div> <el-aside width="200px" class="pt-4">
<div class="flex flex-wrap items-center gap-3 mb-4"> <Sidebar />
<el-input v-model="searchName" placeholder="按姓名查询" clearable style="max-width: 220px" /> </el-aside>
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button @click="onReset">重置</el-button> <el-main class="p-4">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">学案查询</div>
<div class="flex flex-wrap items-center gap-3 mb-4">
<el-input v-model="searchName" placeholder="按姓名查询" clearable style="max-width: 220px" />
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button @click="onReset">重置</el-button>
</div>
<el-table ref="tableRef" :data="rows" border class="w-full" v-loading="loading" row-key="id">
<el-table-column type="expand">
<template #default="{ row }">
<div class="p-3">
<div class="text-sm font-semibold mb-2">学案</div>
<el-table :data="row.plans || []" size="small" border>
<el-table-column prop="title" label="标题" min-width="280" />
<el-table-column label="状态" width="120">
<template #default="{ row: plan }">
<el-tag :type="plan.isFinished === 1 ? 'success' : 'info'"
effect="plain">
{{ plan.isFinished === 1 ? '已完成' : '未完成' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row: plan }">
<el-button type="primary" size="small"
:loading="downloadingIds.includes(plan.id)"
@click="onDownload(plan)">下载</el-button>
<el-button class="ml-2" type="success" size="small"
:disabled="plan.isFinished === 1"
:loading="finishingIds.includes(plan.id)"
@click="onFinish(row.id, plan.id, plan)">完成</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" min-width="120" />
<el-table-column prop="className" label="班级" min-width="120" />
<el-table-column prop="gradeName" label="年级" min-width="120" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total" :total="totalCount"
:page-size="pageSize" :current-page="pageNo" @current-change="handlePageChange"
@size-change="handleSizeChange" />
</div>
</div> </div>
<el-table ref="tableRef" :data="rows" border class="w-full" v-loading="loading" row-key="id"> </el-main>
<el-table-column type="expand"> </el-container>
<template #default="{ row }">
<div class="p-3">
<div class="text-sm font-semibold mb-2">学案</div>
<el-table :data="row.plans || []" size="small" border>
<el-table-column prop="title" label="标题" min-width="280" />
<el-table-column label="状态" width="120">
<template #default="{ row: plan }">
<el-tag :type="plan.isFinished === 1 ? 'success' : 'info'" effect="plain">
{{ plan.isFinished === 1 ? '已完成' : '未完成' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row: plan }">
<el-button
type="primary"
size="small"
:loading="downloadingIds.includes(plan.id)"
@click="onDownload(plan)"
>下载</el-button>
<el-button
class="ml-2"
type="success"
size="small"
:disabled="plan.isFinished === 1"
:loading="finishingIds.includes(plan.id)"
@click="onFinish(row.id, plan.id, plan)"
>完成</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" min-width="120" />
<el-table-column prop="className" label="班级" min-width="120" />
<el-table-column prop="gradeName" label="年级" min-width="120" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total" :total="totalCount"
:page-size="pageSize" :current-page="pageNo" @current-change="handlePageChange"
@size-change="handleSizeChange" />
</div>
</div>
</el-main>
</el-container> </el-container>
</div> </div>
@@ -71,6 +73,7 @@ import { ref, onMounted } from 'vue'
import { findStudentLessonPlans, finishLessonPlan } from '@/api/studentLessonPlans' import { findStudentLessonPlans, finishLessonPlan } from '@/api/studentLessonPlans'
import { downloadLessonPlan } from '@/api/plan' import { downloadLessonPlan } from '@/api/plan'
import { showMessage } from '@/composables/util' import { showMessage } from '@/composables/util'
import Sidebar from '@/layouts/components/Sidebar.vue'
const rows = ref([]) const rows = ref([])
const loading = ref(false) const loading = ref(false)

View File

@@ -4,50 +4,57 @@
<el-header> <el-header>
<Header></Header> <Header></Header>
</el-header> </el-header>
<el-main class="p-4">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <el-container>
<div class="text-lg font-semibold mb-4">TTS</div> <el-aside width="200px" class="pt-4">
<div class="flex items-center gap-3 mb-4"> <Sidebar />
<el-input v-model="planIdInput" placeholder="planId" style="max-width: 220px" /> </el-aside>
<el-button type="primary" :loading="loadingWords" @click="onLoadWords">加载词汇</el-button>
<el-select v-model="voice" placeholder="选择声线" style="max-width: 160px"> <el-main class="p-4">
<el-option label="alloy" value="alloy" /> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<el-option label="verse" value="verse" /> <div class="text-lg font-semibold mb-4">TTS</div>
<el-option label="nova" value="nova" /> <div class="flex items-center gap-3 mb-4">
</el-select> <el-input v-model="planIdInput" placeholder="planId" style="max-width: 220px" />
<el-select v-model="format" placeholder="格式" style="max-width: 120px"> <el-button type="primary" :loading="loadingWords" @click="onLoadWords">加载词汇</el-button>
<el-option label="mp3" value="mp3" /> <el-select v-model="voice" placeholder="选择声线" style="max-width: 160px">
<el-option label="wav" value="wav" /> <el-option label="alloy" value="alloy" />
<el-option label="ogg" value="ogg" /> <el-option label="verse" value="verse" />
</el-select> <el-option label="nova" value="nova" />
<el-button type="success" :disabled="words.length === 0" :loading="generatingAll" </el-select>
@click="onGenerateAll">生成全部音频</el-button> <el-select v-model="format" placeholder="格式" style="max-width: 120px">
<el-option label="mp3" value="mp3" />
<el-option label="wav" value="wav" />
<el-option label="ogg" value="ogg" />
</el-select>
<el-button type="success" :disabled="words.length === 0" :loading="generatingAll"
@click="onGenerateAll">生成全部音频</el-button>
</div>
<el-table :data="tableData" border class="w-full" v-loading="loadingWords">
<el-table-column prop="word" label="词汇/短语" min-width="260" />
<el-table-column label="状态" width="160">
<template #default="{ row }">
<el-tag :type="row.audioUrl ? 'success' : 'info'" effect="plain">
{{ row.audioUrl ? '已生成' : '未生成' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="360" fixed="right">
<template #default="{ row }">
<el-button size="small" type="primary" :loading="row.loading"
@click="onGenerateOne(row)">生成音频</el-button>
<el-button size="small" class="ml-2" :disabled="!row.audioUrl"
@click="onPlay(row)">播放</el-button>
<el-button size="small" class="ml-2" :disabled="!row.audioUrl"
@click="onDownload(row)">下载</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-3 text-sm text-gray-500">
{{ words.length }}
</div>
</div> </div>
<el-table :data="tableData" border class="w-full" v-loading="loadingWords"> </el-main>
<el-table-column prop="word" label="词汇/短语" min-width="260" /> </el-container>
<el-table-column label="状态" width="160">
<template #default="{ row }">
<el-tag :type="row.audioUrl ? 'success' : 'info'" effect="plain">
{{ row.audioUrl ? '已生成' : '未生成' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="360" fixed="right">
<template #default="{ row }">
<el-button size="small" type="primary" :loading="row.loading"
@click="onGenerateOne(row)">生成音频</el-button>
<el-button size="small" class="ml-2" :disabled="!row.audioUrl"
@click="onPlay(row)">播放</el-button>
<el-button size="small" class="ml-2" :disabled="!row.audioUrl"
@click="onDownload(row)">下载</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-3 text-sm text-gray-500">
{{ words.length }}
</div>
</div>
</el-main>
</el-container> </el-container>
</div> </div>
</template> </template>
@@ -59,7 +66,7 @@ import { useRoute } from 'vue-router'
import { getLessonPlanWords } from '@/api/plan' import { getLessonPlanWords } from '@/api/plan'
import { synthesizeOpenAITTS } from '@/api/tts' import { synthesizeOpenAITTS } from '@/api/tts'
import { showMessage } from '@/composables/util' import { showMessage } from '@/composables/util'
import Sidebar from '@/layouts/components/Sidebar.vue'
const route = useRoute() const route = useRoute()
const planIdInput = ref(route.query.planId ? String(route.query.planId) : '') const planIdInput = ref(route.query.planId ? String(route.query.planId) : '')
const words = ref([]) const words = ref([])

View File

@@ -5,73 +5,70 @@
<Header></Header> <Header></Header>
</el-header> </el-header>
<el-main class="p-4"> <el-container>
<el-card> <el-main class="p-4">
<div class="flex items-center mb-4"> <el-card>
<el-input v-model="query.name" placeholder="姓名" clearable style="max-width:220px" /> <div class="flex items-center mb-4">
<el-button type="primary" class="ml-2" @click="fetchList">查询</el-button> <el-input v-model="query.name" placeholder="姓名" clearable style="max-width:220px" />
<el-button class="ml-2" @click="resetSearch">重置</el-button> <el-button type="primary" class="ml-2" @click="fetchList">查询</el-button>
<el-button type="success" class="ml-2" @click="openCreate">新增用户</el-button> <el-button class="ml-2" @click="resetSearch">重置</el-button>
</div> <el-button type="success" class="ml-2" @click="openCreate">新增用户</el-button>
<el-table :data="list" v-loading="loading" border stripe>
<el-table-column prop="name" label="姓名" />
<el-table-column prop="phone" label="手机号" />
<el-table-column prop="roleName" label="角色" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination
background
:current-page="page"
:page-size="pageSize"
:total="totalCount"
layout="prev, pager, next, sizes, total"
@current-change="onPageChange"
@size-change="onSizeChange"
/>
</div>
</el-card>
<el-card class="mt-4">
<template #header>
<div class="flex items-center justify-between">
<span>生成邀请码</span>
</div> </div>
</template> <el-table :data="list" v-loading="loading" border stripe>
<el-form :model="inviteForm" :rules="inviteRules" ref="inviteFormRef" label-width="120px"> <el-table-column prop="name" label="姓名" />
<el-form-item label="使用次数限制" prop="limit"> <el-table-column prop="phone" label="手机号" />
<el-input-number v-model="inviteForm.limit" :min="1" :max="9999" /> <el-table-column prop="roleName" label="角色" />
</el-form-item> </el-table>
<el-form-item label="有效期(天)" prop="expire"> <div class="mt-4 flex justify-end">
<el-input-number v-model="inviteForm.expire" :min="1" :max="3650" /> <el-pagination background :current-page="page" :page-size="pageSize" :total="totalCount"
</el-form-item> layout="prev, pager, next, sizes, total" @current-change="onPageChange"
<el-form-item> @size-change="onSizeChange" />
<el-button type="primary" :loading="inviteLoading" @click="submitInvite">生成邀请码</el-button> </div>
<el-button class="ml-2" @click="resetInvite">重置</el-button> </el-card>
</el-form-item>
</el-form>
<div v-if="inviteCode" class="mt-2">
<el-alert type="success" :closable="false" :title="`邀请码:${inviteCode}`" show-icon />
</div>
</el-card>
<el-dialog v-model="createVisible" title="新增用户" width="420px"> <el-card class="mt-4">
<el-form :model="createForm" :rules="rules" ref="createFormRef" label-width="80px"> <template #header>
<el-form-item label="姓名" prop="name"> <div class="flex items-center justify-between">
<el-input v-model="createForm.name" /> <span>生成邀请码</span>
</el-form-item> </div>
<el-form-item label="手机号" prop="phone"> </template>
<el-input v-model="createForm.phone" /> <el-form :model="inviteForm" :rules="inviteRules" ref="inviteFormRef" label-width="120px">
</el-form-item> <el-form-item label="使用次数限制" prop="limit">
<el-form-item label="密码" prop="password"> <el-input-number v-model="inviteForm.limit" :min="1" :max="9999" />
<el-input v-model="createForm.password" type="password" show-password /> </el-form-item>
</el-form-item> <el-form-item label="有效期(天)" prop="expire">
</el-form> <el-input-number v-model="inviteForm.expire" :min="1" :max="3650" />
<template #footer> </el-form-item>
<el-button @click="createVisible=false">取消</el-button> <el-form-item>
<el-button type="primary" :loading="createLoading" @click="submitCreate">提交</el-button> <el-button type="primary" :loading="inviteLoading"
</template> @click="submitInvite">生成邀请码</el-button>
</el-dialog> <el-button class="ml-2" @click="resetInvite">重置</el-button>
</el-main> </el-form-item>
</el-form>
<div v-if="inviteCode" class="mt-2">
<el-alert type="success" :closable="false" :title="`邀请码:${inviteCode}`" show-icon />
</div>
</el-card>
<el-dialog v-model="createVisible" title="新增用户" width="420px">
<el-form :model="createForm" :rules="rules" ref="createFormRef" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="createForm.name" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="createForm.phone" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="createForm.password" type="password" show-password />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createVisible = false">取消</el-button>
<el-button type="primary" :loading="createLoading" @click="submitCreate">提交</el-button>
</template>
</el-dialog>
</el-main>
</el-container>
</el-container> </el-container>
</div> </div>
@@ -82,6 +79,7 @@ import Header from '@/layouts/components/Header.vue'
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { getUserList, createUser, createInvitationCode } from '@/api/admin' import { getUserList, createUser, createInvitationCode } from '@/api/admin'
import { showMessage } from '@/composables/util.js' import { showMessage } from '@/composables/util.js'
import Sidebar from '@/layouts/components/Sidebar.vue'
const loading = ref(false) const loading = ref(false)
const list = ref([]) const list = ref([])

View File

@@ -5,128 +5,133 @@
<Header></Header> <Header></Header>
</el-header> </el-header>
<el-main class="p-4"> <el-container>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <el-aside width="200px" class="pt-4">
<Sidebar></Sidebar>
</el-aside>
<div class="lg:col-span-1 flex flex-col gap-6"> <el-main class="p-4">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="text-lg font-semibold mb-4">班级列表</div>
<el-table ref="classTableRef" :data="classes" border class="w-full" v-loading="loading" highlight-current-row <div class="lg:col-span-1 flex flex-col gap-6">
row-key="id" :current-row-key="selectedClassId" @row-click="onClassRowClick"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<el-table-column prop="title" label="班级名称" min-width="120" /> <div class="text-lg font-semibold mb-4">班级列表</div>
<el-table ref="classTableRef" :data="classes" border class="w-full" v-loading="loading"
highlight-current-row row-key="id" :current-row-key="selectedClassId"
@row-click="onClassRowClick">
<el-table-column prop="title" label="班级名称" min-width="120" />
<el-table-column prop="gradeName" label="年级" min-width="120" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="danger" size="small"
@click.stop="onDeleteClass(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total"
:total="totalCount" :page-size="pageSize" :current-page="pageNo"
@current-change="handlePageChange" @size-change="handleSizeChange" />
</div>
<div class="mt-3 flex justify-end">
<el-button type="primary" @click="showAddClassDialog = true">新增班级</el-button>
</div>
<AddClassDialog v-model="showAddClassDialog" :default-grade-id="selectedGradeId"
@success="fetchClasses" />
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 lg:col-span-1 lg:row-span-2">
<div class="text-lg font-semibold mb-4">学生查询</div>
<div class="flex flex-wrap items-center gap-3 mb-4">
<el-input v-model="studentName" placeholder="按姓名查询" clearable
style="max-width: 220px" />
<el-tag v-if="selectedClassId" effect="plain">班级{{ selectedClassTitle }} (ID: {{
selectedClassId }})</el-tag>
<el-tag v-if="selectedGradeId" effect="plain">年级{{ selectedGradeTitle }} (ID: {{
selectedGradeId }})</el-tag>
<el-button type="primary" @click="fetchStudents">查询</el-button>
<el-button @click="resetStudentFilters">重置</el-button>
<el-button type="success" :disabled="selectedStudentIds.length !== 1"
@click="showGenerateDialog = true">
生成试题
</el-button>
<el-button type="warning" :disabled="selectedStudentIds.length !== 1"
@click="showLessonPlanDialog = true">
生成学案
</el-button>
</div>
<el-table ref="studentTableRef" :data="students" border class="w-full"
v-loading="studentLoading" @selection-change="onStudentSelectionChange">
<el-table-column type="selection" width="48" />
<el-table-column prop="name" label="姓名" min-width="120" />
<el-table-column prop="className" label="班级" min-width="120" />
<el-table-column prop="gradeName" label="年级" min-width="120" /> <el-table-column prop="gradeName" label="年级" min-width="120" />
<el-table-column label="操作" width="120" fixed="right"> <el-table-column prop="phone" label="学案" min-width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="danger" size="small" @click.stop="onDeleteClass(row)">删除</el-button> <template v-if="generatingPercents[row.id] !== undefined">
<div class="flex items-center gap-2">
<el-progress :percentage="generatingPercents[row.id]"
:stroke-width="8" />
</div>
</template>
<template v-else>
<div class="flex items-center gap-2">
<el-button type="primary" size="small"
@click="planStudentId = row.id; showPlanListDialog = true">查看学案</el-button>
</div>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<!-- 学情分析 -->
<el-button type="info" size="small"
@click.stop="onShowAnalysis(row)">学情分析</el-button>
<el-button type="primary" size="small"
@click.stop="onViewStudent(row)">详情</el-button>
<el-button type="danger" size="small"
@click.stop="onDeleteStudent(row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="mt-4 flex justify-end"> <div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total" :total="totalCount" <el-pagination background layout="prev, pager, next, sizes, total"
:page-size="pageSize" :current-page="pageNo" @current-change="handlePageChange" :total="studentTotalCount" :page-size="studentPageSize"
@size-change="handleSizeChange" /> :current-page="studentPageNo" @current-change="handleStudentPageChange"
@size-change="handleStudentSizeChange" />
</div> </div>
<div class="mt-3 flex justify-end"> <ExamGenerateDialog v-model="showGenerateDialog" :student-ids="selectedStudentIds"
<el-button type="primary" @click="showAddClassDialog = true">新增班级</el-button> :default-grade-id="selectedGradeId" />
</div>
<AddClassDialog v-model="showAddClassDialog" :default-grade-id="selectedGradeId"
@success="fetchClasses" />
</div>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 lg:col-span-1 lg:row-span-2">
<div class="text-lg font-semibold mb-4">学生查询</div>
<div class="flex flex-wrap items-center gap-3 mb-4">
<el-input v-model="studentName" placeholder="按姓名查询" clearable style="max-width: 220px" />
<el-tag v-if="selectedClassId" effect="plain">班级{{ selectedClassTitle }} (ID: {{
selectedClassId }})</el-tag>
<el-tag v-if="selectedGradeId" effect="plain">年级{{ selectedGradeTitle }} (ID: {{
selectedGradeId }})</el-tag>
<el-button type="primary" @click="fetchStudents">查询</el-button>
<el-button @click="resetStudentFilters">重置</el-button>
<el-button type="success" :disabled="selectedStudentIds.length !== 1"
@click="showGenerateDialog = true">
生成试题
</el-button>
<el-button type="warning" :disabled="selectedStudentIds.length !== 1"
@click="showLessonPlanDialog = true">
生成学案
</el-button>
</div>
<el-table ref="studentTableRef" :data="students" border class="w-full"
v-loading="studentLoading" @selection-change="onStudentSelectionChange">
<el-table-column type="selection" width="48" />
<el-table-column prop="name" label="姓名" min-width="120" />
<el-table-column prop="className" label="班级" min-width="120" />
<el-table-column prop="gradeName" label="年级" min-width="120" />
<el-table-column prop="phone" label="学案" min-width="120">
<template #default="{ row }">
<template v-if="generatingPercents[row.id] !== undefined">
<div class="flex items-center gap-2">
<el-progress :percentage="generatingPercents[row.id]" :stroke-width="8" />
</div>
</template>
<template v-else>
<div class="flex items-center gap-2">
<el-button type="primary" size="small" @click="planStudentId = row.id; showPlanListDialog = true">查看学案</el-button>
</div>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="240" fixed="right">
<template #default="{ row }">
<!-- 学情分析 -->
<el-button type="info" size="small" @click.stop="onShowAnalysis(row)">学情分析</el-button>
<el-button type="primary" size="small" @click.stop="onViewStudent(row)">详情</el-button>
<el-button type="danger" size="small" @click.stop="onDeleteStudent(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total"
:total="studentTotalCount" :page-size="studentPageSize" :current-page="studentPageNo"
@current-change="handleStudentPageChange" @size-change="handleStudentSizeChange" />
</div>
<ExamGenerateDialog v-model="showGenerateDialog" :student-ids="selectedStudentIds"
:default-grade-id="selectedGradeId" />
<div class="mt-3 flex justify-end"> <div class="mt-3 flex justify-end">
<el-button type="primary" @click="showAddStudentDialog = true">新增学生</el-button> <el-button type="primary" @click="showAddStudentDialog = true">新增学生</el-button>
</div> </div>
<AddStudentDialog <AddStudentDialog v-model="showAddStudentDialog" :default-class-id="selectedClassId"
v-model="showAddStudentDialog" :default-grade-id="selectedGradeId" @success="fetchStudents" />
:default-class-id="selectedClassId" <LessonPlanDialog v-model="showLessonPlanDialog" :student-id="selectedStudentIds[0]"
:default-grade-id="selectedGradeId" @success="onLessonPlanGenerateSuccess" />
@success="fetchStudents" <StudentPlanListDialog v-model="showPlanListDialog" :student-id="planStudentId" />
/> <el-dialog v-model="showAnalysisDialog" title="学情分析" width="60%">
<LessonPlanDialog <StudyAnalysis v-if="showAnalysisDialog" :student-id="analysisStudentId" />
v-model="showLessonPlanDialog" </el-dialog>
:student-id="selectedStudentIds[0]"
@success="onLessonPlanGenerateSuccess"
/>
<StudentPlanListDialog
v-model="showPlanListDialog"
:student-id="planStudentId"
/>
<el-dialog v-model="showAnalysisDialog" title="学情分析" width="60%">
<StudyAnalysis v-if="showAnalysisDialog" :student-id="analysisStudentId" />
</el-dialog>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading">
<div class="text-lg font-semibold mb-4">年级列表</div>
<el-table ref="gradeTableRef" :data="grades" border class="w-full" highlight-current-row
row-key="id" :current-row-key="selectedGradeId" @row-click="onGradeRowClick">
<el-table-column prop="title" label="年级名称" min-width="160" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total" :total="gradeTotalCount"
:page-size="gradePageSize" :current-page="gradePageNo"
@current-change="handleGradePageChange" @size-change="handleGradeSizeChange" />
</div> </div>
</div>
</div> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading">
</el-main> <div class="text-lg font-semibold mb-4">年级列表</div>
<el-table ref="gradeTableRef" :data="grades" border class="w-full" highlight-current-row
row-key="id" :current-row-key="selectedGradeId" @row-click="onGradeRowClick">
<el-table-column prop="title" label="年级名称" min-width="160" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination background layout="prev, pager, next, sizes, total"
:total="gradeTotalCount" :page-size="gradePageSize" :current-page="gradePageNo"
@current-change="handleGradePageChange" @size-change="handleGradeSizeChange" />
</div>
</div>
</div>
</el-main>
</el-container>
</el-container> </el-container>
</div> </div>
@@ -134,6 +139,7 @@
<script setup> <script setup>
import Header from '@/layouts/components/Header.vue' import Header from '@/layouts/components/Header.vue'
import Sidebar from '@/layouts/components/Sidebar.vue'
import { ref, onMounted, onUnmounted } from 'vue' import { ref, onMounted, onUnmounted } from 'vue'
import { getClassList, deleteClass } from '@/api/class' import { getClassList, deleteClass } from '@/api/class'
import { getGradeList, deleteGrade } from '@/api/grade' import { getGradeList, deleteGrade } from '@/api/grade'

View File

@@ -5,54 +5,62 @@
<Header></Header> <Header></Header>
</el-header> </el-header>
<el-main class="p-4"> <el-container>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6" v-loading="loading"> <el-aside width="200px" class="pt-4">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <Sidebar />
<div class="text-lg font-semibold mb-4">学生详情</div> </el-aside>
<template v-if="detail"> <el-main class="p-4">
<el-descriptions :column="1" border> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6" v-loading="loading">
<el-descriptions-item label="ID">{{ detail.id }}</el-descriptions-item> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<el-descriptions-item label="姓名">{{ detail.name }}</el-descriptions-item> <div class="text-lg font-semibold mb-4">学生详情</div>
<el-descriptions-item label="班级">{{ detail.className }}</el-descriptions-item> <template v-if="detail">
<el-descriptions-item label="年级">{{ detail.gradeName }}</el-descriptions-item> <el-descriptions :column="1" border>
<el-descriptions-item label="学生实际水平年级">{{ detail.actualGrade }}</el-descriptions-item> <el-descriptions-item label="ID">{{ detail.id }}</el-descriptions-item>
</el-descriptions> <el-descriptions-item label="姓名">{{ detail.name }}</el-descriptions-item>
</template> <el-descriptions-item label="班级">{{ detail.className }}</el-descriptions-item>
<template v-else> <el-descriptions-item label="年级">{{ detail.gradeName }}</el-descriptions-item>
<el-empty description="请从班级页跳转" /> <el-descriptions-item label="学生实际水平年级">{{ detail.actualGrade
</template> }}</el-descriptions-item>
</div> </el-descriptions>
</template>
<template v-else>
<el-empty description="请从班级页跳转" />
</template>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">学生词汇统计</div> <div class="text-lg font-semibold mb-4">学生词汇统计</div>
<template v-if="wordStat"> <template v-if="wordStat">
<el-descriptions :column="1" border> <el-descriptions :column="1" border>
<el-descriptions-item label="已掌握">{{ wordStat.masteredWordCount }}</el-descriptions-item> <el-descriptions-item label="已掌握">{{ wordStat.masteredWordCount
<el-descriptions-item label="未掌握">{{ wordStat.unmasteredWordCount }}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="待复习">{{ wordStat.pendingReviewWordCount }}</el-descriptions-item> <el-descriptions-item label="未掌握">{{ wordStat.unmasteredWordCount
</el-descriptions> }}</el-descriptions-item>
</template> <el-descriptions-item label="待复习">{{ wordStat.pendingReviewWordCount
<template v-else> }}</el-descriptions-item>
<el-empty description="暂无统计" /> </el-descriptions>
</template> </template>
</div> <template v-else>
<el-empty description="暂无统计" />
</template>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-md font-semibold mb-3">学生考试记录</div> <div class="text-md font-semibold mb-3">学生考试记录</div>
<ExamHistoryChart :data="history" /> <ExamHistoryChart :data="history" />
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-md font-semibold mb-3">学生学案记录</div>
<PlanHistoryChart :student-id="route.params.id" />
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-md font-semibold mb-3">词汇掌握热力图</div>
<WordMasteryHeatmap :student-id="route.params.id" :columns="50" />
</div>
<StudyAnalysis :student-id="route.params.id" />
</div> </div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> </el-main>
<div class="text-md font-semibold mb-3">学生学案记录</div> </el-container>
<PlanHistoryChart :student-id="route.params.id" />
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-md font-semibold mb-3">词汇掌握热力图</div>
<WordMasteryHeatmap :student-id="route.params.id" :columns="50" />
</div>
<StudyAnalysis :student-id="route.params.id" />
</div>
</el-main>
</el-container> </el-container>
</div> </div>
</template> </template>
@@ -68,6 +76,7 @@ import ExamHistoryChart from '@/layouts/components/student/ExamHistoryChart.vue'
import PlanHistoryChart from '@/layouts/components/student/PlanHistoryChart.vue' import PlanHistoryChart from '@/layouts/components/student/PlanHistoryChart.vue'
import WordMasteryHeatmap from '@/layouts/components/student/WordMasteryHeatmap.vue' import WordMasteryHeatmap from '@/layouts/components/student/WordMasteryHeatmap.vue'
import StudyAnalysis from '@/layouts/components/student/StudyAnalysis.vue' import StudyAnalysis from '@/layouts/components/student/StudyAnalysis.vue'
import Sidebar from '@/layouts/components/Sidebar.vue'
const loading = ref(false) const loading = ref(false)
const detail = ref(null) const detail = ref(null)

View File

@@ -5,90 +5,74 @@
<Header></Header> <Header></Header>
</el-header> </el-header>
<el-main class="p-4"> <el-container>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <el-aside width="200px" class="pt-4">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <Sidebar />
<div class="text-lg font-semibold mb-4">上传图片</div> </el-aside>
<el-upload <el-main class="p-4">
:show-file-list="false" <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
:http-request="doUpload" <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
accept="image/*" <div class="text-lg font-semibold mb-4">上传图片</div>
> <el-upload :show-file-list="false" :http-request="doUpload" accept="image/*">
<el-button type="primary">选择图片并上传</el-button> <el-button type="primary">选择图片并上传</el-button>
</el-upload> </el-upload>
</div> </div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">结果集</div> <div class="text-lg font-semibold mb-4">结果集</div>
<el-form :inline="true" class="mb-4"> <el-form :inline="true" class="mb-4">
<el-form-item label="班级"> <el-form-item label="班级">
<el-select v-model="classId" placeholder="选择班级" clearable filterable @change="onClassChange" style="min-width: 220px"> <el-select v-model="classId" placeholder="选择班级" clearable filterable
<el-option @change="onClassChange" style="min-width: 220px">
v-for="item in classOptions" <el-option v-for="item in classOptions" :key="item.id"
:key="item.id" :label="`${item.title}${item.gradeName ? '' + item.gradeName + '' : ''}`"
:label="`${item.title}${item.gradeName ? '' + item.gradeName + '' : ''}`" :value="item.id" />
:value="item.id" </el-select>
/> </el-form-item>
</el-select> <el-form-item label="年级">
</el-form-item> <el-select v-model="gradeId" placeholder="选择年级" clearable filterable
<el-form-item label="年级"> style="min-width: 220px">
<el-select v-model="gradeId" placeholder="选择年级" clearable filterable style="min-width: 220px"> <el-option v-for="g in gradeOptions" :key="g.id" :label="g.title"
<el-option :value="g.id" />
v-for="g in gradeOptions" </el-select>
:key="g.id" </el-form-item>
:label="g.title" <el-form-item label="学生姓名">
:value="g.id" <el-input v-model="studentName" placeholder="学生姓名" clearable />
/> </el-form-item>
</el-select> <el-form-item>
</el-form-item> <el-button type="primary" @click="handleSearch">查询</el-button>
<el-form-item label="学生姓名"> <el-button @click="handleReset">重置</el-button>
<el-input v-model="studentName" placeholder="学生姓名" clearable /> </el-form-item>
</el-form-item> </el-form>
<el-form-item> <el-table :data="list" border class="w-full" v-loading="loading"
<el-button type="primary" @click="handleSearch">查询</el-button> @row-click="handleRowClick">
<el-button @click="handleReset">重置</el-button> <el-table-column prop="studentName" label="学生姓名" min-width="70" />
</el-form-item> <el-table-column prop="examWordsTitle" label="试题名称" min-width="100" />
</el-form> <el-table-column prop="correctWordCount" label="正确词数" width="110" />
<el-table <el-table-column prop="wrongWordCount" label="错误词数" width="110" />
:data="list" <el-table-column label="完成状态" width="110">
border <template #default="{ row }">
class="w-full" <el-tag :type="row.isFinished === 1 ? 'success' : 'info'">
v-loading="loading" {{ row.isFinished === 1 ? '已完成' : '未完成' }}
@row-click="handleRowClick" </el-tag>
> </template>
<el-table-column prop="studentName" label="学生姓名" min-width="70" /> </el-table-column>
<el-table-column prop="examWordsTitle" label="试题名称" min-width="100" /> <el-table-column prop="startDate" label="开始时间" min-width="160">
<el-table-column prop="correctWordCount" label="正确词数" width="110" /> <template #default="{ row }">
<el-table-column prop="wrongWordCount" label="错误词数" width="110" /> {{ row.startDate.replace('T', ' ') }}
<el-table-column label="完成状态" width="110"> </template>
<template #default="{ row }"> </el-table-column>
<el-tag :type="row.isFinished === 1 ? 'success' : 'info'"> <el-table-column prop="msg" label="判卷结算" min-width="160" />
{{ row.isFinished === 1 ? '已完成' : '未完成' }} </el-table>
</el-tag> <div class="mt-4 flex justify-end">
</template> <el-pagination background layout="prev, pager, next, sizes, total" :total="totalCount"
</el-table-column> :page-size="pageSize" :current-page="pageNo" @current-change="handlePageChange"
<el-table-column prop="startDate" label="开始时间" min-width="160"> @size-change="handleSizeChange" />
<template #default="{ row }"> </div>
{{ row.startDate.replace('T', ' ') }}
</template>
</el-table-column>
<el-table-column prop="msg" label="判卷结算" min-width="160" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination
background
layout="prev, pager, next, sizes, total"
:total="totalCount"
:page-size="pageSize"
:current-page="pageNo"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div> </div>
</div> </div>
</div> <ExamWordsDetailCard v-model="showDetail" :id="selectedId" />
<ExamWordsDetailCard v-model="showDetail" :id="selectedId" /> </el-main>
</el-main> </el-container>
</el-container> </el-container>
</div> </div>
</template> </template>
@@ -101,6 +85,7 @@ import { ref, onMounted } from 'vue'
import { uploadExamWordsPng, getExamWordsResult } from '@/api/exam' import { uploadExamWordsPng, getExamWordsResult } from '@/api/exam'
import { getClassList } from '@/api/class' import { getClassList } from '@/api/class'
import { getGradeList } from '@/api/grade' import { getGradeList } from '@/api/grade'
import Sidebar from '@/layouts/components/Sidebar.vue'
const list = ref([]) const list = ref([])
const pageNo = ref(1) const pageNo = ref(1)
@@ -196,5 +181,4 @@ onMounted(() => {
}) })
</script> </script>
<style scoped> <style scoped></style>
</style>