|
@@ -0,0 +1,215 @@
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { getProblemController } from '@/api/problem-controller'
|
|
|
|
|
+import { getCategoryController } from '@/api/category-controller'
|
|
|
|
|
+import type { CategoryVo, ProblemVo, QueryByPage1Params } from '@/api/models'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
+import { RefreshRight, Search } from '@element-plus/icons-vue'
|
|
|
|
|
+import { onMounted, reactive, ref } from 'vue'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+const api = getProblemController()
|
|
|
|
|
+
|
|
|
|
|
+const searchForm = reactive({
|
|
|
|
|
+ title: '',
|
|
|
|
|
+ difficulty: '',
|
|
|
|
|
+ categoryId: undefined as number | undefined,
|
|
|
|
|
+ isPublic: undefined as number | undefined
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const categoryOptions = ref<CategoryVo[]>([])
|
|
|
|
|
+const loadCategories = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getCategoryController().list()
|
|
|
|
|
+ if (res?.code === 200) {
|
|
|
|
|
+ categoryOptions.value = res.data || []
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ categoryOptions.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const tableData = ref<ProblemVo[]>([])
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const pagination = reactive({
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ total: 0
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const difficultyOptions = [
|
|
|
|
|
+ { label: '简单', value: 'easy' },
|
|
|
|
|
+ { label: '中等', value: 'medium' },
|
|
|
|
|
+ { label: '困难', value: 'hard' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const difficultyTag = (difficulty: ProblemVo['difficulty']) => {
|
|
|
|
|
+ if (difficulty === 'easy') return 'success'
|
|
|
|
|
+ if (difficulty === 'medium') return 'warning'
|
|
|
|
|
+ return 'danger'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const difficultyLabel = (difficulty: ProblemVo['difficulty']) =>
|
|
|
|
|
+ difficultyOptions.find((d) => d.value === difficulty)?.label || difficulty || '-'
|
|
|
|
|
+
|
|
|
|
|
+const fetchData = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const condition: QueryByPage1Params = {
|
|
|
|
|
+ title: searchForm.title || undefined,
|
|
|
|
|
+ difficulty: searchForm.difficulty || undefined,
|
|
|
|
|
+ categoryId: searchForm.categoryId,
|
|
|
|
|
+ isPublic: searchForm.isPublic,
|
|
|
|
|
+ pageNum: pagination.pageNum,
|
|
|
|
|
+ pageSize: pagination.pageSize
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await api.queryByPage1(condition)
|
|
|
|
|
+ const pageData = res?.data
|
|
|
|
|
+ tableData.value = pageData?.data || []
|
|
|
|
|
+ pagination.total = pageData?.total || 0
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ ElMessage.error('获取题目列表失败')
|
|
|
|
|
+ tableData.value = []
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleSearch = () => {
|
|
|
|
|
+ pagination.pageNum = 1
|
|
|
|
|
+ fetchData()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleReset = () => {
|
|
|
|
|
+ searchForm.title = ''
|
|
|
|
|
+ searchForm.difficulty = ''
|
|
|
|
|
+ searchForm.categoryId = undefined
|
|
|
|
|
+ searchForm.isPublic = undefined
|
|
|
|
|
+ handleSearch()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handlePageChange = (page: number) => {
|
|
|
|
|
+ pagination.pageNum = page
|
|
|
|
|
+ fetchData()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleSizeChange = (size: number) => {
|
|
|
|
|
+ pagination.pageSize = size
|
|
|
|
|
+ pagination.pageNum = 1
|
|
|
|
|
+ fetchData()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const goDetail = (row: ProblemVo) => {
|
|
|
|
|
+ if (row.id !== undefined) {
|
|
|
|
|
+ router.push(`/problems/${row.id}`)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadCategories()
|
|
|
|
|
+ fetchData()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="problem-list-view">
|
|
|
|
|
+ <el-card class="search-card" shadow="never">
|
|
|
|
|
+ <el-form inline>
|
|
|
|
|
+ <el-form-item label="标题">
|
|
|
|
|
+ <el-input v-model="searchForm.title" placeholder="请输入题目标题" clearable style="width: 200px" @keyup.enter="handleSearch" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="难度">
|
|
|
|
|
+ <el-select v-model="searchForm.difficulty" placeholder="全部" clearable style="width: 120px">
|
|
|
|
|
+ <el-option v-for="item in difficultyOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="分类">
|
|
|
|
|
+ <el-select v-model="searchForm.categoryId" placeholder="全部" clearable style="width: 160px">
|
|
|
|
|
+ <el-option v-for="item in categoryOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="公开状态">
|
|
|
|
|
+ <el-select v-model="searchForm.isPublic" placeholder="全部" clearable style="width: 120px">
|
|
|
|
|
+ <el-option label="公开" :value="1" />
|
|
|
|
|
+ <el-option label="未公开" :value="0" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" @click="handleSearch">
|
|
|
|
|
+ <el-icon class="el-icon--left"><Search /></el-icon>搜索
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button @click="handleReset">
|
|
|
|
|
+ <el-icon class="el-icon--left"><RefreshRight /></el-icon>重置
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-card class="table-card" shadow="never">
|
|
|
|
|
+ <el-table v-loading="loading" :data="tableData" border stripe style="width: 100%" @row-click="goDetail">
|
|
|
|
|
+ <el-table-column prop="id" label="ID" width="80" align="center" />
|
|
|
|
|
+ <el-table-column prop="title" label="标题" min-width="220">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-link type="primary" :underline="false">{{ row.title || '-' }}</el-link>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="难度" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="difficultyTag(row.difficulty)">{{ difficultyLabel(row.difficulty) }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="公开状态" width="100" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="row.isPublic === 1 ? 'success' : 'info'">{{ row.isPublic === 1 ? '公开' : '未公开' }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="时间限制" width="110" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ row.timeLimitMs ?? '-' }} ms</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="内存限制" width="110" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ row.memoryLimitMb ?? '-' }} MB</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="通过/提交" width="110" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ row.acceptedCount ?? 0 }} / {{ row.tryCount ?? 0 }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="100" align="center" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" link @click.stop="goDetail(row)">查看</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="pagination">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ v-model:current-page="pagination.pageNum"
|
|
|
|
|
+ v-model:page-size="pagination.pageSize"
|
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
+ :total="pagination.total"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ @current-change="handlePageChange"
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.problem-list-view {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-card {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-card {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.pagination {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|