增加科目管理功能

This commit is contained in:
李志强 2025-07-14 17:50:10 +08:00
parent 1bd79c57ee
commit 6f71d177fc
12 changed files with 684 additions and 0 deletions

View File

@ -40,3 +40,14 @@ location / {
## 五、技术支持
- QQ357099073
## 六、数据表结构
1. 题目表(questions)
2. 选择题选项表(question_options)
3. 科目表(subjects)
4. 知识点表(knowledge_points)
5. 题目标签表(question_tags)
6. 题目标签关联表(question_tag_relations)
7. 题目附件表(question_attachments)
8. 题目使用记录表(question_usage_records)
9. 题目统计表(question_statistics)

View File

@ -0,0 +1,253 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
/**
* 后台管理系统-资源管理
*/
namespace app\admin\controller;
use app\admin\controller\BaseController;
use app\admin\model\Tk\TkKnowledgePoints;
use app\admin\model\Tk\TkQuestionAttachments;
use app\admin\model\Tk\TkQuestionOptions;
use app\admin\model\Tk\TkQuestions;
use app\admin\model\Tk\TkQuestionStatistics;
use app\admin\model\Tk\TkQuestionTagRelations;
use app\admin\model\Tk\TkQuestionTags;
use app\admin\model\Tk\TkQuestionUsageRecords;
use app\admin\model\Tk\TkSubjects;
use think\facade\View;
use think\facade\Request;
use think\facade\Db;
use app\admin\controller\LogController as Log;
use think\App;
class TkController extends BaseController
{
//题目分类管理
public function category()
{
try {
$page = input('post.page/d', 1); // 当前页码
$limit = input('post.limit/d', 10); // 每页条数
// 查询总数
$total = TkSubjects::where('is_deleted', 0)->count();
// 查询当前页数据(分页)
$list = TkSubjects::where('is_deleted', 0)
->order('sort ASC, level ASC, id ASC')
->field('id, name, code, parent_id, level, sort')
->page($page, $limit)
->select()
->toArray();
if (Request::isPost()) {
// POST请求返回JSON - 分页数据
return json([
'code' => 0,
'msg' => 'success',
'data' => $list,
'count' => $total
]);
} else {
// GET请求渲染视图传递最大层级
// 这里需要获取全部数据以便计算最大层级
$allList = TkSubjects::where('is_deleted', 0)
->field('level')
->select()
->toArray();
$maxLevel = 0;
if (!empty($allList)) {
$maxLevel = max(array_column($allList, 'level'));
}
View::assign([
'maxLevel' => $maxLevel // 传递最大层级给视图
]);
return View::fetch();
}
} catch (\Exception $e) {
return json([
'code' => 1,
'msg' => '获取分类数据失败',
'data' => [],
'count' => 0,
'error' => $e->getMessage()
]);
}
}
//递归构建树形结构
private function buildTree($item, $list)
{
$item['children'] = [];
foreach ($list as $child) {
if ($child['parent_id'] == $item['id']) {
// 添加层级关系标识
$child['isLeaf'] = ($child['level'] == max(array_column($list, 'level')));
$item['children'][] = $this->buildTree($child, $list);
}
}
return $item;
}
//题目分类增加
public function categoryadd()
{
if (Request::isPost()) {
$name = trim(input('post.name', ''));
$level = trim(input('post.level', ''));
if (empty($name)) {
return json(['code' => 1, 'msg' => '分类名称不能为空']);
}
// 检查是否已存在同名分类
$exists = TkSubjects::where('name', $name)
->where('is_deleted', null)
->find();
if ($exists) {
return json(['code' => 1, 'msg' => '该分类名称已存在']);
}
$data = [
'name' => $name,
'level' => $level,
'status' => 1,
'create_time' => time()
];
$res = TkSubjects::create($data);
if ($res) {
return json(['code' => 0, 'msg' => '新增成功']);
} else {
return json(['code' => 1, 'msg' => '新增失败']);
}
} else {
// GET请求渲染新增分类页面如有需要
return View::fetch();
}
}
//题目管理列表
public function topiclist()
{
if (Request::isPost()) {
$params = [
'category' => input('post.category'),
'name' => input('post.name'),
'uploader' => input('post.uploader')
];
$page = (int) input('post.page', 1);
$limit = (int) input('post.limit', 10);
$query = TkQuestions::where('delete_time', null)
->where('status', 1);
// 分类筛选
if (!empty($params['category'])) {
$cateInfo = TkSubjects::where('name', $params['category'])
->where('delete_time', null)
->where('status', 1)
->field('id')
->find();
if ($cateInfo) {
$query = $query->where('cate', (int) $cateInfo['id']);
}
}
// 名称搜索
if (!empty($params['name'])) {
$query = $query->where('name', 'like', '%' . $params['name'] . '%');
}
// 上传者搜索
if (!empty($params['uploader'])) {
$query = $query->where('uploader', 'like', '%' . $params['uploader'] . '%');
}
$count = $query->count();
$lists = $query->order('id DESC')
->page($page, $limit)
->select()
->each(function ($item) {
// 获取分类信息
$cateInfo = TkSubjects::where('id', (int) $item['cate'])
->field('name, icon')
->find();
if ($cateInfo) {
$item['cate'] = $cateInfo['name'];
if (empty($item['icon']) && !empty($cateInfo['icon'])) {
$item['icon'] = $cateInfo['icon'];
}
}
$item['create_time'] = date('Y-m-d H:i:s', (int) $item['create_time']);
return $item;
});
return json([
'code' => 0,
'msg' => '获取成功',
'count' => $count,
'data' => $lists
]);
} else {
$allCategories = TkSubjects::where('delete_time', null)
->where('status', 1)
->field('id, name, cid, icon')
->order('sort asc, id asc')
->select()
->toArray();
$categories = $this->buildParentChild($allCategories);
View::assign([
'categories' => $categories
]);
return View::fetch();
}
}
// 构建父子结构
private function buildParentChild($lists)
{
$tree = [];
foreach ($lists as $item) {
if ($item['cid'] == 0) {
// 顶级分类
$tree[] = $item;
} else {
// 子分类
foreach ($tree as &$parent) {
if ($parent['id'] == $item['cid']) {
if (!isset($parent['children'])) {
$parent['children'] = [];
}
$parent['children'][] = $item;
break;
}
}
}
}
return $tree;
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkKnowledgePoints extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_knowledge_points';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkQuestionAttachments extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_question_attachments';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkQuestionOptions extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_question_options';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkQuestionStatistics extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_question_statistics';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkQuestionTagRelations extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_question_tag_relations';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkQuestionTags extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_question_tags';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkQuestionUsageRecords extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_question_usage_records';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkQuestions extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_questions';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,30 @@
<?php
/**
* 商业使用授权协议
*
* Copyright (c) 2025 [云泽网]. 保留所有权利.
*
* 本软件仅供评估使用。任何商业用途必须获得书面授权许可。
* 未经授权商业使用本软件属于侵权行为,将承担法律责任。
*
* 授权购买请联系: 357099073@qq.com
* 官方网站: https://www.yunzer.cn
*
* 评估用户须知:
* 1. 禁止移除版权声明
* 2. 禁止用于生产环境
* 3. 禁止转售或分发
*/
namespace app\admin\model\Tk;
use think\Model;
class TkSubjects extends Model
{
// 设置当前模型对应的数据表名称(不含前缀)
protected $name = 'tk_subjects';
// 设置主键
protected $pk = 'id';
}

View File

@ -0,0 +1,150 @@
{include file="public/header" /}
<div class="layui-container" style="margin-top:30px;">
<div class="layui-row">
<div class="layui-col-md12">
<button class="layui-btn layui-btn-normal" id="addCategoryBtn">新增科目</button>
<table class="layui-hide" id="categoryTable" lay-filter="categoryTableFilter"></table>
</div>
</div>
</div>
<!-- 分类表格操作栏模板 -->
<script type="text/html" id="categoryTableBar">
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>
<!-- 新增/编辑分类弹窗模板 -->
<script type="text/html" id="categoryFormTpl">
<form class="layui-form" lay-filter="categoryForm">
<div style="display: flex;padding:20px;flex-direction: column;">
<div class="layui-form-item">
<label class="layui-form-label">科目名称</label>
<div class="layui-input-block">
<input type="text" name="name" required placeholder="请输入科目名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">科目等级</label>
<div class="layui-input-block">
<select name="level" class="layui-input">
<option value="0"></option>
<option value="1">初级</option>
<option value="2">中级</option>
<option value="3">高级</option>
<option value="4">特级</option>
</select>
</div>
</div>
<input type="hidden" name="id">
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-bg-blue" lay-submit lay-filter="submitCategory" type="submit">提交</button>
</div>
</div>
</div>
</form>
</script>
<script>
layui.use(['table', 'layer', 'form'], function () {
var table = layui.table;
var layer = layui.layer;
var form = layui.form;
var $ = layui.$;
// 渲染表格
table.render({
elem: '#categoryTable',
url: '/admin/tk/category',
method: 'post',
page: true,
cols: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'name', title: '科目名称' },
{
field: 'level',
title: '科目等级',
templet: function (d) {
var levelMap = {
1: '初级',
2: '中级',
3: '高级',
4: '特级'
};
return levelMap[d.level] || '无';
}
},
{ fixed: 'right', title: '操作', toolbar: '#categoryTableBar', width: 150 }
]],
page: true,
});
// 新增分类
$('#addCategoryBtn').on('click', function () {
layer.open({
type: 1,
title: '新增分类',
area: ['800px', '600px'],
content: $('#categoryFormTpl').html(),
success: function (layero, index) {
form.render();
form.on('submit(submitCategory)', function (data) {
$.post('/admin/tk/categoryadd', data.field, function (res) {
if (res.code === 0) {
layer.msg('新增成功');
layer.close(index);
table.reload('categoryTable');
} else {
layer.msg(res.msg || '新增失败');
}
}, 'json');
return false;
});
}
});
});
// 表格操作事件
table.on('tool(categoryTableFilter)', function (obj) {
var data = obj.data;
if (obj.event === 'edit') {
layer.open({
type: 1,
title: '编辑分类',
area: ['400px', '250px'],
content: $('#categoryFormTpl').html(),
success: function (layero, index) {
form.val('categoryForm', data);
form.render();
form.on('submit(submitCategory)', function (formData) {
$.post('/admin/tk/category/edit', formData.field, function (res) {
if (res.code === 0) {
layer.msg('编辑成功');
layer.close(index);
table.reload('categoryTable');
} else {
layer.msg(res.msg || '编辑失败');
}
}, 'json');
return false;
});
}
});
} else if (obj.event === 'del') {
layer.confirm('确定删除该分类吗?', function (index) {
$.post('/admin/tk/category/delete', { id: data.id }, function (res) {
if (res.code === 0) {
layer.msg('删除成功');
table.reload('categoryTable');
} else {
layer.msg(res.msg || '删除失败');
}
}, 'json');
layer.close(index);
});
}
});
});
</script>