初始化
This commit is contained in:
24
src/api/question-category.js
Normal file
24
src/api/question-category.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import apiClient from './index';
|
||||||
|
|
||||||
|
export const questionCategoryApi = {
|
||||||
|
// 获取分类树列表
|
||||||
|
getTreeList: () => apiClient.get('/question-category/tree-list'),
|
||||||
|
|
||||||
|
// 获取分类详情
|
||||||
|
getDetail: (id) => apiClient.get(`/question-category/${id}`),
|
||||||
|
|
||||||
|
// 创建分类
|
||||||
|
create: (data) => apiClient.post('/question-category', data),
|
||||||
|
|
||||||
|
// 更新分类
|
||||||
|
update: (id, data) => apiClient.put(`/question-category/${id}`, data),
|
||||||
|
|
||||||
|
// 删除分类
|
||||||
|
delete: (id) => apiClient.delete(`/question-category/${id}`),
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
updateState: (id, state) => apiClient.patch(`/question-category/${id}/state`, { state }),
|
||||||
|
|
||||||
|
// 获取分类选项(用于下拉选择)
|
||||||
|
getOptions: () => apiClient.get('/question-category/options')
|
||||||
|
}
|
||||||
@@ -33,6 +33,10 @@
|
|||||||
<el-icon><MessageBox /></el-icon>
|
<el-icon><MessageBox /></el-icon>
|
||||||
<span>题库管理</span>
|
<span>题库管理</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/question-category">
|
||||||
|
<el-icon><MessageBox /></el-icon>
|
||||||
|
<span>题库分类</span>
|
||||||
|
</el-menu-item>
|
||||||
<el-menu-item index="/history">
|
<el-menu-item index="/history">
|
||||||
<el-icon><Finished /></el-icon>
|
<el-icon><Finished /></el-icon>
|
||||||
<span>会话历史</span>
|
<span>会话历史</span>
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ const routes = [
|
|||||||
name: 'AnswerRecord',
|
name: 'AnswerRecord',
|
||||||
component: () => import('../views/AnswerRecord.vue'),
|
component: () => import('../views/AnswerRecord.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'question-category',
|
||||||
|
name: 'QuestionCategory',
|
||||||
|
component: () => import('@/views/question-category/index.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
13
src/utils/date.js
Normal file
13
src/utils/date.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const formatDate = (dateString) => {
|
||||||
|
if (!dateString) return '-'
|
||||||
|
|
||||||
|
const date = new Date(dateString)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
121
src/views/question-category/components/CategoryForm.vue
Normal file
121
src/views/question-category/components/CategoryForm.vue
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<div class="category-form">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
label-position="right"
|
||||||
|
>
|
||||||
|
<el-form-item label="分类名称" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.name"
|
||||||
|
placeholder="请输入分类名称"
|
||||||
|
maxlength="32"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="上级分类" prop="parent_id">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="formData.parent_id"
|
||||||
|
:data="categoryOptions"
|
||||||
|
:props="treeProps"
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
placeholder="请选择上级分类"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="formData.sort" :min="0" :max="999" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="状态" prop="state">
|
||||||
|
<el-radio-group v-model="formData.state">
|
||||||
|
<el-radio
|
||||||
|
v-for="item in statusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSubmit">提交</el-button>
|
||||||
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { FORM_RULES, CATEGORY_STATUS_OPTIONS } from '../constants/constant.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CategoryForm',
|
||||||
|
props: {
|
||||||
|
initialData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
categoryOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['submit', 'cancel'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const formRef = ref()
|
||||||
|
const formData = reactive({
|
||||||
|
name: '',
|
||||||
|
parent_id: 0,
|
||||||
|
sort: 0,
|
||||||
|
state: 1,
|
||||||
|
...props.initialData
|
||||||
|
})
|
||||||
|
|
||||||
|
const treeProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'name',
|
||||||
|
children: 'children'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value.validate()
|
||||||
|
emit('submit', { ...formData })
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('请完善表单信息')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formRef,
|
||||||
|
formData,
|
||||||
|
formRules: FORM_RULES,
|
||||||
|
statusOptions: CATEGORY_STATUS_OPTIONS,
|
||||||
|
treeProps,
|
||||||
|
handleSubmit,
|
||||||
|
handleCancel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.category-form {
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
115
src/views/question-category/components/CategoryTable.vue
Normal file
115
src/views/question-category/components/CategoryTable.vue
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<div class="category-table">
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
row-key="id"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
v-loading="loading"
|
||||||
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||||
|
>
|
||||||
|
<el-table-column prop="name" label="分类名称" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="level-indent" :style="{ marginLeft: (row.level - 1) * 16 + 'px' }"></span>
|
||||||
|
<i v-if="row.children && row.children.length > 0" class="el-icon-folder"></i>
|
||||||
|
<i v-else class="el-icon-document"></i>
|
||||||
|
<span class="name-text">{{ row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column prop="parent_id" label="上级ID" width="90" />
|
||||||
|
|
||||||
|
<el-table-column prop="level" label="层级" width="70">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small">L{{ row.level }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="sort" label="排序" width="70" />
|
||||||
|
|
||||||
|
<el-table-column prop="state" label="状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.state === 1 ? 'success' : 'danger'" size="small">
|
||||||
|
{{ row.state === 1 ? '启用' : '禁用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="created_time" label="创建时间" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.created_time) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="updated_time" label="更新时间" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.updated_time) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button link type="primary" @click="handleAddChild(row)">添加子类</el-button>
|
||||||
|
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { formatDate } from '@/utils/date'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CategoryTable',
|
||||||
|
props: {
|
||||||
|
tableData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['add-child', 'edit', 'delete'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const handleAddChild = (row) => {
|
||||||
|
emit('add-child', row)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
emit('edit', row)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (row) => {
|
||||||
|
emit('delete', row)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formatDate,
|
||||||
|
handleAddChild,
|
||||||
|
handleEdit,
|
||||||
|
handleDelete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.category-table {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-indent {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-text {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
99
src/views/question-category/components/Create.vue
Normal file
99
src/views/question-category/components/Create.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<div class="category-create">
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>新增题型分类</h2>
|
||||||
|
<el-button @click="goBack">返回列表</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<CategoryForm
|
||||||
|
ref="formRef"
|
||||||
|
:category-options="categoryOptions"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
@cancel="goBack"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import CategoryForm from './CategoryForm.vue'
|
||||||
|
import { questionCategoryApi } from '@/api/question-category.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'QuestionCategoryCreate',
|
||||||
|
components: {
|
||||||
|
CategoryForm
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const formRef = ref()
|
||||||
|
const categoryOptions = ref([])
|
||||||
|
|
||||||
|
const loadCategoryOptions = async () => {
|
||||||
|
try {
|
||||||
|
const data = await questionCategoryApi.getOptions()
|
||||||
|
categoryOptions.value = data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载分类选项失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (formData) => {
|
||||||
|
try {
|
||||||
|
// 如果有parentId参数,优先使用
|
||||||
|
if (route.query.parentId) {
|
||||||
|
formData.parent_id = parseInt(route.query.parentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
await questionCategoryApi.create(formData)
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
goBack()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建分类失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
router.push('/question-category')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadCategoryOptions()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
formRef,
|
||||||
|
categoryOptions,
|
||||||
|
handleSubmit,
|
||||||
|
goBack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.category-create {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
110
src/views/question-category/components/Edit.vue
Normal file
110
src/views/question-category/components/Edit.vue
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<div class="category-edit">
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>编辑题型分类</h2>
|
||||||
|
<el-button @click="goBack">返回列表</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<CategoryForm
|
||||||
|
ref="formRef"
|
||||||
|
:initial-data="formData"
|
||||||
|
:category-options="categoryOptions"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
@cancel="goBack"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import CategoryForm from './CategoryForm.vue'
|
||||||
|
import { questionCategoryApi } from '@/api/question-category.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'QuestionCategoryEdit',
|
||||||
|
components: {
|
||||||
|
CategoryForm
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const formRef = ref()
|
||||||
|
const formData = reactive({
|
||||||
|
name: '',
|
||||||
|
parent_id: 0,
|
||||||
|
sort: 0,
|
||||||
|
state: 1
|
||||||
|
})
|
||||||
|
const categoryOptions = ref([])
|
||||||
|
const categoryId = ref(null)
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
categoryId.value = route.params.id
|
||||||
|
const [detail, options] = await Promise.all([
|
||||||
|
questionCategoryApi.getDetail(categoryId.value),
|
||||||
|
questionCategoryApi.getOptions()
|
||||||
|
])
|
||||||
|
|
||||||
|
Object.assign(formData, detail)
|
||||||
|
categoryOptions.value = options
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载数据失败:', error)
|
||||||
|
ElMessage.error('加载分类信息失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async (submitData) => {
|
||||||
|
try {
|
||||||
|
await questionCategoryApi.update(categoryId.value, submitData)
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
goBack()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新分类失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
router.push('/question-category')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
formRef,
|
||||||
|
formData,
|
||||||
|
categoryOptions,
|
||||||
|
handleSubmit,
|
||||||
|
goBack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.category-edit {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
src/views/question-category/components/SearchBar.vue
Normal file
80
src/views/question-category/components/SearchBar.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-bar">
|
||||||
|
<el-form :model="formData" inline>
|
||||||
|
<el-form-item label="分类名称">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.name"
|
||||||
|
placeholder="请输入分类名称"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="formData.state" placeholder="请选择状态" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in statusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch" icon="Search">搜索</el-button>
|
||||||
|
<el-button @click="handleReset" icon="Refresh">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { CATEGORY_STATUS_OPTIONS } from '../constants/constant.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SearchBar',
|
||||||
|
emits: ['search', 'reset'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const formData = reactive({
|
||||||
|
name: '',
|
||||||
|
state: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusOptions = CATEGORY_STATUS_OPTIONS
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
emit('search', { ...formData })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.assign(formData, {
|
||||||
|
name: '',
|
||||||
|
state: ''
|
||||||
|
})
|
||||||
|
emit('reset')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formData,
|
||||||
|
statusOptions,
|
||||||
|
handleSearch,
|
||||||
|
handleReset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-bar {
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
29
src/views/question-category/constants/constant.js
Normal file
29
src/views/question-category/constants/constant.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 分类状态
|
||||||
|
export const CATEGORY_STATUS = {
|
||||||
|
DISABLED: 0,
|
||||||
|
ENABLED: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CATEGORY_STATUS_MAP = {
|
||||||
|
[CATEGORY_STATUS.DISABLED]: '禁用',
|
||||||
|
[CATEGORY_STATUS.ENABLED]: '启用'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CATEGORY_STATUS_OPTIONS = [
|
||||||
|
{ label: '启用', value: CATEGORY_STATUS.ENABLED },
|
||||||
|
{ label: '禁用', value: CATEGORY_STATUS.DISABLED }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
export const FORM_RULES = {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入分类名称', trigger: 'blur' },
|
||||||
|
{ min: 1, max: 32, message: '长度在 1 到 32 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
parent_id: [
|
||||||
|
{ required: true, message: '请选择上级分类', trigger: 'change' }
|
||||||
|
],
|
||||||
|
sort: [
|
||||||
|
{ required: true, message: '请输入排序值', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,11 +1,159 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="category-index">
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>题型分类管理</h2>
|
||||||
|
<p>管理试题的分类体系,支持多级分类</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<SearchBar
|
||||||
|
@search="handleSearch"
|
||||||
|
@reset="handleResetSearch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="action-bar">
|
||||||
|
<el-button type="primary" @click="handleCreate" icon="Plus">新增分类</el-button>
|
||||||
|
<el-button @click="handleRefresh" icon="Refresh">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CategoryTable
|
||||||
|
:table-data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
@add-child="handleAddChild"
|
||||||
|
@edit="handleEdit"
|
||||||
|
@delete="handleDelete"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<script>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import SearchBar from './components/SearchBar.vue'
|
||||||
|
import CategoryTable from './components/CategoryTable.vue'
|
||||||
|
import { questionCategoryApi } from '@/api/question-category.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'QuestionCategoryIndex',
|
||||||
|
components: {
|
||||||
|
SearchBar,
|
||||||
|
CategoryTable
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const router = useRouter()
|
||||||
|
const tableData = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const searchParams = ref({})
|
||||||
|
|
||||||
|
const loadTableData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await questionCategoryApi.getTreeList()
|
||||||
|
tableData.value = data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载分类数据失败:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = (params) => {
|
||||||
|
searchParams.value = params
|
||||||
|
// 这里可以根据params进行筛选,实际项目中需要后端支持搜索
|
||||||
|
loadTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResetSearch = () => {
|
||||||
|
searchParams.value = {}
|
||||||
|
loadTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
router.push('/question-category/create')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddChild = (row) => {
|
||||||
|
router.push({
|
||||||
|
path: '/question-category/create',
|
||||||
|
query: { parentId: row.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
router.push(`/question-category/edit/${row.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (row) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确定要删除分类 "${row.name}" 吗?此操作不可恢复。`,
|
||||||
|
'警告',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await questionCategoryApi.delete(row.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
loadTableData()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
loadTableData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadTableData()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
tableData,
|
||||||
|
loading,
|
||||||
|
handleSearch,
|
||||||
|
handleResetSearch,
|
||||||
|
handleCreate,
|
||||||
|
handleAddChild,
|
||||||
|
handleEdit,
|
||||||
|
handleDelete,
|
||||||
|
handleRefresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.category-index {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header p {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|||||||
Reference in New Issue
Block a user