小说小程序前端代码仓库(小程序)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

591 lines
13 KiB

<template>
<!-- 书架页面 -->
<view class="page">
<!-- 头部标签切换 -->
<view class="header">
<view class="header-content">
<view class="tab-container">
<view class="tab" :class="{'active': activeTab === 'read'}" @click="switchTab('read')">阅读</view>
<view class="tab" :class="{'active': activeTab === 'work'}" @click="switchTab('work')">作品</view>
</view>
</view>
</view>
<!-- 书籍列表 - 阅读模式 -->
<view class="novel-grid" v-if="activeTab === 'read' && !isEditMode">
<view class="novel-row" v-for="(row, rowIndex) in novelRows" :key="rowIndex">
<view class="novel-item"
v-for="(novel, index) in list"
:key="novel.id"
@click="toNovelDetail(novel.id)"
@longpress="enterEditMode">
<novel-item
:book="novel"
horizontal="true"
:style="{ width: '220rpx' }">
</novel-item>
<view class="novel-tag" v-if="novel.tag">{{novel.tag}}</view>
<view class="novel-original" v-if="novel.isOriginal">
<text>原创</text>
</view>
</view>
</view>
<!-- 空状态提示 -->
<view class="empty-works" v-if="list.length === 0">
<text class="empty-text">你书架还没有书籍呢</text>
<text class="empty-tips">去首页看看吧</text>
</view>
</view>
<!-- 作品列表 - 作品模式 -->
<view class="works-container" v-if="activeTab === 'work' && !isEditMode">
<!-- 顶部创建区域 -->
<view class="works-header">
<new-work-item @click="createNewWork" @settings="toReaderSettings" />
</view>
<!-- 作品列表 -->
<view class="works-content">
<work-item
v-for="work in list"
:key="work.id"
:work="work"
@click="toWorkDetail(work.id)"
@longpress="enterEditMode"
/>
<!-- 空状态提示 -->
<view class="empty-works" v-if="list.length === 0">
<text class="empty-text">你还没有创建作品</text>
<text class="empty-tips">点击左上角"+"创建你的第一部作品吧</text>
</view>
</view>
</view>
<!-- 编辑模式 - 阅读 -->
<view class="novel-grid edit-mode" v-if="activeTab === 'read' && isEditMode">
<view class="novel-row" v-for="(row, rowIndex) in novelRows" :key="rowIndex">
<view class="novel-item"
v-for="(novel, index) in list"
:key="novel.id"
@click="toggleSelect(novel, 'novel')">
<view class="item-checkbox" v-if="selectedItems.includes(novel.id)">
<view class="checkbox-inner">
<uv-icon name="checkmark" size="28" color="#ffffff"></uv-icon>
</view>
</view>
<view class="item-checkbox" v-else>
<view class="checkbox-inner-no">
</view>
</view>
<novel-item
:book="novel"
horizontal="true"
:style="{ width: '220rpx', opacity: selectedItems.includes(novel.id) ? '0.8' : '1' }">
</novel-item>
<view class="novel-tag" v-if="novel.tag">{{novel.tag}}</view>
<view class="novel-original" v-if="novel.isOriginal">
<text>原创</text>
</view>
</view>
</view>
</view>
<!-- 编辑模式 - 作品 -->
<view class="works-container edit-mode" v-if="activeTab === 'work' && isEditMode">
<view class="works-content">
<view
class="work-item-wrapper"
v-for="work in list"
:key="work.id"
@click="toggleSelect(work, 'work')"
>
<work-item
:work="work"
:style="{ opacity: selectedItems.includes(work.id) ? '0.8' : '1' }"
/>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-action-bar" v-if="isEditMode">
<view class="action-item" @click="exitEditMode">
<view class="action-icon">
<uv-icon name="reload" size="40" color="#666"></uv-icon>
</view>
<text>取消</text>
</view>
<view class="action-item" @click="selectAll">
<view class="action-icon">
<uv-icon name="grid-fill" size="40" color="#ff9900"></uv-icon>
</view>
<text>全选</text>
</view>
<view class="action-item" @click="removeSelected">
<view class="action-icon">
<uv-icon name="trash-fill" size="40" color="#f56c6c"></uv-icon>
</view>
<text>{{activeTab === 'read' ? '移出书架' : '删除'}}</text>
</view>
</view>
<tabber select="bookshelf" v-if="!isEditMode"/>
</view>
</template>
<script>
import tabber from '@/components/base/tabbar.vue'
import novelItem from '@/components/novel/novelItem.vue'
import workItem from '@/components/novel/workItem.vue'
import newWorkItem from '@/components/novel/newWorkItem.vue'
import mixinsList from '@/mixins/list.js'
export default {
mixins: [mixinsList],
components : {
tabber,
novelItem,
workItem,
newWorkItem
},
computed : {
// 将小说列表分成每行3个的二维数组
novelRows() {
const rows = [];
const itemsPerRow = 3;
for (let i = 0; i < this.list.length; i += itemsPerRow) {
rows.push(this.list.slice(i, i + itemsPerRow));
}
return rows;
}
},
data() {
return {
statusBarHeight: 0, // 状态栏高度
navBarHeight: 0, // 导航栏高度
activeTab: 'read',
isEditMode: false,
selectedItems: [], // 统一选中项
mixinsListApi : '',
apiMap : {
read : 'getReadBookPage',
work : 'getMyBookPage',
// work : 'getMyBookPage',
},
}
},
onLoad() {
// 检查是否需要切换到作品标签
const activeTab = uni.getStorageSync('activeBookshelfTab')
// if (activeTab === 'work') {
// this.activeTab = 'work'
// this.mixinsListApi = this.apiMap[tab]
// uni.removeStorageSync('activeBookshelfTab')
// }
// 监听切换到作品标签的事件
// uni.$on('switchToWork', () => {
// this.activeTab = 'work'
// })
},
onShow() {
// this.isEditMode = false;
// this.selectedItems = [];
if(this.isLogin){
this.mixinsListApi = this.apiMap[tab]
}
},
onUnload() {
// 移除事件监听
uni.$off('switchToWork')
},
methods: {
// 切换标签
switchTab(tab) {
this.activeTab = tab;
if(this.isLogin){
this.mixinsListApi = this.apiMap[tab]
}
this.list = []
this.getData()
// 退出编辑模式
this.exitEditMode();
},
// 跳转到小说阅读页
toNovelDetail(id) {
uni.navigateTo({
url: '/pages_order/novel/readnovels?id=' + id
})
},
// 跳转到作品详情页
toWorkDetail(id) {
console.log(id);
uni.navigateTo({
url: '/pages/work/detail?id=' + id
})
},
// 创建新作品
createNewWork() {
uni.navigateTo({
url: '/pages_order/novel/createNovel'
})
},
// 跳转到读者成就设置
toReaderSettings() {
uni.navigateTo({
url: '/pages_order/novel/ReaderAchievement'
})
},
// 进入编辑模式
enterEditMode() {
this.isEditMode = true;
this.selectedItems = [];
},
// 退出编辑模式
exitEditMode() {
this.isEditMode = false;
this.selectedItems = [];
},
// 切换选择状态
toggleSelect(item, type) {
const index = this.selectedItems.indexOf(item.id);
if (index === -1) {
this.selectedItems.push(item.id);
} else {
this.selectedItems.splice(index, 1);
}
},
// 全选
selectAll() {
if (this.activeTab === 'read') {
// 已经全选,则取消全选
if (this.selectedItems.length === this.novels.length) {
this.selectedItems = [];
} else {
// 全选所有小说
this.selectedItems = this.novels.map(novel => novel.id);
}
} else {
// 已经全选,则取消全选
if (this.selectedItems.length === this.list.length) {
this.selectedItems = [];
} else {
// 全选所有作品
this.selectedItems = this.list.map(work => work.id);
}
}
},
// 移除选中的项目
removeSelected() {
if (this.selectedItems.length === 0) {
uni.showToast({
title: '请先选择项目',
icon: 'none'
});
return;
}
const title = this.activeTab === 'read' ? '移出书架' : '删除作品';
const content = this.activeTab === 'read'
? `确定要将选中的${this.selectedItems.length}本小说移出书架吗?`
: `确定要删除选中的${this.selectedItems.length}部作品吗?`;
uni.showModal({
title: '提示',
content: content,
success: async (res) => {
if (res.confirm) {
if (this.activeTab === 'read') {
// 移除选中的小说
// this.novels = this.novels.filter(novel => !this.selectedItems.includes(novel.id));
await this.$fetch('batchRemoveReadBook', {
bookIds : this.selectedItems.join(',')
})
uni.showToast({
title: '移除成功',
icon: 'success'
});
this.getData()
} else {
// 删除选中的作品
this.list = this.list.filter(work => !this.selectedItems.includes(work.id));
// 保存更新后的作品列表
uni.setStorageSync('list', this.list)
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
this.selectedItems = [];
// 如果没有数据了,退出编辑模式
if ((this.activeTab === 'read' && this.list.length === 0) ||
(this.activeTab === 'work' && this.list.length === 0)) {
this.exitEditMode();
}
}
}
});
},
}
}
</script>
<style scoped lang="scss">
.page {
background-color: #ffffff;
min-height: 100vh;
position: relative;
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.header {
display: flex;
flex-direction: column;
justify-content: flex-end;
position: sticky;
top: 0;
z-index: 100;
background-color: #ffffff;
box-sizing: border-box;
width: 100%;
border-bottom: 1rpx solid #f5f5f5;
padding-top: calc(var(--status-bar-height) + 20rpx);
.header-content {
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx 30rpx;
padding-bottom: 24rpx;
width: 100%;
}
.tab-container {
display: flex;
align-items: center;
font-size: 34rpx;
.tab {
margin-right: 40rpx;
color: #999;
position: relative;
padding: 10rpx 0;
&.active {
color: #000;
font-weight: bold;
font-size: 36rpx;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 6rpx;
background-color: #000;
border-radius: 3rpx;
}
}
}
}
.header-right {
display: flex;
align-items: center;
.header-icon {
margin-left: 30rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 28rpx;
color: #333;
}
}
}
}
.empty-works {
width: 100%;
padding: 100rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.empty-text {
font-size: 32rpx;
color: #666;
margin-bottom: 20rpx;
}
.empty-tips {
font-size: 28rpx;
color: #999;
}
}
.novel-grid {
padding: 20rpx;
padding-top: 30rpx;
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
.novel-row {
display: flex;
margin-bottom: 40rpx;
.novel-item {
width: 31%;
position: relative;
.novel-tag {
position: absolute;
top: 10rpx;
right: 10rpx;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 20rpx;
padding: 4rpx 10rpx;
border-radius: 6rpx;
z-index: 1;
}
.novel-original {
position: absolute;
top: 10rpx;
right: 10rpx;
background-color: #ff9900;
color: #fff;
font-size: 20rpx;
padding: 4rpx 10rpx;
border-radius: 6rpx;
z-index: 1;
}
}
}
}
/* 作品列表相关样式 */
.works-container {
padding: 30rpx;
.works-header {
margin-bottom: 30rpx;
}
.works-content {
display: flex;
flex-direction: column;
.work-item-wrapper {
position: relative;
margin-bottom: 20rpx;
}
}
}
.bottom-action-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: calc(120rpx + env(safe-area-inset-bottom));
background-color: #fff;
border-top: 1rpx solid #eee;
display: flex;
justify-content: space-around;
align-items: center;
z-index: 99;
padding-bottom: env(safe-area-inset-bottom);
.action-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.action-icon {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
text {
font-size: 24rpx;
color: #666;
margin-top: 8rpx;
}
}
}
// 编辑模式公共样式
.edit-mode {
.item-checkbox {
position: absolute;
top: 10rpx;
left: 10rpx;
z-index: 10;
background-color: rgba(255,255,255,0.8);
border-radius: 50%;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.checkbox-inner {
width: 40rpx;
height: 40rpx;
background-color: #1989fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.checkbox-inner-no {
width: 40rpx;
height: 40rpx;
background-color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>