slipt question table components

This commit is contained in:
zhangyuheng 2023-12-28 09:48:17 +00:00
parent 5d0a4b2834
commit ebb1010831
10 changed files with 477 additions and 396 deletions

View File

@ -1,15 +1,15 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router';
import HelloWorld from './components/HelloWorld.vue';
import SideMenu from '@/components/SideMenu.vue';
</script>
<template>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
<RouterView />
<el-aside class="nav-menu-side">
<SideMenu />
</el-aside>
<el-main class="container-main">
<RouterView />
</el-main>
</template>
<style scoped>
@ -18,60 +18,22 @@ header {
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
.container-main {
padding: 20px;
margin-left: 200px;
margin-top: 20px;
width: calc(100% - 200px);
}
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
.nav-menu-side {
width: 200px;
height: 100vh;
background-color: #fff;
border-right: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
z-index: 100;
}
</style>

View File

@ -1,8 +1,8 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
max-width: auto;
margin: 0 0;
padding: 2rem;
font-weight: normal;
}
@ -29,7 +29,6 @@ a,
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

View File

@ -1,41 +0,0 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

View File

@ -0,0 +1,141 @@
<script setup lang="ts">
import type { QuestionQueryCondition } from '../../interfaces/question';
import { ORDER_TYPE } from '../../interfaces/page';
import type { Tag } from '../../interfaces/tag';
import { ref } from 'vue';
import { CircleCloseFilled, Search } from '@element-plus/icons-vue';
// tag list for filter (need to be fetched from backend)
const tagList = ref<Tag[]>([]);
// todo: fetch tag list from backend
// data model
const queryCondition = ref<QuestionQueryCondition>({
page: 1,
page_size: 10,
sort_by: 'id',
sort_order: ORDER_TYPE.ASC,
search: '',
is_published: -1,
tag_ids: [],
types: [],
after_create_time: 0,
before_create_time: 0,
after_update_time: 0,
before_update_time: 0,
});
// timerange for create_time and update_time filter
const filterTimerangeCreate = ref<number[]>([]);
const filterTimerangeUpdate = ref<number[]>([]);
const createTimeRangeChange = (val: string[]) => {
queryCondition.value.after_create_time = Number(val[0]) / 1000;
queryCondition.value.before_create_time = Number(val[1]) / 1000;
};
const updateTimeRangeChange = (val: string[]) => {
queryCondition.value.after_update_time = Number(val[0]) / 1000;
queryCondition.value.before_update_time = Number(val[1]) / 1000;
};
// publish status filter
const filterPublishStatu = ref<string>('-1');
const handlePublishChange = (val: string) => {
queryCondition.value.is_published = Number(val);
};
// question types filter
const filterTypes = ref<string[]>([]);
const handleTypeChange = (val: string[]) => {
queryCondition.value.types = val.map((item) => Number(item));
};
// tags filter
const filterTagIds = ref<string[]>([]);
const handleTagChange = (val: string[]) => {
queryCondition.value.tag_ids = val.map((item) => Number(item));
};
// page info prpos
const props = defineProps(['page', 'page_size']);
// callback method send to parent component
const emit = defineEmits(['search']);
const outGoingData = () => {
queryCondition.value.page = props.page;
queryCondition.value.page_size = props.page_size;
emit('search', queryCondition.value);
};
</script>
<template>
<main>
<el-form :inline="true" :model="queryCondition" class="demo-form-inline">
<el-form-item label="发布状态">
<el-select v-model="filterPublishStatu" placeholder="请选择" @change="handlePublishChange">
<el-option label="全部" value="-1" />
<el-option label="已发布" value="1" />
<el-option label="编辑中" value="0" />
</el-select>
</el-form-item>
<el-form-item label="题型">
<el-select v-model="filterTypes" multiple placeholder="请选择" @change="handleTypeChange">
<el-option label="单选题" value="0" />
<el-option label="多选题" value="1" />
<el-option label="判断题" value="2" />
<el-option label="填空题" value="3" />
<el-option label="简答题" value="4" />
<el-option label="文件上传" value="5" />
</el-select>
</el-form-item>
<el-form-item label="标签">
<el-select v-model="filterTagIds" multiple placeholder="请选择" @change="handleTagChange">
<el-option v-for="tag in tagList" :key="tag.id" :label="tag.name" :value="tag.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input v-model="queryCondition.search" placeholder="请输入关键字" />
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="filterTimerangeCreate"
type="daterange"
range-separator="至"
value-format="x"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="createTimeRangeChange"
/>
<el-button
:icon="CircleCloseFilled"
@click="
filterTimerangeCreate = [];
queryCondition.after_create_time = 0;
queryCondition.before_create_time = 0;
"
type="info"
text
/>
</el-form-item>
<el-form-item label="更新时间">
<el-date-picker
v-model="filterTimerangeUpdate"
type="daterange"
range-separator="至"
value-format="x"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="updateTimeRangeChange"
/>
<el-button
:icon="CircleCloseFilled"
@click="
filterTimerangeUpdate = [];
queryCondition.after_update_time = 0;
queryCondition.before_update_time = 0;
"
type="info"
text
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="outGoingData">搜索</el-button>
</el-form-item>
</el-form>
</main>
</template>

View File

@ -0,0 +1,266 @@
<template>
<main>
<!-- Condition Filter From -->
<QueryConditions
@search="doFilterSearch"
:page="queryCondition.page"
:page_size="queryCondition.page_size"
/>
<!-- Table -->
<el-table
:data="page_data.items"
:table-layout="'auto'"
:default-sort="{ prop: 'id', order: 'ascending' }"
:border="true"
:disabled="disabled"
@sort-change="handleSortChange"
class="table-questions"
>
<el-table-column prop="id" label="ID" :sortable="'custom'" class="table-column" width="70">
<template #default="scope">
<div class="table-item">
{{ scope.row.id }}
</div>
</template>
</el-table-column>
<el-table-column prop="title" label="标题" :sortable="'custom'" class="table-column" />
<el-table-column
prop="type"
label="题型"
:sortable="'custom'"
class="table-column"
width="80"
>
<template #default="scope">
<div class="table-item">
{{ getQuestionTypeText(scope.row.type) }}
</div>
</template>
</el-table-column>
<el-table-column label="正确率" :sortable="'custom'" width="100">
<template #default="scope">
<div class="table-item">
{{
scope.row.reference_count.value === 0
? scope.row.correct_count.value / scope.row.reference_count.value
: 0
}}
%
</div>
</template>
</el-table-column>
<el-table-column
prop="reference_count"
label="引用"
:sortable="'custom'"
class="table-column"
width="80"
>
<template #default="scope">
<div class="table-item">
{{ scope.row.reference_count }}
</div>
</template>
</el-table-column>
<el-table-column
prop="is_published"
label="状态"
:sortable="'custom'"
class="table-column"
width="80"
>
<template #default="scope">
<div class="table-item">
<el-tag v-if="scope.row.is_published" type="success">已发布</el-tag>
<el-tag v-else type="info">编辑中</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="标签" class="table-column">
<template #default="scope">
<div class="table-item">
<div v-if="scope.row.tags.length === 0"></div>
<div v-else-if="scope.row.tags.length > 3">
<el-tag v-for="tag in scope.row.tags.slice(0, 3)" :key="tag.id" :color="tag.color">{{
tag.name
}}</el-tag>
<el-tag type="info">...</el-tag>
</div>
<div v-else>
<el-tag v-for="tag in scope.row.tags" :key="tag.id" type="info">{{
tag.name
}}</el-tag>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="创建时间" :sortable="'custom'" class="table-column" width="180">
<template #default="scope">
<div class="table-item">
{{ scope.row.created_at }}
</div>
</template>
</el-table-column>
<el-table-column label="更新时间" :sortable="'custom'" class="table-column" width="180">
<template #default="scope">
<div class="table-item">
{{ scope.row.updated_at }}
</div>
</template>
</el-table-column>
<!-- edit -->
<el-table-column class="table-column" width="150">
<template #header> 操作 </template>
<template #default="scope">
<div class="table-item">
<el-row :gutter="20">
<el-col :span="10"
><el-button size="small" @click="handleEdit(scope.$index, scope.row)"
>编辑</el-button
></el-col
>
<el-col :span="10"
><el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>删除</el-button
></el-col
>
</el-row>
</div>
</template>
</el-table-column>
</el-table>
<!-- Pagination -->
<el-pagination
v-model:current-page="page_data.page"
v-model:page-size="page_data.page_size"
:page-sizes="[10, 50, 100]"
:disabled="disabled"
:background="true"
layout="prev, pager, next, total, jumper, sizes"
:total="page_data.total_count"
@current-change="handlePageChange"
@size-change="handlePageSizeChange"
class="pagination"
/>
</main>
</template>
<script lang="ts" setup>
import { defineComponent, ref, h } from 'vue';
import type { QuestionPage, QuestionQueryCondition } from '@/interfaces/question';
import { ORDER_TYPE } from '@/interfaces/page';
import { getQuestionTypeText, QuestionType } from '@/interfaces/question';
import type { Tag } from '@/interfaces/tag';
import QueryConditions from '@/components/Question/QueryConditions.vue';
import { getQuestionsByPage } from '@/apis/question';
import { CircleCloseFilled } from '@element-plus/icons-vue';
const queryCondition = ref<QuestionQueryCondition>({
page: 1,
page_size: 10,
sort_by: 'id',
sort_order: ORDER_TYPE.ASC,
search: '',
is_published: -1,
tag_ids: [],
types: [],
after_create_time: 0,
before_create_time: 0,
after_update_time: 0,
before_update_time: 0,
});
const disabled = ref<boolean>(false); // before data loaded, disable pagination
const page_data = ref<QuestionPage>({
page: 1,
page_size: 10,
total_count: 0,
total_page: 0,
items: [],
});
const doFilterSearch = (val: QuestionQueryCondition) => {
queryCondition.value.search = val.search;
queryCondition.value.is_published = val.is_published;
queryCondition.value.tag_ids = val.tag_ids;
queryCondition.value.types = val.types;
queryCondition.value.after_create_time = val.after_create_time;
queryCondition.value.before_create_time = val.before_create_time;
queryCondition.value.after_update_time = val.after_update_time;
queryCondition.value.before_update_time = val.before_update_time;
fetchQuestionList();
};
const handlePageChange = (val: number) => {
queryCondition.value.page = val;
fetchQuestionList();
queryCondition.value.page = page_data.value.page;
queryCondition.value.page_size = page_data.value.page_size;
};
const handlePageSizeChange = (val: number) => {
queryCondition.value.page_size = val;
fetchQuestionList();
queryCondition.value.page = page_data.value.page;
queryCondition.value.page_size = page_data.value.page_size;
};
const handleSortChange = (val: any) => {
queryCondition.value.sort_by = val.prop;
queryCondition.value.sort_order = val.order === 'ascending' ? ORDER_TYPE.ASC : ORDER_TYPE.DESC;
fetchQuestionList();
};
const handleEdit = (index: number, row: any) => {
// todo: go to edit question view
};
const handleDelete = (index: number, row: any) => {
// todo: pop up a warning
};
const fetchQuestionList = () => {
disabled.value = true;
getQuestionsByPage(queryCondition.value)
.then((res) => {
page_data.value = res;
})
.catch((err) => {
console.log(err);
});
disabled.value = false;
};
// todo get tag list
fetchQuestionList();
disabled.value = false;
</script>
<style>
.table-questions {
width: 100%;
}
.table-column {
text-align: center;
}
.el-row {
margin: 4px;
height: auto;
}
.table-item {
margin: 4px;
text-align: center;
}
.pagination {
margin: 20px 0;
}
</style>

View File

@ -0,0 +1,26 @@
<template>
<main>
<el-menu
router
default-active="1"
class="el-menu-vertical-demo"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="/">
<i class="el-icon-menu"></i>
<span slot="title">概览</span>
</el-menu-item>
<el-menu-item index="/questions">
<i class="el-icon-menu"></i>
<span slot="title">题库</span>
</el-menu-item>
</el-menu>
</main>
</template>
<script setup lang="ts">
const handleOpen = (key: string, keyPath: string) => {};
const handleClose = (key: string, keyPath: string) => {};
</script>

View File

@ -1,267 +0,0 @@
<template>
<!-- Condition Filter From -->
<el-form :inline="true" :model="query_condition" class="demo-form-inline">
<el-form-item label="发布状态">
<el-select v-model="publish_statu" placeholder="请选择" @change="handlePublishChange">
<el-option label="全部" value="-1" />
<el-option label="已发布" value="1" />
<el-option label="编辑中" value="0" />
</el-select>
</el-form-item>
<el-form-item label="题型">
<el-select v-model="types" multiple placeholder="请选择" @change="handleTypeChange">
<el-option label="单选题" value="0" />
<el-option label="多选题" value="1" />
<el-option label="判断题" value="2" />
<el-option label="填空题" value="3" />
<el-option label="简答题" value="4" />
<el-option label="文件上传" value="5" />
</el-select>
</el-form-item>
<el-form-item label="标签">
<el-select v-model="query_condition.tag_ids" multiple placeholder="请选择">
<el-option v-for="tag in tag_list" :key="tag.id" :label="tag.name" :value="tag.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input v-model="query_condition.search" placeholder="请输入关键字" />
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="timerange_create"
type="daterange"
range-separator="至"
value-format="x"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="createTimeRangeChange"
/>
<el-button
:icon="CircleCloseFilled"
@click="
timerange_create = [];
query_condition.after_create_time = 0;
query_condition.before_create_time = 0;
"
type="info"
text
/>
</el-form-item>
<el-form-item label="更新时间">
<el-date-picker
v-model="timerange_update"
type="daterange"
range-separator="至"
value-format="x"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="updateTimeRangeChange"
/>
<el-button
:icon="CircleCloseFilled"
@click="
timerange_update = [];
query_condition.after_update_time = 0;
query_condition.before_update_time = 0;
"
type="info"
text
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="fetchQuestionList">搜索</el-button>
</el-form-item>
</el-form>
<!-- Table -->
<el-table
:data="page_data.items"
:table-layout="'auto'"
:default-sort="{ prop: 'id', order: 'ascending' }"
:border="true"
:disabled="disabled"
@sort-change="handleSortChange"
>
<el-table-column prop="id" label="ID" :sortable="'custom'" />
<el-table-column prop="title" label="标题" :sortable="'custom'" />
<el-table-column prop="type" label="题型" :sortable="'custom'">
<template #default="scope">
{{ getQuestionTypeText(scope.row.type) }}
</template>
</el-table-column>
<el-table-column label="正确率" :sortable="'custom'">
<template #default="scope">
{{ scope.row.correct_count.value / scope.row.reference_count.value }} %
</template>
</el-table-column>
<el-table-column prop="reference_count" label="引用次数" :sortable="'custom'" />
<el-table-column prop="is_published" label="发布状态" :sortable="'custom'">
<template #default="scope">
<el-tag v-if="scope.row.is_published" type="success">已发布</el-tag>
<el-tag v-else type="info">编辑中</el-tag>
</template>
</el-table-column>
<el-table-column label="标签">
<template #default="scope">
<div v-if="scope.row.tags.length === 0"></div>
<div v-else-if="scope.row.tags.length > 3">
<el-tag v-for="tag in scope.row.tags.slice(0, 3)" :key="tag.id" :color="tag.color">{{
tag.name
}}</el-tag>
<el-tag type="info">...</el-tag>
</div>
<div v-else>
<el-tag v-for="tag in scope.row.tags" :key="tag.id" type="info">{{ tag.name }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="创建时间" :sortable="'custom'">
<template #default="scope">
{{ scope.row.created_at }}
</template>
</el-table-column>
<el-table-column label="更新时间" :sortable="'custom'">
<template #default="scope">
{{ scope.row.updated_at }}
</template>
</el-table-column>
<!-- edit -->
<el-table-column align="right">
<template #header> 操作 </template>
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<!-- Pagination -->
<el-pagination
v-model:current-page="page_data.page"
v-model:page-size="page_data.page_size"
:page-sizes="[10, 50, 100]"
:disabled="disabled"
:background="true"
layout="prev, pager, next, total, jumper, sizes"
:total="page_data.total_count"
@current-change="handlePageChange"
@size-change="handlePageSizeChange"
/>
</template>
<script lang="ts" setup>
import { defineComponent, ref } from 'vue';
import type { QuestionPage, QuestionQueryCondition } from '@/interfaces/question';
import { ORDER_TYPE } from '@/interfaces/page';
import { getQuestionTypeText, QuestionType } from '@/interfaces/question';
import type { Tag } from '@/interfaces/tag';
import { isNumber } from 'element-plus/es/utils/types.mjs';
import { getQuestionsByPage } from '@/apis/question';
import { CircleCloseFilled } from '@element-plus/icons-vue';
const timerange_create = ref<number[]>([]);
const timerange_update = ref<number[]>([]);
const createTimeRangeChange = (val: string[]) => {
query_condition.value.after_create_time = Number(val[0]) / 1000;
query_condition.value.before_create_time = Number(val[1]) / 1000;
};
const updateTimeRangeChange = (val: string[]) => {
query_condition.value.after_update_time = Number(val[0]) / 1000;
query_condition.value.before_update_time = Number(val[1]) / 1000;
};
const disabled = ref<boolean>(false); // before data loaded, disable pagination
const publish_statu = ref<string>('-1');
const types = ref<string[]>([]);
const page_data = ref<QuestionPage>({
page: 1,
page_size: 10,
total_count: 0,
total_page: 0,
items: [],
});
const tag_list = ref<Tag[]>([]);
const query_condition = ref<QuestionQueryCondition>({
page: 1,
page_size: 10,
sort_by: 'id',
sort_order: ORDER_TYPE.ASC,
search: '',
is_published: -1,
tag_ids: [],
types: [],
after_create_time: 0,
before_create_time: 0,
after_update_time: 0,
before_update_time: 0,
});
const handlePageChange = (val: number) => {
query_condition.value.page = val;
fetchQuestionList();
query_condition.value.page = page_data.value.page;
query_condition.value.page_size = page_data.value.page_size;
};
const handlePublishChange = (val: string) => {
query_condition.value.is_published = Number(val);
};
const handleTypeChange = (val: string[]) => {
types.value = val;
query_condition.value.types = val.map((item) => Number(item));
};
const handlePageSizeChange = (val: number) => {
query_condition.value.page_size = val;
fetchQuestionList();
query_condition.value.page = page_data.value.page;
query_condition.value.page_size = page_data.value.page_size;
};
const handleSortChange = (val: any) => {
query_condition.value.sort_by = val.prop;
query_condition.value.sort_order = val.order === 'ascending' ? ORDER_TYPE.ASC : ORDER_TYPE.DESC;
fetchQuestionList();
};
const handleEdit = (index: number, row: any) => {
// todo: go to edit question view
};
const handleDelete = (index: number, row: any) => {
// todo: pop up a warning
};
const fetchQuestionList = () => {
disabled.value = true;
getQuestionsByPage(query_condition.value)
.then((res) => {
page_data.value = res;
})
.catch((err) => {
console.log(err);
});
disabled.value = false;
};
// todo get tag list
fetchQuestionList();
disabled.value = false;
</script>
<style>
.demo-form-inline {
margin-bottom: 20px;
}
.demo-form-inline .el-form-item {
margin-right: 20px;
}
</style>

View File

@ -1,9 +1,5 @@
<script setup lang="ts">
import TableQuestions from './TableQuestions.vue'
</script>
<script setup lang="ts"></script>
<template>
<main>
<TableQuestions />
</main>
<main></main>
</template>

View File

@ -1,23 +1,21 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import TableQuestion from '@/components/Question/TableQuestions.vue';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
name: '概览',
component: HomeView,
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
}
]
})
path: '/questions',
name: '题库',
component: TableQuestion,
},
],
});
export default router
export default router;

View File

@ -6,7 +6,7 @@
</template>
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue'
import TheWelcome from '../components/TheWelcome.vue';
import { createNewQuestion } from '@/apis/question';
import type { Question } from '@/interfaces/question';
import { QuestionType } from '@/interfaces/question';
@ -23,8 +23,9 @@ const createTest = () => {
reference_count: 0,
correct_count: 0,
create_at: '',
update_at: ''
}
createNewQuestion(question)
}
update_at: '',
tags: [],
};
createNewQuestion(question);
};
</script>