Files
sbnews/uniapp/src/components/channel-manage/channel-manage.vue
T
2026-06-11 12:15:29 +08:00

251 lines
6.5 KiB
Vue

<template>
<u-popup v-model="visible" mode="top" length="100%" :mask="true" :mask-close-able="true" @close="handleClose">
<view class="channel-manage" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="channel-manage__header">
<view class="channel-manage__close" @tap="handleClose">
<u-icon name="close" size="36" />
</view>
<view class="channel-manage__header-right">
<view class="channel-manage__search" @tap="goSearch">
<u-icon name="search" size="36" />
</view>
</view>
</view>
<scroll-view scroll-y class="channel-manage__scroll">
<!-- 我的频道 -->
<view class="channel-section">
<view class="channel-section__header">
<text class="channel-section__title">我的频道</text>
<text class="channel-section__edit" @tap="toggleEdit">{{ isEditing ? '完成' : '编辑' }}</text>
</view>
<view class="channel-grid">
<view v-for="(item, index) in myChannels" :key="item.id" class="channel-tag" :class="{
'channel-tag--active': index === activeIndex,
'channel-tag--fixed': item.fixed
}" @tap="handleMyChannelClick(index)">
<text>{{ item.name }}</text>
<text v-if="isEditing && !item.fixed" class="channel-tag__remove">-</text>
</view>
</view>
</view>
<!-- 推荐频道 -->
<view v-for="group in recommendGroups" :key="group.title" class="channel-section">
<view class="channel-section__header">
<text class="channel-section__title">{{ group.title }}</text>
</view>
<view class="channel-grid">
<view v-for="item in group.channels" :key="item.id" class="channel-tag channel-tag--add"
@tap="isEditing && handleAddChannel(item)">
<text>{{ item.name }}</text>
<text v-if="isEditing" class="channel-tag__add">+</text>
</view>
</view>
</view>
</scroll-view>
</view>
</u-popup>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { getArticleCate } from '@/api/news'
const props = withDefaults(
defineProps<{
show: boolean
channels: any[]
activeIndex: number
}>(),
{
show: false,
channels: () => [],
activeIndex: 0
}
)
const emit = defineEmits<{
(e: 'update:show', value: boolean): void
(e: 'change', channels: any[]): void
(e: 'select', index: number): void
}>()
const visible = ref(false)
const isEditing = ref(false)
const myChannels = ref<any[]>([])
const allCateList = ref<any[]>([])
const statusBarHeight = ref(0)
uni.getSystemInfo({
success: (res) => {
// #ifdef APP-PLUS || MP
statusBarHeight.value = res.statusBarHeight || 44
// #endif
// #ifdef H5
statusBarHeight.value = 0
// #endif
}
})
const recommendGroups = ref<{ title: string; channels: any[] }[]>([])
const loadAllCate = async () => {
try {
const data = await getArticleCate()
allCateList.value = data || []
} catch (e) {
console.log('获取全部分类失败=>', e)
}
}
watch(() => props.show, (val) => {
visible.value = val
if (val) {
myChannels.value = [...props.channels]
isEditing.value = false
loadAllCate().then(() => filterRecommend())
}
})
watch(() => visible.value, (val) => {
if (!val) emit('update:show', false)
})
const filterRecommend = () => {
const myIds = new Set(myChannels.value.map(c => String(c.id)))
const remaining = allCateList.value.filter(c => !myIds.has(String(c.id)))
recommendGroups.value = remaining.length
? [{ title: '推荐频道', channels: remaining }]
: []
}
const toggleEdit = () => {
isEditing.value = !isEditing.value
}
const handleClose = () => {
emit('change', [...myChannels.value])
visible.value = false
}
const handleMyChannelClick = (index: number) => {
if (isEditing.value) {
const item = myChannels.value[index]
if (item.fixed) return
myChannels.value.splice(index, 1)
filterRecommend()
} else {
emit('select', index)
visible.value = false
}
}
const handleAddChannel = (item: any) => {
myChannels.value.push({ ...item, fixed: false })
filterRecommend()
}
const goSearch = () => {
visible.value = false
uni.navigateTo({ url: '/pages/search/search' })
}
</script>
<style lang="scss" scoped>
.channel-manage {
background: #fff;
min-height: 100vh;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
}
&__close,
&__search {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
}
&__scroll {
height: calc(100vh - 120rpx);
}
}
.channel-section {
padding: 20rpx 30rpx;
&__header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
&__title {
font-size: 34rpx;
font-weight: bold;
color: #222;
}
&__edit {
font-size: 26rpx;
color: #185DFF;
margin-left: 16rpx;
}
}
.channel-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.channel-tag {
position: relative;
min-width: 140rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: #F4F5F7;
border-radius: 8rpx;
padding: 0 28rpx;
font-size: 28rpx;
color: #808080;
&--active {
color: #185DFF;
font-weight: bold;
}
&--fixed {
color: #185DFF;
}
&--add {
color: #185dff;
background: #E5EDFF;
}
&__remove {
margin-left: 8rpx;
font-size: 32rpx;
font-weight: bold;
color: #999;
}
&__add {
margin-left: 8rpx;
font-size: 32rpx;
font-weight: bold;
color: #185DFF;
}
}
</style>