小说小程序前端代码仓库(小程序)
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.
 
 
 

681 lines
17 KiB

<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>