分类信息排序方案实施计划(方案 1.0 完整版)
一、项目概述
1.1 方案目标
基于 Discuz 分类信息系统实现目录树和版块列表页的统一排序,解决以下问题:
- 排序一致性:目录树排序和列表页排序保持一致
- 配置简化:发帖时直接设置排序值,无需在配置帖中查 tid
- 教育成本降低:使用下拉单选 + 置顶判断,无需学习特殊格式
- 顶级帖子展示:支持置顶说明帖自动展示在 0 级目录
- 配置帖可控:配置帖可通过选项控制是否展示在目录树中
1.2 核心设计
| 项目 |
值 |
说明 |
| sortid |
5 |
分类名称:文档管理 |
| optionid=21 |
sort_order |
类型:字串 (text),用于帖子排序值 |
| optionid=22 |
is_config |
类型:单选 (radio),0=普通帖子,1=配置帖子 |
| optionid=23 |
show_in_tree |
类型:单选 (radio),0=不展示,1=展示(仅对配置帖有效) |
1.3 判断逻辑
0 级目录(顶级):displayorder > 0 AND is_config = 0
→ 自动展示所有置顶说明帖
配置帖展示:displayorder > 0 AND is_config = 1 AND show_in_tree = 1
→ 配置帖可选择是否展示在目录树中
普通帖子:displayorder = 0
→ 按 sort_order 排序归入对应目录
二、数据库设计
2.1 分类信息项目创建
2.1.1 创建分类信息(sortid=5)
在 Discuz 后台 → 分类信息 → 添加分类信息
| 字段 |
值 |
| 分类名称 |
文档管理 |
| 类型标识 |
document |
| 显示顺序 |
5 |
| 可用 |
是 |
2.1.2 创建选项项目
optionid=21 - sort_order(排序值)
| 字段 |
值 |
| 所属分类 |
sortid=5 |
| 选项类型 |
字串 (text) |
| 选项标题 |
排序权重 |
| 变量名 |
sort_order |
| 输入提示 |
数字越小越靠前,如 1、2、3... |
| 必填 |
否(默认 99) |
optionid=22 - is_config(配置帖标识)
| 字段 |
值 |
| 所属分类 |
sortid=5 |
| 选项类型 |
单选 (radio) |
| 选项标题 |
是否为配置帖 |
| 变量名 |
is_config |
| 选项值 |
0=否\n1=是 |
| 默认值 |
0 |
| 必填 |
是 |
optionid=23 - show_in_tree(目录树展示控制)
| 字段 |
值 |
| 所属分类 |
sortid=5 |
| 选项类型 |
单选 (radio) |
| 选项标题 |
是否展示在目录树 |
| 变量名 |
show_in_tree |
| 选项值 |
0=不展示\n1=展示 |
| 默认值 |
1 |
| 权限设置 |
仅管理员可设置 |
| 说明 |
仅对配置帖有效,普通帖子始终展示 |
2.2 涉及的数据表
-- 分类信息选项定义表
pre_forum_typeoption
-- 分类信息选项数据表
pre_forum_typeoptionvar
-- 分类信息单选值表
pre_forum_typeoptionvalue
-- 帖子表(查询用)
pre_forum_thread
-- 标签表(现有功能)
pre_common_tag
pre_common_tagitem
2.3 索引优化建议
-- 为分类信息表添加组合索引
ALTER TABLE `pre_forum_typeoptionvar`
ADD INDEX `idx_sortid_optionid` (`sortid`, `optionid`);
ALTER TABLE `pre_forum_typeoptionvar`
ADD INDEX `idx_tid_optionid` (`tid`, `optionid`);
-- 为帖子表添加索引(如果尚未存在)
ALTER TABLE `pre_forum_thread`
ADD INDEX `idx_fid_displayorder` (`fid`, `displayorder`);
三、代码实施步骤
3.1 文件修改清单
| 文件 |
修改类型 |
优先级 |
template/xmyc_doc/php/directory_tree.php |
核心重写 |
P0 |
template/xmyc_touch_doc/touch/php/directory_tree.php |
核心重写 |
P0 |
template/xmyc_doc/forum/forumdisplay.htm |
修改 |
P1 |
template/xmyc_touch_doc/touch/forum/forumdisplay.htm |
修改 |
P1 |
3.2 directory_tree.php 核心修改
3.2.1 新增辅助函数
/**
* 批量获取帖子的分类信息值
* @param array $tids 帖子 ID 数组
* @param int $optionid 选项 ID
* @return array [tid => value]
*/
function get_typeoption_values($tids, $optionid) {
$values = array();
if(empty($tids)) return $values;
$tids_str = implode(',', $tids);
$query = DB::query("SELECT tid, value FROM ".DB::table('forum_typeoptionvar')."
WHERE tid IN ($tids_str) AND optionid=".intval($optionid));
while($row = DB::fetch($query)) {
$values[$row['tid']] = $row['value'];
}
return $values;
}
/**
* 获取版块下所有相关帖子(优化版)
* @param int $fid 版块 ID
* @return array ['config'=>[], 'top_sticky'=>[], 'normal'=>[]]
*/
function get_directory_threads_optimized($fid) {
$result = array(
'config' => array(), // 配置帖
'top_sticky' => array(), // 置顶说明帖(0 级目录)
'normal' => array() // 普通帖子
);
$fid = intval($fid);
if(empty($fid)) {
global $_G;
$fid = intval($_G['fid']);
}
// 查询所有置顶帖和带目录标签的普通帖
$query = DB::query("SELECT t.tid, t.subject, t.dateline, t.displayorder
FROM ".DB::table('forum_thread')." t
WHERE t.fid=".$fid."
AND t.subject NOT LIKE '[DIR]%'
AND t.displayorder>=0
ORDER BY t.displayorder DESC, t.dateline DESC");
$threads = array();
while($row = DB::fetch($query)) {
$threads[$row['tid']] = $row;
}
if(empty($threads)) {
return $result;
}
$tids = array_keys($threads);
// 批量获取分类信息
$sort_orders = get_typeoption_values($tids, 21); // sort_order
$is_configs = get_typeoption_values($tids, 22); // is_config
$show_in_trees = get_typeoption_values($tids, 23); // show_in_tree
// 分类帖子
foreach($threads as $tid => $thread) {
$is_config = isset($is_configs[$tid]) ? $is_configs[$tid] : '0';
$show_in_tree = isset($show_in_trees[$tid]) ? $show_in_trees[$tid] : '1';
$sort_order = isset($sort_orders[$tid]) ? $sort_orders[$tid] : '99';
$thread['sort_order'] = $sort_order;
if($thread['displayorder'] > 0 && $is_config == '1') {
// 配置帖:根据 show_in_tree 决定是否加入 0 级目录
if($show_in_tree == '1') {
$result['config'][$tid] = $thread;
}
} elseif($thread['displayorder'] > 0 && $is_config == '0') {
// 置顶说明帖:自动加入 0 级目录
$result['top_sticky'][$tid] = $thread;
} else {
// 普通帖子
$result['normal'][$tid] = $thread;
}
}
return $result;
}
3.2.2 主逻辑修改
// === 原代码 ===
// $_tagged_threads = get_directory_threads($_G['fid']);
// === 新代码 ===
// 使用优化版函数获取帖子
$all_threads = get_directory_threads_optimized($_G['fid']);
// 初始化目录树
$flquery = array(
'choices' => array(),
'curkey' => '',
'source' => 'tag',
'sortid' => 0,
'identifier' => 'dir'
);
// 设置当前选中的目录
$dir_param = isset($_GET['dir']) ? $_GET['dir'] : '';
$flquery['curkey'] = str_replace('.', '-', trim($dir_param));
// 1. 首先添加 0 级目录(置顶帖和配置帖)
$level_0_index = 0;
// 添加置顶说明帖(displayorder > 0 AND is_config = 0)
foreach($all_threads['top_sticky'] as $tid => $thread) {
$thread_key = 'tid_' . $tid;
$flquery['choices'][$thread_key] = array(
'key' => $thread_key,
'num' => $tid,
'level' => 0,
'name' => $thread['subject'],
'type' => 'thread',
'href' => generate_thread_link($tid),
'list' => array(),
'sort_order' => $level_0_index++
);
}
// 添加配置帖(displayorder > 0 AND is_config = 1 AND show_in_tree = 1)
foreach($all_threads['config'] as $tid => $thread) {
$thread_key = 'tid_' . $tid;
$flquery['choices'][$thread_key] = array(
'key' => $thread_key,
'num' => $tid,
'level' => 0,
'name' => $thread['subject'],
'type' => 'thread',
'href' => generate_thread_link($tid),
'list' => array(),
'sort_order' => $level_0_index++
);
}
// 2. 处理目录配置帖,构建目录树结构
if(!empty($directory_config)) {
// 检查是否使用新格式
$is_new_format = false;
foreach($directory_config['directories'] as $num_path => $config) {
if(!empty($config['is_new_format'])) {
$is_new_format = true;
break;
}
}
if($is_new_format) {
// 新格式处理:序号 = 标签名 = 目录名
foreach($directory_config['directories'] as $num_path => $config) {
create_directory_node_new($flquery['choices'], $num_path, $config['name']);
}
// 添加普通帖子到对应目录(通过标签名称匹配)
foreach($all_threads['normal'] as $tid => $thread) {
// 获取帖子标签
$tags_str = get_thread_tags($tid);
$tag_names = extract_directory_tags($tags_str);
foreach($tag_names as $tag_name) {
$num_path = find_num_path_by_tag($directory_config, $tag_name);
if($num_path) {
// 使用分类信息中的 sort_order
$sort_order = intval($thread['sort_order']);
add_thread_to_directory_new($flquery['choices'], $num_path, $thread, $sort_order);
}
}
}
} else {
// 旧格式处理(兼容)
// ...(保持原有逻辑)
}
} else {
// 没有配置帖,按标签自动生成单层目录
foreach($all_threads['normal'] as $tid => $thread) {
$tags_str = get_thread_tags($tid);
$dir_names = extract_directory_tags($tags_str);
foreach($dir_names as $dir_name) {
build_directory_tree($flquery['choices'], $dir_name, $thread, intval($thread['sort_order']));
}
}
}
// === 新增辅助函数:获取帖子标签 ===
function get_thread_tags($tid) {
$tags = '';
$query = DB::query("SELECT tag.tagname FROM ".DB::table('common_tagitem')." ti
INNER JOIN ".DB::table('common_tag')." tag ON ti.tagid=tag.tagid
WHERE ti.itemid=".intval($tid)." AND ti.idtype='tid'");
$tag_arr = array();
while($row = DB::fetch($query)) {
$tag_arr[] = $row['tagname'];
}
if(!empty($tag_arr)) {
$tags = implode(' ', $tag_arr);
}
return $tags;
}
3.3 版块列表页模板修改
3.3.1 forumdisplay.htm 修改要点
在主题列表循环中,添加 sort_order 排序:
// 原代码(按回复时间排序)
// $orderby = 'lastpost';
// 新代码(优先按 sort_order 排序)
// 需要在查询时获取分类信息
$_G['forum']['threads_option_sort'] = 5; // 使用分类信息 sortid=5
$_G['forum']['threads_option_order'] = 21; // 使用 optionid=21 (sort_order)
在模板循环中:
<!-- 在主题列表循环前获取 sort_order -->
<!--{loop $_G['forum_threadlist'] $thread}-->
<!--{if $thread['sortid'] == 5}-->
<!--{eval $sort_order = $thread['option']['21'] ? $thread['option']['21'] : 99;}-->
<!--{/if}-->
<!--{/loop}-->
四、数据迁移计划
4.1 现有帖子数据迁移
4.1.1 迁移脚本(SQL 示例)
-- 1. 为现有配置帖初始化分类信息
-- 查找 [DIR] 开头的置顶帖,设置为配置帖
INSERT INTO `pre_forum_typeoptionvar` (`sortid`, `tid`, `optionid`, `value`)
SELECT 5, tid, 22, '1'
FROM `pre_forum_thread`
WHERE subject LIKE '[DIR]%' AND displayorder > 0
ON DUPLICATE KEY UPDATE value = '1';
-- 2. 为其他置顶帖设置 is_config = 0
INSERT INTO `pre_forum_typeoptionvar` (`sortid`, `tid`, `optionid`, `value`)
SELECT 5, tid, 22, '0'
FROM `pre_forum_thread`
WHERE displayorder > 0 AND subject NOT LIKE '[DIR]%'
ON DUPLICATE KEY UPDATE value = '0';
-- 3. 为带目录标签的普通帖子设置 sort_order = 99(默认)
-- 需要先获取这些帖子的 tid(通过标签关联)
-- 这个步骤建议在 PHP 中完成,因为需要查询标签
-- 4. 为所有普通帖子设置默认值
INSERT INTO `pre_forum_typeoptionvar` (`sortid`, `tid`, `optionid`, `value`)
SELECT 5, tid, 21, '99'
FROM `pre_forum_thread`
WHERE displayorder = 0
ON DUPLICATE KEY UPDATE value = '99';
4.1.2 PHP 迁移脚本
<?php
/**
* 数据迁移脚本:为现有帖子初始化分类信息
* 使用方法:在 Discuz 根目录执行
*/
define('IN_DISCUZ', true);
require './source/class/class_core.php';
$discuz = C::app();
$discuz->init();
// 获取所有帖子
$query = DB::query("SELECT tid, displayorder FROM ".DB::table('forum_thread')." WHERE displayorder>=0");
$migrated_count = 0;
while($thread = DB::fetch($query)) {
$tid = $thread['tid'];
$displayorder = $thread['displayorder'];
// 判断是否为配置帖
$is_config = (strpos($thread['subject'], '[DIR]') === 0 && $displayorder > 0) ? '1' : '0';
// 设置 is_config
DB::insert('forum_typeoptionvar', array(
'sortid' => 5,
'tid' => $tid,
'optionid' => 22,
'value' => $is_config
), false, true);
// 设置 sort_order(普通帖子默认 99)
if($displayorder == 0) {
DB::insert('forum_typeoptionvar', array(
'sortid' => 5,
'tid' => $tid,
'optionid' => 21,
'value' => '99'
), false, true);
}
// 设置 show_in_tree(默认展示)
DB::insert('forum_typeoptionvar', array(
'sortid' => 5,
'tid' => $tid,
'optionid' => 23,
'value' => '1'
), false, true);
$migrated_count++;
}
echo "迁移完成!共迁移 {$migrated_count} 个帖子\n";
?>
4.2 版主操作指引
发帖/编辑帖子时的操作流程:
4.2.1 发布置顶说明帖(0 级目录)
- 发帖时选择分类信息:文档管理
- 设置 是否为配置帖 = 否
- 设置 置顶 = 置顶
- 发布后自动展示在 0 级目录
4.2.2 发布配置帖
- 发帖时选择分类信息:文档管理
- 设置 是否为配置帖 = 是
- 设置 是否展示在目录树 = 是/否(根据需要)
- 设置 置顶 = 置顶
- 发布后可展示在 0 级目录(如果 show_in_tree=1)
4.2.3 发布普通文档帖
- 发帖时选择分类信息:文档管理
- 设置 排序权重 = 数字(如 1、2、3...)
- 设置 是否为配置帖 = 否
- 添加标签:#目录_xxx
- 发布后按 sort_order 归入对应目录
五、测试验证清单
5.1 单元测试
| 测试项 |
操作步骤 |
预期结果 |
状态 |
| 0 级目录 - 置顶说明帖 |
发布 is_config=0 的置顶帖 |
自动展示在 0 级目录 |
☐ |
| 0 级目录 - 配置帖展示 |
发布 is_config=1, show_in_tree=1 的置顶帖 |
展示在 0 级目录 |
☐ |
| 0 级目录 - 配置帖隐藏 |
发布 is_config=1, show_in_tree=0 的置顶帖 |
不展示在 0 级目录 |
☐ |
| 目录树 - 新格式 |
配置新格式,帖子打标签 |
帖子正确归入目录 |
☐ |
| 目录树 - 旧格式 |
配置旧格式,帖子打标签 |
帖子正确归入目录(兼容) |
☐ |
| 排序 - sort_order |
设置不同 sort_order 值 |
按数字从小到大排序 |
☐ |
| 排序 - 默认值 |
不设置 sort_order |
默认 99,排在最后 |
☐ |
| Ajax 请求 |
getdirectory=true |
返回正确 JSON 数据 |
☐ |
5.2 集成测试
| 测试项 |
操作步骤 |
预期结果 |
状态 |
| 目录树与列表页排序一致 |
设置多个帖子 sort_order |
两处排序顺序相同 |
☐ |
| 配置帖编辑 |
修改配置帖的 show_in_tree |
目录树即时更新 |
☐ |
| 帖子移动 |
移动帖子到其他版块 |
分类信息保持 |
☐ |
| 帖子删除 |
删除配置帖 |
目录树自动移除 |
☐ |
| 权限控制 |
普通用户编辑帖子 |
无法修改 show_in_tree |
☐ |
5.3 性能测试
| 测试项 |
指标 |
目标值 |
状态 |
| 目录树生成时间 |
100 个帖子 |
< 100ms |
☐ |
| 分类信息查询 |
批量查询 |
< 50ms |
☐ |
| Ajax 响应时间 |
目录数据请求 |
< 200ms |
☐ |
六、上线部署计划
6.1 部署阶段
阶段一:数据库准备(预计 30 分钟)
- 在 Discuz 后台创建分类信息
- 创建三个选项项目
- 执行索引优化 SQL
- 负责人:数据库管理员
- 验收标准:后台可看到新分类信息
阶段二:代码部署(预计 1 小时)
- 备份原文件
- 部署新版 directory_tree.php
- 部署新版 forumdisplay.htm
- 负责人:开发人员
- 验收标准:语法检查通过
阶段三:数据迁移(预计 2 小时)
- 执行数据迁移脚本
- 验证迁移结果
- 抽查帖子分类信息
- 负责人:开发人员
- 验收标准:所有帖子有默认值
阶段四:测试验证(预计 4 小时)
- 执行单元测试
- 执行集成测试
- 执行性能测试
- 负责人:测试人员
- 验收标准:所有测试项通过
阶段五:上线切换(预计 30 分钟)
- 选择低峰期(如凌晨 2-4 点)
- 切换代码到生产环境
- 验证生产环境功能
- 负责人:运维人员
- 验收标准:生产环境功能正常
6.2 回滚计划
如上线后发现问题,按以下步骤回滚:
- 立即回滚代码:还原 directory_tree.php 到旧版本
- 数据保留:分类信息数据保留,不影响现有功能
- 问题排查:在测试环境复现并修复问题
- 重新上线:问题修复后重新执行上线流程
七、时间估算和里程碑
7.1 时间估算
| 阶段 |
工时 |
备注 |
| 数据库准备 |
0.5 小时 |
创建分类和选项 |
| 代码开发 |
4 小时 |
修改 4 个文件 |
| 数据迁移 |
2 小时 |
脚本编写 + 执行 |
| 测试验证 |
4 小时 |
完整测试用例 |
| 上线部署 |
1 小时 |
含回滚准备 |
| 总计 |
11.5 小时 |
约 1.5 个工作日 |
7.2 里程碑
| 里程碑 |
交付物 |
验收标准 |
| M1:数据库就绪 |
分类信息创建完成 |
后台可见,选项可编辑 |
| M2:代码完成 |
directory_tree.php 修改完成 |
单元测试通过 |
| M3:数据迁移完成 |
帖子分类信息初始化 |
抽查验证通过 |
| M4:测试通过 |
测试报告 |
所有测试项通过 |
| M5:上线成功 |
生产环境验证 |
功能正常,性能达标 |
八、风险与应对
8.1 技术风险
| 风险 |
影响 |
概率 |
应对措施 |
| 分类信息表数据量大 |
查询性能下降 |
中 |
添加索引,分页查询 |
| 迁移脚本执行失败 |
数据不一致 |
低 |
事务处理,回滚机制 |
| 旧格式兼容问题 |
部分目录失效 |
中 |
保留旧代码,单独测试 |
8.2 业务风险
| 风险 |
影响 |
概率 |
应对措施 |
| 用户不会设置分类信息 |
排序混乱 |
高 |
发布操作指引,培训版主 |
| 版主权限设置错误 |
普通用户修改排序 |
中 |
严格权限控制,审核机制 |
| 上线后出现 bug |
用户体验受损 |
中 |
快速回滚,及时修复 |
九、附录
9.1 相关文件路径
template/xmyc_doc/php/directory_tree.php # PC 版核心
template/xmyc_touch_doc/touch/php/directory_tree.php # 移动版核心
template/xmyc_doc/forum/forumdisplay.htm # PC 版列表页
template/xmyc_touch_doc/touch/forum/forumdisplay.htm # 移动版列表页
9.2 数据库表结构
pre_forum_typeoption # 分类信息选项定义
pre_forum_typeoptionvar # 分类信息选项数据
pre_forum_typeoptionvalue # 分类信息单选值
pre_forum_thread # 帖子表
pre_common_tag # 标签表
pre_common_tagitem # 标签项目关联表
9.3 参考资料
文档版本:1.0
创建日期:2026-02-16
创建人:蜗牛