xnx_tag 话题标签
功能
现代 Hashtag 话题系统:发帖时自由输入话题(# 号触发自动补全),支持推荐 / 热门话题
帖子创建 / 编辑时自动同步话题关联(
syncThreadTags对比新旧列表批量增删)话题聚合页:
topic.htm列出所有话题;topic-{tagid}.htm/topic-{tagid}-{page}.htm显示该话题下帖子(分页 + 权限过滤审核状态)自动补全 API:
/autocomplete?q=xxx返回 JSON,前端 Bootstrap 5 + 原生 JS(无 jQuery)热门话题侧边栏 widget(首页
index_site_brief_after.htm)帖子标题后 / 列表显示话题徽章(
TagService::renderBadges支持最多显示 N 个 ++剩余提示)后台管理:话题列表搜索、新增 / 编辑 / 删除、设置 / 取消推荐(含批量)、合并话题(源 → 目标)、重置使用计数
配置项:最大话题数(1-20)、列表显示、详情显示、热门侧边栏、热门数量(1-50)、分页大小(5-100)
联动
DiscoverService:可注册到发现页(图标 / 名称 / 排序)删帖时自动清理话题关联 + 递减使用计数(
onThreadDelete)缓存:热门 / 推荐 / 帖子标签使用
CacheHelper::remember(),写操作后clearHotCache()清理多语言(zh-cn / zh-tw / en-us)
文件结构
plugin/xnx_tag/
├── conf.json
├── install.php / uninstall.php / upgrade.php # 建 xnx_tag / xnx_thread_tag 表
├── setting.php # 后台管理 + 设置
├── icon.png
├── update.md
├── model/
│ └── TagService.php # 核心服务(CRUD / 关联 / 计数 / 合并 / 渲染)
├── route/
│ ├── tag.php # 话题聚合页(topic.htm / topic-{tagid}.htm)
│ └── autocomplete.php # 自动补全 JSON API
├── view/htm/
│ ├── setting.htm # 后台管理页
│ ├── tag.htm # 单话题下帖子列表
│ └── tags_all.htm # 全部话题列表
└── hook/
├── model_inc_file.php # 自动加载 TagService
├── model_route_table_end.php # 注册 tag / autocomplete 路由
├── index_route_case_end.php
├── index_site_brief_after.htm # 首页热门话题侧边栏
├── post_start_init.htm # 发帖页注入话题输入 + 自动补全
├── post_ref_thread_after.htm
├── post_update_post_start.php # 编辑帖子时同步话题
├── thread_create_thread_start.php
├── thread_create_thread_end.php # 帖子创建后写入话题关联
├── model_thread_delete_end.php # 删帖时清理关联
├── thread_subject_after.htm # 详情页标题后徽章
├── thread_list_inc_start.htm
├── thread_list_inc_subject_after.htm # 列表页标题后徽章
├── footer_js_after.htm # 注入话题输入 JS
├── lang_zh_cn_bbs.php
├── lang_zh_tw_bbs.php
└── lang_en_us_bbs.php
配置流程
后台「插件管理」安装并启用 xnx_tag
进入「插件设置」→ 配置最大话题数、显示开关、热门侧边栏数量、分页大小
在「话题管理」可手动新增话题、设置推荐、合并话题、重置计数
测试:发帖时在话题输入框输入
#或关键字 → 自动补全提示 → 保存帖子后话题计数 +1;访问topic.htm查看全部话题;访问topic-{tagid}.htm查看话题下帖子按需在「发现页配置」开启插件入口到发现页
扩展说明
TagService 通过 hook/model_inc_file.php 自动加载,大量公开静态方法可被其他插件调用。
Service 类与方法签名
// ---- CRUD ----
TagService::create(string $name, int $featured = 0): int|false // 名称唯一,已存在则返回已有 ID
TagService::read(int $tagid): array
TagService::update(int $tagid, array $arr): bool
TagService::delete(int $tagid): bool // 同步删除 xnx_thread_tag 关联
TagService::setFeatured(int $tagid, int $featured = 1): bool
TagService::findFeatured(int $limit = 10): array // 300s 缓存,仅返回 threads>0
TagService::findAllFeatured(int $limit = 50): array // 不过滤 threads
// ---- 关联管理 ----
TagService::addTagToThread(int $tagid, int $tid): bool // 已存在返回 true,自动 +1
TagService::removeTagFromThread(int $tagid, int $tid): bool // 自动 -1
TagService::syncThreadTags(int $tid, array $newTagNames): bool // 对比新旧列表批量增删
// ---- 计数维护 ----
TagService::incrementThreads(int $tagid): void
TagService::decrementThreads(int $tagid): void // GREATEST(threads-1, 0)
TagService::resetAllCounts(): bool // 按关联表重置所有 threads 计数
// ---- 合并 ----
TagService::mergeTags(int $sourceTagid, int $targetTagid): bool // 源话题帖子转移到目标,删除源话题
// ---- 查询 ----
TagService::findByName(string $keyword, int $page = 1, int $pagesize = 20): array // LIKE 模糊
TagService::findByTid(int $tid): array // 300s 缓存
TagService::findByTids(array $tids): array // [tid => [tag1, tag2, ...]] 列表页用
TagService::findHot(int $limit = 15): array // 300s 缓存
TagService::autocomplete(string $keyword, int $limit = 10): array
TagService::findList(array $cond = [], int $page = 1, int $pagesize = 20): array
TagService::count(array $cond = []): int
// ---- 设置与工具 ----
TagService::getSettings(): array
TagService::saveSettings(array $arr): bool
TagService::onThreadDelete(int $tid): bool // 删帖时清理关联 + 递减计数
TagService::clearHotCache(): void // CacheHelper::pluginDeletePrefix('tag')
TagService::renderBadges(array $tags, int $maxShow = 0): string // 生成话题徽章 HTML,maxShow>0 时显示 +N
TagService::initCache(): void // 注册缓存键到 CacheHelper
数据表与触发时机
xnx_tag:话题主表(name唯一索引,threads使用次数,featured推荐)xnx_thread_tag:帖子-话题关联表(tagid+tid联合主键)hook/thread_create_thread_end.php与hook/post_update_post_start.php调用syncThreadTags()同步帖子话题hook/model_thread_delete_end.php调用onThreadDelete()清理删帖的话题关联缓存键通过
CacheHelper::registerKeys('tag', ...)注册:hot/thread_tags/featured,前缀p_tag_,写操作后通过clearHotCache()一并清理路由风格伪静态下,话题聚合页 URL 形如
topic-{tagid}-{page}.htm,由核心route_url('topic_page', ...)生成