feat: add community post categories and channels

This commit is contained in:
hajimi
2026-08-02 17:39:52 +08:00
parent 95f03af60e
commit b0b278d11f
18 changed files with 1132 additions and 12 deletions
+45
View File
@@ -25,6 +25,21 @@ export function communityPostSetHot(params: any) {
return request.post({ url: '/community.communityPost/setHot', params })
}
// 帖子设推荐
export function communityPostSetRecommend(params: any) {
return request.post({ url: '/community.communityPost/setRecommend', params })
}
// 帖子设话题
export function communityPostSetTopic(params: any) {
return request.post({ url: '/community.communityPost/setTopic', params })
}
// 帖子设置分类
export function communityPostSetCategory(params: any) {
return request.post({ url: '/community.communityPost/setCategory', params })
}
// 删除帖子
export function communityPostDelete(params: any) {
return request.post({ url: '/community.communityPost/delete', params })
@@ -60,6 +75,36 @@ export function communityTagDetail(params: any) {
return request.get({ url: '/community.communityTag/detail', params })
}
// 帖子分类列表
export function communityCategoryLists(params?: any) {
return request.get({ url: '/community.communityCategory/lists', params })
}
// 全部帖子分类
export function communityCategoryAll(params?: any) {
return request.get({ url: '/community.communityCategory/all', params })
}
// 帖子分类详情
export function communityCategoryDetail(params: any) {
return request.get({ url: '/community.communityCategory/detail', params })
}
// 新增帖子分类
export function communityCategoryAdd(params: any) {
return request.post({ url: '/community.communityCategory/add', params })
}
// 编辑帖子分类
export function communityCategoryEdit(params: any) {
return request.post({ url: '/community.communityCategory/edit', params })
}
// 删除帖子分类
export function communityCategoryDelete(params: any) {
return request.post({ url: '/community.communityCategory/delete', params })
}
// 评论列表
export function communityCommentLists(params?: any) {
return request.get({ url: '/community.communityComment/lists', params })
@@ -0,0 +1,178 @@
<template>
<div class="community-category">
<el-card class="!border-none" shadow="never">
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
<el-form-item class="w-[280px]" label="分类名称">
<el-input
v-model="queryParams.name"
placeholder="输入分类名称"
clearable
@keyup.enter="resetPage"
/>
</el-form-item>
<el-form-item class="w-[220px]" label="分类状态">
<el-select v-model="queryParams.status" clearable>
<el-option label="全部" value="" />
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="resetPage">查询</el-button>
<el-button @click="resetParams">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<el-button
v-perms="['community.communityCategory/add']"
type="primary"
class="mb-4"
@click="handleAdd"
>
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增分类
</el-button>
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
<el-table-column label="ID" prop="id" min-width="60" />
<el-table-column label="分类名称" prop="name" min-width="140" />
<el-table-column label="图标" min-width="80">
<template #default="{ row }">
<el-avatar v-if="row.icon" :src="row.icon" :size="36" shape="square" />
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="帖子数" prop="post_count" min-width="80" />
<el-table-column label="排序" prop="sort" min-width="80" />
<el-table-column label="状态" min-width="80">
<template #default="{ row }">
<el-tag v-if="row.status === 1" type="success">启用</el-tag>
<el-tag v-else type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button
v-perms="['community.communityCategory/edit']"
type="primary"
link
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button
v-perms="['community.communityCategory/delete']"
type="danger"
link
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="flex justify-end mt-4">
<pagination v-model="pager" @change="getLists" />
</div>
</el-card>
<el-dialog v-model="showEdit" :title="editData.id ? '编辑分类' : '新增分类'" width="500px">
<el-form ref="editFormRef" :model="editData" :rules="editRules" label-width="80px">
<el-form-item label="分类名称" prop="name">
<el-input v-model="editData.name" placeholder="请输入分类名称" />
</el-form-item>
<el-form-item label="分类图标" prop="icon">
<div>
<material-picker v-model="editData.icon" :limit="1" size="80px" />
<div class="form-tips">建议上传正方形图标</div>
</div>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="editData.sort" :min="0" :max="9999" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="editData.status" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showEdit = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="communityCategory">
import {
communityCategoryAdd,
communityCategoryDelete,
communityCategoryEdit,
communityCategoryLists
} from '@/api/community'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import type { FormInstance } from 'element-plus'
const queryParams = reactive({
name: '',
status: ''
})
const { pager, getLists, resetPage, resetParams } = usePaging({
fetchFun: communityCategoryLists,
params: queryParams
})
const showEdit = ref(false)
const editFormRef = ref<FormInstance>()
const editData = reactive({
id: 0,
name: '',
icon: '',
sort: 0,
status: 1
})
const editRules = {
name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
}
const handleAdd = () => {
Object.assign(editData, { id: 0, name: '', icon: '', sort: 0, status: 1 })
showEdit.value = true
}
const handleEdit = (row: any) => {
Object.assign(editData, {
id: row.id,
name: row.name,
icon: row.icon || '',
sort: row.sort,
status: row.status
})
showEdit.value = true
}
const handleSubmit = async () => {
await editFormRef.value?.validate()
if (editData.id) {
await communityCategoryEdit(editData)
} else {
await communityCategoryAdd(editData)
}
showEdit.value = false
getLists()
}
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除该分类?')
await communityCategoryDelete({ id })
getLists()
}
getLists()
</script>
+103 -1
View File
@@ -17,6 +17,35 @@
<el-option label="赛事分析" :value="1" />
</el-select>
</el-form-item>
<el-form-item class="w-[240px]" label="帖子分类">
<el-select v-model="queryParams.category_id" clearable filterable>
<el-option label="全部" value="" />
<el-option label="未分类" :value="0" />
<el-option v-for="item in categoryList" :key="item.id"
:label="item.status === 1 ? item.name : `${item.name}(禁用)`" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item class="w-[200px]" label="推荐">
<el-select v-model="queryParams.is_recommend" clearable>
<el-option label="全部" value="" />
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
<el-form-item class="w-[200px]" label="热门">
<el-select v-model="queryParams.is_hot" clearable>
<el-option label="全部" value="" />
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
<el-form-item class="w-[200px]" label="话题">
<el-select v-model="queryParams.is_topic" clearable>
<el-option label="全部" value="" />
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="resetPage">查询</el-button>
<el-button @click="resetParams">重置</el-button>
@@ -42,6 +71,17 @@
<el-tag v-else>普通</el-tag>
</template>
</el-table-column>
<el-table-column label="分类" min-width="150">
<template #default="{ row }">
<el-select v-perms="['community.communityPost/setCategory']" v-model="row.category_id"
filterable @change="handleSetCategory($event, row.id)">
<el-option label="未分类" :value="0" />
<el-option v-for="item in categoryList" :key="item.id"
:label="item.status === 1 ? item.name : `${item.name}(禁用)`" :value="item.id"
:disabled="item.status !== 1" />
</el-select>
</template>
</el-table-column>
<el-table-column label="付费" min-width="80">
<template #default="{ row }">
<el-tag v-if="row.is_paid" type="danger">{{ row.price_points }}积分</el-tag>
@@ -57,12 +97,24 @@
:inactive-value="0" @change="handleSetTop($event, row.id)" />
</template>
</el-table-column>
<el-table-column label="推荐" min-width="70">
<template #default="{ row }">
<el-switch v-perms="['community.communityPost/setRecommend']" v-model="row.is_recommend"
:active-value="1" :inactive-value="0" @change="handleSetRecommend($event, row.id)" />
</template>
</el-table-column>
<el-table-column label="热门" min-width="70">
<template #default="{ row }">
<el-switch v-perms="['community.communityPost/setHot']" v-model="row.is_hot" :active-value="1"
:inactive-value="0" @change="handleSetHot($event, row.id)" />
</template>
</el-table-column>
<el-table-column label="话题" min-width="70">
<template #default="{ row }">
<el-switch v-perms="['community.communityPost/setTopic']" v-model="row.is_topic"
:active-value="1" :inactive-value="0" @change="handleSetTopic($event, row.id)" />
</template>
</el-table-column>
<el-table-column label="状态" min-width="80">
<template #default="{ row }">
<el-tag v-if="row.status === 0" type="warning">待审核</el-tag>
@@ -129,7 +181,10 @@
<el-descriptions-item label="付费积分">{{ detailData.is_paid ? detailData.price_points + '积分' : '免费'
}}</el-descriptions-item>
<el-descriptions-item label="置顶">{{ detailData.is_top ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="分类">{{ detailData.category_name || '未分类' }}</el-descriptions-item>
<el-descriptions-item label="推荐">{{ detailData.is_recommend ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="热门">{{ detailData.is_hot ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="话题">{{ detailData.is_topic ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ detailData.update_time || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
@@ -146,6 +201,10 @@ import {
communityPostStatus,
communityPostSetTop,
communityPostSetHot,
communityPostSetRecommend,
communityPostSetTopic,
communityPostSetCategory,
communityCategoryAll,
communityPostDelete
} from '@/api/community'
import { usePaging } from '@/hooks/usePaging'
@@ -153,9 +212,15 @@ import feedback from '@/utils/feedback'
const queryParams = reactive({
status: '',
post_type: ''
post_type: '',
category_id: '',
is_recommend: '',
is_hot: '',
is_topic: ''
})
const categoryList = ref<any[]>([])
const { pager, getLists, resetPage, resetParams } = usePaging({
fetchFun: communityPostLists,
params: queryParams
@@ -185,6 +250,33 @@ const handleSetHot = async (is_hot: any, id: number) => {
}
}
const handleSetRecommend = async (is_recommend: any, id: number) => {
try {
await communityPostSetRecommend({ id, is_recommend })
getLists()
} catch (error) {
getLists()
}
}
const handleSetTopic = async (is_topic: any, id: number) => {
try {
await communityPostSetTopic({ id, is_topic })
getLists()
} catch (error) {
getLists()
}
}
const handleSetCategory = async (category_id: any, id: number) => {
try {
await communityPostSetCategory({ id, category_id })
getLists()
} catch (error) {
getLists()
}
}
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除该帖子?')
await communityPostDelete({ id })
@@ -206,5 +298,15 @@ const handleDetail = async (id: number) => {
}
}
const loadCategories = async () => {
try {
const categories = await communityCategoryAll()
categoryList.value = Array.isArray(categories) ? categories : []
} catch {
categoryList.value = []
}
}
loadCategories()
getLists()
</script>
@@ -0,0 +1,232 @@
CREATE TABLE IF NOT EXISTS `la_community_post_category` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '分类名称',
`icon` varchar(500) NOT NULL DEFAULT '' COMMENT '分类图标',
`sort` int unsigned NOT NULL DEFAULT 0 COMMENT '排序',
`post_count` int unsigned NOT NULL DEFAULT 0 COMMENT '帖子数',
`status` tinyint unsigned NOT NULL DEFAULT 1 COMMENT '0禁用1启用',
`create_time` int unsigned NOT NULL DEFAULT 0,
`update_time` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`),
KEY `idx_status_sort` (`status`, `sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='社区帖子分类';
SET @has_category_id = (
SELECT COUNT(*)
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'la_community_post'
AND `column_name` = 'category_id'
);
SET @sql = IF(
@has_category_id = 0,
'ALTER TABLE `la_community_post` ADD COLUMN `category_id` int unsigned NOT NULL DEFAULT 0 COMMENT ''帖子主分类ID'' AFTER `post_type`',
'SELECT ''la_community_post.category_id already exists'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @has_is_topic = (
SELECT COUNT(*)
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'la_community_post'
AND `column_name` = 'is_topic'
);
SET @sql = IF(
@has_is_topic = 0,
'ALTER TABLE `la_community_post` ADD COLUMN `is_topic` tinyint unsigned NOT NULL DEFAULT 0 COMMENT ''是否话题 0否 1是'' AFTER `is_recommend`',
'SELECT ''la_community_post.is_topic already exists'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @has_category_index = (
SELECT COUNT(*)
FROM `information_schema`.`statistics`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'la_community_post'
AND `index_name` = 'idx_category'
);
SET @sql = IF(
@has_category_index = 0,
'ALTER TABLE `la_community_post` ADD INDEX `idx_category` (`category_id`)',
'SELECT ''la_community_post.idx_category already exists'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @has_recommend_index = (
SELECT COUNT(*)
FROM `information_schema`.`statistics`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'la_community_post'
AND `index_name` = 'idx_status_recommend'
);
SET @sql = IF(
@has_recommend_index = 0,
'ALTER TABLE `la_community_post` ADD INDEX `idx_status_recommend` (`status`, `is_recommend`, `create_time`)',
'SELECT ''la_community_post.idx_status_recommend already exists'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @has_hot_index = (
SELECT COUNT(*)
FROM `information_schema`.`statistics`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'la_community_post'
AND `index_name` = 'idx_status_hot'
);
SET @sql = IF(
@has_hot_index = 0,
'ALTER TABLE `la_community_post` ADD INDEX `idx_status_hot` (`status`, `is_hot`, `create_time`)',
'SELECT ''la_community_post.idx_status_hot already exists'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @has_topic_index = (
SELECT COUNT(*)
FROM `information_schema`.`statistics`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'la_community_post'
AND `index_name` = 'idx_status_topic'
);
SET @sql = IF(
@has_topic_index = 0,
'ALTER TABLE `la_community_post` ADD INDEX `idx_status_topic` (`status`, `is_topic`, `create_time`)',
'SELECT ''la_community_post.idx_status_topic already exists'''
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
INSERT IGNORE INTO `la_community_post_category`
(`id`, `name`, `icon`, `sort`, `post_count`, `status`, `create_time`, `update_time`)
SELECT
`id`, `name`, `icon`, `sort`, `post_count`, `status`, `create_time`, UNIX_TIMESTAMP()
FROM `la_community_tag`;
UPDATE `la_community_post` post
INNER JOIN (
SELECT ranked.`post_id`, ranked.`tag_id` AS `category_id`
FROM (
SELECT
post_tag.`post_id`,
post_tag.`tag_id`,
ROW_NUMBER() OVER (
PARTITION BY post_tag.`post_id`
ORDER BY tag.`sort` DESC, post_tag.`id` ASC
) AS row_number
FROM `la_community_post_tag` post_tag
INNER JOIN `la_community_tag` tag ON tag.`id` = post_tag.`tag_id`
INNER JOIN `la_community_post_category` category ON category.`id` = post_tag.`tag_id`
) ranked
WHERE ranked.row_number = 1
) selected_category ON selected_category.`post_id` = post.`id`
SET post.`category_id` = selected_category.`category_id`
WHERE post.`category_id` = 0;
UPDATE `la_community_post` post
SET post.`is_topic` = 1
WHERE post.`is_topic` = 0
AND EXISTS (
SELECT 1
FROM `la_community_post_tag` post_tag
WHERE post_tag.`post_id` = post.`id`
);
UPDATE `la_community_post_category` category
LEFT JOIN (
SELECT `category_id`, COUNT(*) AS `post_count`
FROM `la_community_post`
WHERE `delete_time` IS NULL
AND `category_id` > 0
GROUP BY `category_id`
) post_stat ON post_stat.`category_id` = category.`id`
SET category.`post_count` = COALESCE(post_stat.`post_count`, 0),
category.`update_time` = UNIX_TIMESTAMP();
SET @community_parent_id = COALESCE(
(SELECT `pid` FROM `la_system_menu` WHERE `perms` = 'community.communityPost/lists' LIMIT 1),
(SELECT `pid` FROM `la_system_menu` WHERE `component` = 'community/post/index' LIMIT 1),
(SELECT `pid` FROM `la_system_menu` WHERE `perms` = 'community.communityTag/lists' LIMIT 1),
(SELECT `id` FROM `la_system_menu` WHERE `type` = 'M' AND `paths` = 'community' LIMIT 1),
0
);
INSERT INTO `la_system_menu`
(`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`)
SELECT
@community_parent_id, 'C', '帖子分类', '', 0, 'community.communityCategory/lists', 'category', 'community/category/index', '', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
WHERE @community_parent_id > 0
AND NOT EXISTS (
SELECT 1 FROM `la_system_menu` WHERE `perms` = 'community.communityCategory/lists'
);
SET @category_menu_id = (
SELECT `id` FROM `la_system_menu`
WHERE `perms` = 'community.communityCategory/lists'
LIMIT 1
);
INSERT INTO `la_system_menu`
(`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`)
SELECT @category_menu_id, 'A', permission.`name`, '', 0, permission.`perms`, '', '', '', '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
FROM (
SELECT '分类选项' AS `name`, 'community.communityCategory/all' AS `perms`
UNION ALL SELECT '分类详情', 'community.communityCategory/detail'
UNION ALL SELECT '分类新增', 'community.communityCategory/add'
UNION ALL SELECT '分类编辑', 'community.communityCategory/edit'
UNION ALL SELECT '分类删除', 'community.communityCategory/delete'
) permission
WHERE @category_menu_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM `la_system_menu` existing_menu WHERE existing_menu.`perms` = permission.`perms`
);
SET @post_menu_id = COALESCE(
(SELECT `id` FROM `la_system_menu` WHERE `perms` = 'community.communityPost/lists' LIMIT 1),
(SELECT `id` FROM `la_system_menu` WHERE `component` = 'community/post/index' LIMIT 1)
);
INSERT INTO `la_system_menu`
(`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`)
SELECT @post_menu_id, 'A', permission.`name`, '', 0, permission.`perms`, '', '', '', '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
FROM (
SELECT '设置推荐' AS `name`, 'community.communityPost/setRecommend' AS `perms`
UNION ALL SELECT '设置话题', 'community.communityPost/setTopic'
UNION ALL SELECT '设置分类', 'community.communityPost/setCategory'
) permission
WHERE @post_menu_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM `la_system_menu` existing_menu WHERE existing_menu.`perms` = permission.`perms`
);
INSERT INTO `la_system_role_menu` (`role_id`, `menu_id`)
SELECT 1, menu.`id`
FROM `la_system_menu` menu
WHERE menu.`perms` IN (
'community.communityCategory/lists',
'community.communityCategory/all',
'community.communityCategory/detail',
'community.communityCategory/add',
'community.communityCategory/edit',
'community.communityCategory/delete',
'community.communityPost/setRecommend',
'community.communityPost/setTopic',
'community.communityPost/setCategory'
)
AND NOT EXISTS (
SELECT 1
FROM `la_system_role_menu` role_menu
WHERE role_menu.`role_id` = 1
AND role_menu.`menu_id` = menu.`id`
);
@@ -0,0 +1,170 @@
# Community Post Channels and Categories Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为社区帖子补齐推荐、热门、话题频道管理,并新增独立帖子分类表、管理后台和前台频道筛选。
**Architecture:** 复用 `la_community_post.is_recommend``is_hot`,只新增 `is_topic` 和单选 `category_id`。分类通过独立 `la_community_post_category` 管理,初始化时复制标签并回填历史帖子;标签多选关系继续保留。后台使用 likeadmin Controller/Logic/Validate/Lists 分层,用户端列表通过现有 `/api/community/postLists` 参数筛选。
**Tech Stack:** PHP 8、ThinkPHP/likeadmin、MySQL 8、Vue 3、Element Plus、uni-app、TypeScript
---
### Task 1: 数据库结构、初始化数据与菜单权限
**Files:**
- Create: `docs/sql/create_community_post_categories.sql`
- Modify: `sbnews.sql`
- [x] **Step 1: 创建独立分类表和帖子字段**
SQL 必须幂等创建 `la_community_post_category`,字段为 `id/name/icon/sort/post_count/status/create_time/update_time`,并在 `la_community_post` 增加:
```sql
`category_id` int unsigned NOT NULL DEFAULT 0 COMMENT '帖子主分类ID',
`is_topic` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '是否话题 0否 1是'
```
同时添加 `category_id``status + is_recommend``status + is_hot``status + is_topic` 查询索引。
- [x] **Step 2: 从标签初始化分类并回填历史帖子**
分类初始化使用 `INSERT IGNORE ... SELECT` 保留标签 ID,不覆盖后续独立维护的分类。历史帖子只在 `category_id=0` 时回填,按标签 `sort DESC, community_post_tag.id ASC` 选取一个主分类;已有标签的帖子初始化为 `is_topic=1` 以保持原话题流可见性,最后按实际帖子数重算 `post_count`
- [x] **Step 3: 初始化管理菜单与按钮权限**
新增 `community.communityCategory/lists` 页面及 `detail/add/edit/delete` 按钮权限;补充帖子 `setRecommend/setTopic/setCategory` 权限。所有菜单通过已有社区帖子或标签菜单反查父级,使用 `NOT EXISTS` 幂等插入,并授权角色 `role_id=1`
### Task 2: 后端分类管理和帖子管理接口
**Files:**
- Create: `server/app/common/model/community/CommunityCategory.php`
- Modify: `server/app/common/model/community/CommunityPost.php`
- Create: `server/app/adminapi/controller/community/CommunityCategoryController.php`
- Create: `server/app/adminapi/logic/community/CommunityCategoryLogic.php`
- Create: `server/app/adminapi/validate/community/CommunityCategoryValidate.php`
- Create: `server/app/adminapi/lists/community/CommunityCategoryLists.php`
- Modify: `server/app/adminapi/controller/community/CommunityPostController.php`
- Modify: `server/app/adminapi/logic/community/CommunityPostLogic.php`
- Modify: `server/app/adminapi/validate/community/CommunityPostValidate.php`
- Modify: `server/app/adminapi/lists/community/CommunityPostLists.php`
- [x] **Step 1: 实现分类 CRUD**
分类接口沿用标签管理风格:
```text
GET /adminapi/community.communityCategory/lists
GET /adminapi/community.communityCategory/all
GET /adminapi/community.communityCategory/detail
POST /adminapi/community.communityCategory/add
POST /adminapi/community.communityCategory/edit
POST /adminapi/community.communityCategory/delete
```
分类图标使用 `FileService::getFileUrl/setFileUrl`;删除前查询 `CommunityPost::where('category_id', id)->count()`,存在引用时返回“该分类下存在帖子,不能删除”。
- [x] **Step 2: 扩展帖子管理字段**
帖子列表筛选增加:
```php
'=' => ['status', 'post_type', 'category_id', 'is_top', 'is_hot', 'is_recommend', 'is_topic', 'user_id']
```
列表和详情批量补充 `category_name`。新增 `setTopic()``setCategory()`;复用已有 `setRecommend()`。设置分类和删除帖子后重算受影响分类的 `post_count`
- [x] **Step 3: 补齐参数校验**
`is_topic``is_hot``is_recommend` 只允许 `0/1``category_id` 允许 `0`,大于 `0` 时必须存在于分类表。控制器继续使用 likeadmin 统一成功/失败响应。
### Task 3: 前台帖子频道筛选
**Files:**
- Modify: `server/app/api/lists/community/CommunityPostLists.php`
- Modify: `uniapp/src/pages/community/community.vue`
- [x] **Step 1: 后端列表接收频道参数**
`queryWhere()` 对明确传入的值进行过滤:
```php
foreach (['is_recommend', 'is_hot', 'is_topic', 'category_id'] as $field) {
if (isset($this->params[$field]) && $this->params[$field] !== '') {
$where[] = [$field, '=', (int) $this->params[$field]];
}
}
```
列表字段返回 `category_id/is_topic`,并批量补充 `category_name`;排序保持 `is_top DESC, is_hot DESC, create_time DESC`
- [x] **Step 2: 用户端频道发送真实筛选条件**
社区页请求参数调整为:
```ts
if (activeFeed.value === 'recommend') params.is_recommend = 1
if (activeFeed.value === 'follow') params.follow = 1
if (activeFeed.value === 'hot') params.is_hot = 1
if (activeFeed.value === 'topic') params.is_topic = 1
if (activeTagId.value > 0) params.tag_id = activeTagId.value
```
移除仅对当前页进行热门分数排序的 `rankHotPosts()`,防止分页结果与后台热门开关不一致。
### Task 4: 管理端分类页面和帖子字段管理
**Files:**
- Create: `admin/src/views/community/category/index.vue`
- Modify: `admin/src/api/community.ts`
- Modify: `admin/src/views/community/post/index.vue`
- [x] **Step 1: 新增分类 API 和管理页**
API 增加分类 `lists/all/detail/add/edit/delete`。分类页复用标签页布局,支持名称搜索、图标、排序、状态、新增、编辑和受保护删除。
- [x] **Step 2: 补齐帖子 API**
新增调用:
```ts
communityPostSetRecommend({ id, is_recommend })
communityPostSetTopic({ id, is_topic })
communityPostSetCategory({ id, category_id })
```
- [x] **Step 3: 扩展帖子列表管理**
搜索区增加分类、推荐、热门、话题筛选;表格增加分类单选框,以及推荐、热门、话题三个开关。接口失败时重新获取列表恢复服务端真实值,详情弹窗展示分类和三个频道状态。
### Task 5: Review、静态验证、提交与测试服同步
**Files:**
- Modify: `docs/业务进度管理.md`
- Create: `docs/superpowers/plans/2026-08-02-community-post-channels-and-categories.md`
- [x] **Step 1: 静态检查**
运行所有修改 PHP 文件的 `php -l``git diff --check`、SQL 未完成标记扫描,并检查 Controller/Logic/Validate/Lists/Model/Admin API 字段名称一致。项目未授权执行完整构建或自动化测试。
- [x] **Step 2: 前端代码 review**
检查管理端请求失败回滚、权限标识、选择器数值类型、分页筛选参数和用户端频道切换请求,确认不存在客户端分页内伪排序或未使用导入。
- [ ] **Step 3: 提交源码**
只暂存本任务文件,排除工作区原有 `AGENTS.md` 和共享文档混合改动,提交信息:
```text
feat: add community post categories and channels
```
- [ ] **Step 4: 同步测试服并应用数据库初始化**
先执行项目规定部署命令:
```powershell
Set-Location D:\www\gs-sport-era; powershell -ExecutionPolicy Bypass -File .\scripts\deploy-server.ps1 -Sudo -AutoIncremental -AutoIncrementalCommits 3 -RemoteDir /www/wwwroot/test-server.sbnews.net
```
随后在测试服运行幂等 SQL `docs/sql/create_community_post_categories.sql`,复核分类表、帖子新字段和社区分类菜单已经存在。
+24
View File
@@ -328,6 +328,24 @@ CREATE TABLE `la_community_like` (
INDEX `idx_target`(`target_id` ASC, `target_type` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '点赞' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for la_community_post_category
-- ----------------------------
DROP TABLE IF EXISTS `la_community_post_category`;
CREATE TABLE `la_community_post_category` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '分类名称',
`icon` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '分类图标',
`sort` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序',
`post_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '帖子数',
`status` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '0禁用1启用',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
`update_time` int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_name`(`name` ASC) USING BTREE,
INDEX `idx_status_sort`(`status` ASC, `sort` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '社区帖子分类' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for la_community_post
-- ----------------------------
@@ -339,6 +357,7 @@ CREATE TABLE `la_community_post` (
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '帖子内容',
`images` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '图片地址JSON数组',
`post_type` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '0普通帖1赛事推荐2战绩分享',
`category_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '帖子主分类ID',
`match_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联赛事ID(推荐帖)',
`is_paid` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '0免费1付费',
`price_points` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '所需积分',
@@ -352,6 +371,7 @@ CREATE TABLE `la_community_post` (
`is_top` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '0否1置顶',
`is_hot` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '0否1热门',
`is_recommend` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否推荐 0否 1是',
`is_topic` tinyint UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否话题 0否 1是',
`status` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '0待审核1正常2隐藏',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
`update_time` int UNSIGNED NOT NULL DEFAULT 0,
@@ -360,7 +380,11 @@ CREATE TABLE `la_community_post` (
UNIQUE INDEX `idx_origin_id`(`origin_id` ASC) USING BTREE,
INDEX `idx_user`(`user_id` ASC) USING BTREE,
INDEX `idx_type`(`post_type` ASC) USING BTREE,
INDEX `idx_category`(`category_id` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE,
INDEX `idx_status_recommend`(`status` ASC, `is_recommend` ASC, `create_time` ASC) USING BTREE,
INDEX `idx_status_hot`(`status` ASC, `is_hot` ASC, `create_time` ASC) USING BTREE,
INDEX `idx_status_topic`(`status` ASC, `is_topic` ASC, `create_time` ASC) USING BTREE,
INDEX `idx_create`(`create_time` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 331 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '社区帖子' ROW_FORMAT = Dynamic;
@@ -0,0 +1,54 @@
<?php
namespace app\adminapi\controller\community;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\lists\community\CommunityCategoryLists;
use app\adminapi\logic\community\CommunityCategoryLogic;
use app\adminapi\validate\community\CommunityCategoryValidate;
class CommunityCategoryController extends BaseAdminController
{
public function lists()
{
return $this->dataLists(new CommunityCategoryLists());
}
public function all()
{
return $this->data(CommunityCategoryLogic::all());
}
public function detail()
{
$params = (new CommunityCategoryValidate())->goCheck('detail');
return $this->data(CommunityCategoryLogic::detail($params));
}
public function add()
{
$params = (new CommunityCategoryValidate())->post()->goCheck('add');
if (CommunityCategoryLogic::add($params)) {
return $this->success('添加成功', [], 1, 1);
}
return $this->fail(CommunityCategoryLogic::getError());
}
public function edit()
{
$params = (new CommunityCategoryValidate())->post()->goCheck('edit');
if (CommunityCategoryLogic::edit($params)) {
return $this->success('编辑成功', [], 1, 1);
}
return $this->fail(CommunityCategoryLogic::getError());
}
public function delete()
{
$params = (new CommunityCategoryValidate())->post()->goCheck('delete');
if (CommunityCategoryLogic::delete($params)) {
return $this->success('删除成功', [], 1, 1);
}
return $this->fail(CommunityCategoryLogic::getError());
}
}
@@ -61,6 +61,26 @@ class CommunityPostController extends BaseAdminController
return $this->fail(CommunityPostLogic::getError());
}
public function setTopic()
{
$params = (new CommunityPostValidate())->post()->goCheck('topic');
$result = CommunityPostLogic::setTopic($params);
if (true === $result) {
return $this->success('设置成功', [], 1, 1);
}
return $this->fail(CommunityPostLogic::getError());
}
public function setCategory()
{
$params = (new CommunityPostValidate())->post()->goCheck('category');
$result = CommunityPostLogic::setCategory($params);
if (true === $result) {
return $this->success('设置成功', [], 1, 1);
}
return $this->fail(CommunityPostLogic::getError());
}
public function delete()
{
$params = (new CommunityPostValidate())->post()->goCheck('delete');
@@ -0,0 +1,33 @@
<?php
namespace app\adminapi\lists\community;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\lists\ListsSearchInterface;
use app\common\model\community\CommunityCategory;
class CommunityCategoryLists extends BaseAdminDataLists implements ListsSearchInterface
{
public function setSearch(): array
{
return [
'%like%' => ['name'],
'=' => ['status'],
];
}
public function lists(): array
{
return CommunityCategory::where($this->searchWhere)
->limit($this->limitOffset, $this->limitLength)
->order('sort', 'desc')
->order('id', 'desc')
->select()
->toArray();
}
public function count(): int
{
return CommunityCategory::where($this->searchWhere)->count();
}
}
@@ -4,6 +4,7 @@ namespace app\adminapi\lists\community;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\lists\ListsSearchInterface;
use app\common\model\community\CommunityCategory;
use app\common\model\community\CommunityPost;
use app\common\model\user\User;
@@ -12,7 +13,7 @@ class CommunityPostLists extends BaseAdminDataLists implements ListsSearchInterf
public function setSearch(): array
{
return [
'=' => ['status', 'post_type', 'is_top', 'is_hot', 'is_recommend', 'user_id'],
'=' => ['status', 'post_type', 'category_id', 'is_top', 'is_hot', 'is_recommend', 'is_topic', 'user_id'],
];
}
@@ -26,11 +27,16 @@ class CommunityPostLists extends BaseAdminDataLists implements ListsSearchInterf
$userIds = array_unique(array_column($lists, 'user_id'));
$users = User::whereIn('id', $userIds)->column('nickname,avatar', 'id');
$categoryIds = array_values(array_filter(array_unique(array_column($lists, 'category_id'))));
$categories = $categoryIds
? CommunityCategory::whereIn('id', $categoryIds)->column('name', 'id')
: [];
foreach ($lists as &$item) {
$user = $users[$item['user_id']] ?? [];
$item['nickname'] = $user['nickname'] ?? '-';
$item['avatar'] = $user['avatar'] ?? '';
$item['category_name'] = $categories[$item['category_id']] ?? '';
$item['create_time'] = is_numeric($item['create_time']) ? date('Y-m-d H:i:s', $item['create_time']) : $item['create_time'];
}
@@ -0,0 +1,72 @@
<?php
namespace app\adminapi\logic\community;
use app\common\logic\BaseLogic;
use app\common\model\community\CommunityCategory;
use app\common\model\community\CommunityPost;
class CommunityCategoryLogic extends BaseLogic
{
public static function add(array $params): bool
{
try {
CommunityCategory::create([
'name' => trim($params['name']),
'icon' => $params['icon'] ?? '',
'sort' => $params['sort'] ?? 0,
'status' => $params['status'] ?? 1,
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
public static function edit(array $params): bool
{
try {
CommunityCategory::update([
'id' => $params['id'],
'name' => trim($params['name']),
'icon' => $params['icon'] ?? '',
'sort' => $params['sort'] ?? 0,
'status' => $params['status'] ?? 1,
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
public static function delete(array $params): bool
{
if (CommunityPost::where('category_id', $params['id'])->count() > 0) {
self::setError('该分类下存在帖子,不能删除');
return false;
}
try {
CommunityCategory::destroy($params['id']);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
public static function detail(array $params): array
{
return CommunityCategory::findOrEmpty($params['id'])->toArray();
}
public static function all(): array
{
return CommunityCategory::order('sort', 'desc')
->order('id', 'desc')
->select()
->toArray();
}
}
@@ -3,9 +3,11 @@
namespace app\adminapi\logic\community;
use app\common\logic\BaseLogic;
use app\common\model\community\CommunityCategory;
use app\common\model\community\CommunityPost;
use app\common\service\ai\KbSyncService;
use app\common\model\user\User;
use app\common\service\ai\KbSyncService;
use think\facade\Db;
class CommunityPostLogic extends BaseLogic
{
@@ -21,6 +23,9 @@ class CommunityPostLogic extends BaseLogic
$user = User::field('nickname,avatar')->findOrEmpty($post['user_id'] ?? 0)->toArray();
$post['nickname'] = $user['nickname'] ?? '-';
$post['avatar'] = $user['avatar'] ?? '';
$post['category_name'] = (int) ($post['category_id'] ?? 0) > 0
? (string) CommunityCategory::where('id', $post['category_id'])->value('name')
: '';
return $post;
}
@@ -87,9 +92,65 @@ class CommunityPostLogic extends BaseLogic
}
}
public static function setTopic(array $params): bool
{
try {
CommunityPost::update([
'id' => $params['id'],
'is_topic' => $params['is_topic'],
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
public static function setCategory(array $params): bool
{
$post = CommunityPost::findOrEmpty($params['id']);
if ($post->isEmpty()) {
self::setError('帖子不存在');
return false;
}
$oldCategoryId = (int) $post->category_id;
$newCategoryId = (int) $params['category_id'];
if ($oldCategoryId === $newCategoryId) {
return true;
}
Db::startTrans();
try {
$post->category_id = $newCategoryId;
$post->save();
self::refreshCategoryCount($oldCategoryId);
self::refreshCategoryCount($newCategoryId);
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
self::setError($e->getMessage());
return false;
}
}
public static function delete(array $params)
{
$categoryId = (int) CommunityPost::where('id', $params['id'])->value('category_id');
CommunityPost::destroy($params['id']);
self::refreshCategoryCount($categoryId);
KbSyncService::enqueue('post', 'post', (int) $params['id'], 'delete', 20);
}
private static function refreshCategoryCount(int $categoryId): void
{
if ($categoryId <= 0) {
return;
}
CommunityCategory::where('id', $categoryId)->update([
'post_count' => CommunityPost::where('category_id', $categoryId)->count(),
'update_time' => time(),
]);
}
}
@@ -0,0 +1,64 @@
<?php
namespace app\adminapi\validate\community;
use app\common\model\community\CommunityCategory;
use app\common\validate\BaseValidate;
class CommunityCategoryValidate extends BaseValidate
{
protected $rule = [
'id' => 'require|checkCategory',
'name' => 'require|length:1,50|checkNameUnique',
'icon' => 'max:500',
'sort' => 'integer|egt:0',
'status' => 'in:0,1',
];
protected $message = [
'id.require' => '分类id不能为空',
'name.require' => '分类名称不能为空',
'name.length' => '分类名称长度须在1-50位字符',
'icon.max' => '分类图标地址不能超过500个字符',
'sort.integer' => '排序值必须为整数',
'sort.egt' => '排序值不能小于0',
'status.in' => '分类状态值错误',
];
public function sceneAdd()
{
return $this->remove('id', 'require|checkCategory');
}
public function sceneEdit()
{
}
public function sceneDetail()
{
return $this->only(['id']);
}
public function sceneDelete()
{
return $this->only(['id']);
}
public function checkCategory($value)
{
$category = CommunityCategory::findOrEmpty($value);
if ($category->isEmpty()) {
return '分类不存在';
}
return true;
}
public function checkNameUnique($value, $rule, $data)
{
$query = CommunityCategory::where('name', trim((string) $value));
if (!empty($data['id'])) {
$query->where('id', '<>', (int) $data['id']);
}
return $query->count() > 0 ? '分类名称已存在' : true;
}
}
@@ -3,6 +3,7 @@
namespace app\adminapi\validate\community;
use app\common\validate\BaseValidate;
use app\common\model\community\CommunityCategory;
use app\common\model\community\CommunityPost;
class CommunityPostValidate extends BaseValidate
@@ -13,6 +14,8 @@ class CommunityPostValidate extends BaseValidate
'is_top' => 'require|in:0,1',
'is_hot' => 'require|in:0,1',
'is_recommend' => 'require|in:0,1',
'is_topic' => 'require|in:0,1',
'category_id' => 'require|integer|egt:0|checkCategory',
];
protected $message = [
@@ -50,6 +53,16 @@ class CommunityPostValidate extends BaseValidate
return $this->only(['id', 'is_recommend']);
}
public function sceneTopic()
{
return $this->only(['id', 'is_topic']);
}
public function sceneCategory()
{
return $this->only(['id', 'category_id']);
}
public function checkPost($value)
{
$post = CommunityPost::findOrEmpty($value);
@@ -58,4 +71,16 @@ class CommunityPostValidate extends BaseValidate
}
return true;
}
public function checkCategory($value)
{
if ((int) $value === 0) {
return true;
}
$category = CommunityCategory::where('id', $value)->where('status', 1)->findOrEmpty();
if ($category->isEmpty()) {
return '帖子分类不存在或已禁用';
}
return true;
}
}
@@ -3,6 +3,7 @@
namespace app\api\lists\community;
use app\api\lists\BaseApiDataLists;
use app\common\model\community\CommunityCategory;
use app\common\model\community\CommunityPost;
use app\common\model\community\CommunityFollow;
use app\common\model\community\CommunityTag;
@@ -61,6 +62,11 @@ class CommunityPostLists extends BaseApiDataLists
$where[] = ['id', '=', 0];
}
}
foreach (['is_recommend', 'is_hot', 'is_topic', 'category_id'] as $field) {
if (isset($this->params[$field]) && $this->params[$field] !== '') {
$where[] = [$field, '=', (int) $this->params[$field]];
}
}
return $where;
}
@@ -88,7 +94,7 @@ class CommunityPostLists extends BaseApiDataLists
public function lists(): array
{
$list = $this->buildQuery()
->field('id,origin_id,user_id,content,images,ext,post_type,match_id,is_paid,price_points,paid_count,view_count,like_count,comment_count,share_count,is_top,is_hot,is_recommend,create_time')
->field('id,origin_id,user_id,content,images,ext,post_type,category_id,match_id,is_paid,price_points,paid_count,view_count,like_count,comment_count,share_count,is_top,is_hot,is_recommend,is_topic,create_time')
->orderRaw('is_top DESC, is_hot DESC, create_time DESC')
->limit($this->limitOffset, $this->limitLength)
->select()
@@ -107,6 +113,11 @@ class CommunityPostLists extends BaseApiDataLists
}
}
$categoryIds = array_values(array_filter(array_unique(array_column($list, 'category_id'))));
$categoryNames = $categoryIds
? CommunityCategory::whereIn('id', $categoryIds)->column('name', 'id')
: [];
// 批量查询帖子标签 → post_id => [tag_name, ...]
$tagMap = [];
if ($postIds) {
@@ -166,6 +177,7 @@ class CommunityPostLists extends BaseApiDataLists
$item['user'] = $userMap[$item['user_id']] ?? null;
$item['tags'] = $tagMap[$item['id']] ?? [];
$item['category_name'] = $categoryNames[$item['category_id']] ?? '';
$item['source_url'] = $ext['url'] ?? '';
$item['is_liked'] = false;
if ($this->userId) {
@@ -0,0 +1,21 @@
<?php
namespace app\common\model\community;
use app\common\model\BaseModel;
use app\common\service\FileService;
class CommunityCategory extends BaseModel
{
protected $name = 'community_post_category';
public function getIconAttr($value): string
{
return trim((string) $value) ? FileService::getFileUrl((string) $value) : '';
}
public function setIconAttr($value): string
{
return trim((string) $value) ? FileService::setFileUrl($value) : '';
}
}
@@ -26,4 +26,9 @@ class CommunityPost extends BaseModel
{
return $this->belongsToMany(CommunityTag::class, 'la_community_post_tag', 'tag_id', 'post_id');
}
public function category()
{
return $this->belongsTo(CommunityCategory::class, 'category_id');
}
}
+4 -8
View File
@@ -184,12 +184,6 @@ const shouldHidePost = (post: any) => {
const isAbortError = (error: any) => String(error?.errMsg || error?.message || error || '').includes('abort')
const rankHotPosts = (items: any[]) => [...items].sort((left, right) => {
const leftScore = (Number(left.is_hot) * 1000000) + Number(left.view_count || 0) + Number(left.like_count || 0) * 8 + Number(left.comment_count || 0) * 12
const rightScore = (Number(right.is_hot) * 1000000) + Number(right.view_count || 0) + Number(right.like_count || 0) * 8 + Number(right.comment_count || 0) * 12
return rightScore - leftScore
})
const fetchPosts = async (reset = false) => {
if (reset) {
page.value = 1
@@ -202,14 +196,16 @@ const fetchPosts = async (reset = false) => {
try {
const params: Record<string, any> = { page_no: page.value, page_size: pageSize }
if (activeFeed.value === 'recommend') params.is_recommend = 1
if (activeFeed.value === 'follow') params.follow = 1
if (activeFeed.value === 'hot') params.is_hot = 1
if (activeFeed.value === 'topic') params.is_topic = 1
if (activeTagId.value > 0) params.tag_id = activeTagId.value
const response = await getCommunityPosts(params)
const rawList = response?.lists || []
if (requestId !== fetchRequestId.value) return
const list = rawList.filter((item: any) => !shouldHidePost(item))
const normalizedList = activeFeed.value === 'hot' ? rankHotPosts(list) : list
postList.value = reset || page.value === 1 ? normalizedList : [...postList.value, ...normalizedList]
postList.value = reset || page.value === 1 ? list : [...postList.value, ...list]
if (rawList.length < pageSize) finished.value = true
page.value++
} catch (error) {