<template>
|
|
<!-- 书架页面 -->
|
|
<view class="page">
|
|
<!-- 头部标签切换 -->
|
|
<view class="header" :style="{ paddingTop: `${statusBarHeight}px` }">
|
|
<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 row"
|
|
: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>
|
|
|
|
<!-- 作品列表 - 作品模式 -->
|
|
<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 worksList"
|
|
:key="work.id"
|
|
:work="work"
|
|
@click="toWorkDetail(work.id)"
|
|
@longpress="enterEditMode"
|
|
/>
|
|
|
|
<!-- 空状态提示 -->
|
|
<view class="empty-works" v-if="worksList.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 row"
|
|
: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 worksList"
|
|
: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 { mapGetters } from 'vuex'
|
|
export default {
|
|
components : {
|
|
tabber,
|
|
novelItem,
|
|
workItem,
|
|
newWorkItem
|
|
},
|
|
computed : {
|
|
...mapGetters(['userShop']),
|
|
// 将小说列表分成每行3个的二维数组
|
|
novelRows() {
|
|
const rows = [];
|
|
const itemsPerRow = 3;
|
|
|
|
for (let i = 0; i < this.novels.length; i += itemsPerRow) {
|
|
rows.push(this.novels.slice(i, i + itemsPerRow));
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
statusBarHeight: 0, // 状态栏高度
|
|
navBarHeight: 0, // 导航栏高度
|
|
activeTab: 'read',
|
|
isEditMode: false,
|
|
selectedItems: [], // 统一选中项
|
|
novels: [
|
|
{
|
|
id: '1',
|
|
title: '我是半妖',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '炎兰',
|
|
desc: '都市玄幻小说,主角获得半妖化能力,通过吸收妖气不断变强...',
|
|
tags: ['玄幻', '都市', '热血'],
|
|
status: '连载中'
|
|
},
|
|
{
|
|
id: '2',
|
|
title: '兽王进化:从被小萝莉召唤开始',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '九灵',
|
|
desc: '一场意外让主角获得兽王血脉,开始了进化之路...',
|
|
tags: ['奇幻', '冒险'],
|
|
isOriginal: true,
|
|
status: '连载中'
|
|
},
|
|
{
|
|
id: '3',
|
|
title: '魔法少女纯爷们',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '烟火',
|
|
desc: '一个普通男孩意外获得魔法少女的力量,开始了奇妙冒险...',
|
|
tags: ['搞笑', '奇幻'],
|
|
status: '已完结'
|
|
},
|
|
{
|
|
id: '4',
|
|
title: '我是一条小青龙',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '东升',
|
|
desc: '重生为一条小青龙,主角在修仙世界中成长的故事...',
|
|
tags: ['仙侠', '修真'],
|
|
tag: '独家',
|
|
status: '连载中'
|
|
},
|
|
{
|
|
id: '5',
|
|
title: '女帝:别闹,朕怀孕了!',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '君临',
|
|
desc: '一代女帝意外穿越成了皇帝,却发现自己怀孕了...',
|
|
tags: ['宫廷', '穿越'],
|
|
isOriginal: true,
|
|
status: '连载中'
|
|
},
|
|
{
|
|
id: '6',
|
|
title: '中国式应酬——应酬是门技术活',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '商业顾问',
|
|
desc: '一本教你如何在商业场合应对各种应酬的实用指南...',
|
|
tags: ['商业', '实用'],
|
|
status: '已完结'
|
|
},
|
|
{
|
|
id: '7',
|
|
title: '苏世民:我的经验与教训',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '苏世民',
|
|
desc: '黑石集团创始人苏世民的商业回忆录...',
|
|
tags: ['传记', '商业'],
|
|
status: '已完结'
|
|
},
|
|
{
|
|
id: '8',
|
|
title: '认知觉醒:开启自我改变的原动力',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: '周岭',
|
|
desc: '帮助你打破思维局限,重塑认知结构的心理学著作...',
|
|
tags: ['心理', '自助'],
|
|
status: '已完结'
|
|
},
|
|
{
|
|
id: '9',
|
|
title: '纳瓦尔宝典',
|
|
cover: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain',
|
|
author: 'Naval',
|
|
desc: '硅谷天使投资人纳瓦尔·拉维坎特的人生智慧...',
|
|
tags: ['哲学', '投资'],
|
|
status: '已完结'
|
|
}
|
|
],
|
|
// 作品列表数据
|
|
worksList: [] // 清空初始数据,改为动态加载
|
|
}
|
|
},
|
|
onLoad() {
|
|
// 获取系统信息
|
|
const systemInfo = uni.getSystemInfoSync();
|
|
this.statusBarHeight = systemInfo.statusBarHeight;
|
|
|
|
// 加载保存的作品列表
|
|
this.loadWorksList()
|
|
|
|
// 检查是否需要切换到作品标签
|
|
const activeTab = uni.getStorageSync('activeBookshelfTab')
|
|
if (activeTab === 'work') {
|
|
this.activeTab = 'work'
|
|
uni.removeStorageSync('activeBookshelfTab')
|
|
}
|
|
|
|
// 监听切换到作品标签的事件
|
|
uni.$on('switchToWork', () => {
|
|
this.activeTab = 'work'
|
|
})
|
|
|
|
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
|
const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
|
|
const navBarHeight = (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 + menuButtonInfo.height + systemInfo.statusBarHeight;
|
|
this.navBarHeight = navBarHeight;
|
|
// #endif
|
|
},
|
|
onShow() {
|
|
// 重新加载作品列表
|
|
this.loadWorksList()
|
|
|
|
// 检查是否需要弹窗
|
|
const pages = getCurrentPages();
|
|
const current = pages[pages.length - 1];
|
|
if (current.options && current.options.fromPublish === '1') {
|
|
this.activeTab = 'work';
|
|
uni.showToast({
|
|
title: '发布成功',
|
|
icon: 'success'
|
|
});
|
|
// 移除参数,防止返回时重复弹窗
|
|
delete current.options.fromPublish;
|
|
}
|
|
this.isEditMode = false;
|
|
this.selectedItems = [];
|
|
},
|
|
onUnload() {
|
|
// 移除事件监听
|
|
uni.$off('switchToWork')
|
|
},
|
|
methods: {
|
|
// 切换标签
|
|
switchTab(tab) {
|
|
this.activeTab = tab;
|
|
|
|
// 退出编辑模式
|
|
this.exitEditMode();
|
|
},
|
|
|
|
// 跳转到小说详情页
|
|
toNovelDetail(id) {
|
|
uni.navigateTo({
|
|
url: '/pages_order/book/bookDetail?id=' + id
|
|
})
|
|
},
|
|
|
|
// 跳转到作品详情页
|
|
toWorkDetail(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.worksList.length) {
|
|
this.selectedItems = [];
|
|
} else {
|
|
// 全选所有作品
|
|
this.selectedItems = this.worksList.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: (res) => {
|
|
if (res.confirm) {
|
|
if (this.activeTab === 'read') {
|
|
// 移除选中的小说
|
|
this.novels = this.novels.filter(novel => !this.selectedItems.includes(novel.id));
|
|
uni.showToast({
|
|
title: '移除成功',
|
|
icon: 'success'
|
|
});
|
|
} else {
|
|
// 删除选中的作品
|
|
this.worksList = this.worksList.filter(work => !this.selectedItems.includes(work.id));
|
|
// 保存更新后的作品列表
|
|
uni.setStorageSync('worksList', this.worksList)
|
|
uni.showToast({
|
|
title: '删除成功',
|
|
icon: 'success'
|
|
});
|
|
}
|
|
|
|
this.selectedItems = [];
|
|
|
|
// 如果没有数据了,退出编辑模式
|
|
if ((this.activeTab === 'read' && this.novels.length === 0) ||
|
|
(this.activeTab === 'work' && this.worksList.length === 0)) {
|
|
this.exitEditMode();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
// 加载作品列表
|
|
loadWorksList() {
|
|
const savedWorks = uni.getStorageSync('worksList') || []
|
|
this.worksList = savedWorks
|
|
}
|
|
}
|
|
}
|
|
</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: constant(safe-area-inset-top); /* iOS 11.0 */
|
|
padding-top: env(safe-area-inset-top); /* iOS 11.2+ */
|
|
|
|
.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.novel-grid {
|
|
padding: 20rpx;
|
|
padding-top: 30rpx;
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
box-sizing: border-box;
|
|
|
|
.novel-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.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>
|