13 changed files with 9873 additions and 2 deletions
@ -0,0 +1,71 @@ |
|||||
|
<template> |
||||
|
<view class="battery-container"> |
||||
|
<view class="battery-body"> |
||||
|
<view class="battery" :style="{width: `${level}%`}"></view> |
||||
|
<text class="iconfont charging" v-if="charging"></text> |
||||
|
</view> |
||||
|
<view class="battery-head"></view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props:{ |
||||
|
level: { |
||||
|
type: Number, |
||||
|
default: 0 |
||||
|
}, |
||||
|
charging: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
}, |
||||
|
methods: { |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.battery-container{ |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
width: 25px; |
||||
|
height: 10px; |
||||
|
.battery-body{ |
||||
|
position: relative; |
||||
|
padding: 1px; |
||||
|
width: 22px; |
||||
|
height: 100%; |
||||
|
border-radius: 1px; |
||||
|
border: $minor-text-color solid 1px; |
||||
|
.battery{ |
||||
|
height: 100%; |
||||
|
background-color: $minor-text-color; |
||||
|
} |
||||
|
.charging{ |
||||
|
position: absolute; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
height: 12px; |
||||
|
line-height: 12px; |
||||
|
font-size: 15px; |
||||
|
color: #333; |
||||
|
} |
||||
|
} |
||||
|
.battery-head{ |
||||
|
width: 2px; |
||||
|
height: 6px; |
||||
|
background-color: $minor-text-color; |
||||
|
} |
||||
|
} |
||||
|
</style> |
File diff suppressed because one or more lines are too long
@ -0,0 +1,455 @@ |
|||||
|
const chaptersList = [ |
||||
|
{ |
||||
|
index: 0, |
||||
|
chapterId: '1', |
||||
|
name: '第1章 考研失败是有原因的' |
||||
|
}, |
||||
|
{ |
||||
|
index: 1, |
||||
|
chapterId: '2', |
||||
|
name: '第2章 收服上古神兽' |
||||
|
}, |
||||
|
{ |
||||
|
index: 2, |
||||
|
chapterId: '3', |
||||
|
name: '第3章 宗门选拔' |
||||
|
}, |
||||
|
{ |
||||
|
index: 3, |
||||
|
chapterId: '4', |
||||
|
name: '第4章 成功登百阶' |
||||
|
}, |
||||
|
{ |
||||
|
index: 4, |
||||
|
chapterId: '5', |
||||
|
name: '第5章 你们求不得的我应有尽有' |
||||
|
}, |
||||
|
{ |
||||
|
index: 5, |
||||
|
chapterId: '6', |
||||
|
name: '第6章 月华果的主人居然是他?' |
||||
|
}, |
||||
|
{ |
||||
|
index: 6, |
||||
|
chapterId: '7', |
||||
|
name: '第7章 大师姐的黑暗料理' |
||||
|
}, |
||||
|
{ |
||||
|
index: 7, |
||||
|
chapterId: '8', |
||||
|
name: '第8章 有钱的无为宗' |
||||
|
}, |
||||
|
{ |
||||
|
index: 8, |
||||
|
chapterId: '9', |
||||
|
name: '第9章 天下武修第一人' |
||||
|
}, |
||||
|
{ |
||||
|
index: 9, |
||||
|
chapterId: '10', |
||||
|
name: '第10章 虽然没灵力,但我有外挂' |
||||
|
}, |
||||
|
{ |
||||
|
index: 10, |
||||
|
chapterId: '11', |
||||
|
name: '第11章 前尘往事难道破' |
||||
|
}, |
||||
|
{ |
||||
|
index: 11, |
||||
|
chapterId: '12', |
||||
|
name: '第12章 鬼艮的秘密' |
||||
|
}, |
||||
|
{ |
||||
|
index: 12, |
||||
|
chapterId: '13', |
||||
|
name: '第13章 屎中添花' |
||||
|
}, |
||||
|
{ |
||||
|
index: 13, |
||||
|
chapterId: '14', |
||||
|
name: '第14章 强盗一般的人物' |
||||
|
}, |
||||
|
{ |
||||
|
index: 14, |
||||
|
chapterId: '15', |
||||
|
name: '第15章 恶人先告状' |
||||
|
}, |
||||
|
{ |
||||
|
index: 15, |
||||
|
chapterId: '16', |
||||
|
name: '第16章 被关惩戒堂' |
||||
|
}, |
||||
|
{ |
||||
|
index: 16, |
||||
|
chapterId: '17', |
||||
|
name: '第17章 惩戒堂里的东西' |
||||
|
}, |
||||
|
{ |
||||
|
index: 17, |
||||
|
chapterId: '18', |
||||
|
name: '第18章 一举拿下魄' |
||||
|
}, |
||||
|
{ |
||||
|
index: 18, |
||||
|
chapterId: '19', |
||||
|
name: '第19章 掌门掐架' |
||||
|
}, |
||||
|
{ |
||||
|
index: 19, |
||||
|
chapterId: '20', |
||||
|
name: '第20章 无为道法' |
||||
|
}, |
||||
|
{ |
||||
|
index: 20, |
||||
|
chapterId: '21', |
||||
|
name: '第21章 他的精神力是最上乘' |
||||
|
}, |
||||
|
{ |
||||
|
index: 21, |
||||
|
chapterId: '22', |
||||
|
name: '第22章 时空回溯的能力' |
||||
|
}, |
||||
|
{ |
||||
|
index: 22, |
||||
|
chapterId: '23', |
||||
|
name: '第23章 碧波龙鳞扇' |
||||
|
}, |
||||
|
{ |
||||
|
index: 23, |
||||
|
chapterId: '24', |
||||
|
name: '第24章 萧玉林当他的贴身保镖' |
||||
|
}, |
||||
|
{ |
||||
|
index: 24, |
||||
|
chapterId: '25', |
||||
|
name: '第25章 大师姐的饭菜' |
||||
|
}, |
||||
|
{ |
||||
|
index: 25, |
||||
|
chapterId: '26', |
||||
|
name: '第26章 张景轩是登徒子' |
||||
|
}, |
||||
|
{ |
||||
|
index: 26, |
||||
|
chapterId: '27', |
||||
|
name: '第27章 言出法随,风动' |
||||
|
}, |
||||
|
{ |
||||
|
index: 27, |
||||
|
chapterId: '28', |
||||
|
name: '第28章 师父给的压力' |
||||
|
}, |
||||
|
{ |
||||
|
index: 28, |
||||
|
chapterId: '29', |
||||
|
name: '第29章 新徒比试大会(一)' |
||||
|
}, |
||||
|
{ |
||||
|
index: 29, |
||||
|
chapterId: '30', |
||||
|
name: '第30章 新徒比试大会(二)' |
||||
|
}, |
||||
|
{ |
||||
|
index: 30, |
||||
|
chapterId: '31', |
||||
|
name: '第31章 新徒比试大会(三)' |
||||
|
}, |
||||
|
{ |
||||
|
index: 31, |
||||
|
chapterId: '32', |
||||
|
name: '第32章 走后门' |
||||
|
}, |
||||
|
{ |
||||
|
index: 32, |
||||
|
chapterId: '33', |
||||
|
name: '第33章 把魄当狗遛' |
||||
|
}, |
||||
|
{ |
||||
|
index: 33, |
||||
|
chapterId: '34', |
||||
|
name: '第34章 万事不要逞强' |
||||
|
}, |
||||
|
{ |
||||
|
index: 34, |
||||
|
chapterId: '35', |
||||
|
name: '第35章 进入侯林' |
||||
|
}, |
||||
|
{ |
||||
|
index: 35, |
||||
|
chapterId: '36', |
||||
|
name: '第36章 侯林密林,第二境无觅处' |
||||
|
}, |
||||
|
{ |
||||
|
index: 36, |
||||
|
chapterId: '37', |
||||
|
name: '第37章 灵草收集完' |
||||
|
}, |
||||
|
{ |
||||
|
index: 37, |
||||
|
chapterId: '38', |
||||
|
name: '第38章 赛博武器' |
||||
|
}, |
||||
|
{ |
||||
|
index: 38, |
||||
|
chapterId: '39', |
||||
|
name: '第39章 起阵画符得心应手' |
||||
|
}, |
||||
|
{ |
||||
|
index: 39, |
||||
|
chapterId: '40', |
||||
|
name: '第40章 窥天镜异动' |
||||
|
}, |
||||
|
{ |
||||
|
index: 40, |
||||
|
chapterId: '41', |
||||
|
name: '第41章 姜思张锁仙对战' |
||||
|
}, |
||||
|
{ |
||||
|
index: 41, |
||||
|
chapterId: '42', |
||||
|
name: '第42章 有人跟踪他' |
||||
|
}, |
||||
|
{ |
||||
|
index: 42, |
||||
|
chapterId: '43', |
||||
|
name: '第43章 灵狼求救' |
||||
|
}, |
||||
|
{ |
||||
|
index: 43, |
||||
|
chapterId: '44', |
||||
|
name: '第44章 无奈当奶妈' |
||||
|
}, |
||||
|
{ |
||||
|
index: 44, |
||||
|
chapterId: '45', |
||||
|
name: '第45章 误入留园' |
||||
|
}, |
||||
|
{ |
||||
|
index: 45, |
||||
|
chapterId: '46', |
||||
|
name: '第46章 青芽决' |
||||
|
}, |
||||
|
{ |
||||
|
index: 46, |
||||
|
chapterId: '47', |
||||
|
name: '第47章 仙缘初现' |
||||
|
}, |
||||
|
{ |
||||
|
index: 47, |
||||
|
chapterId: '48', |
||||
|
name: '第48章 青木长生' |
||||
|
}, |
||||
|
{ |
||||
|
index: 48, |
||||
|
chapterId: '49', |
||||
|
name: '第49章 封印解密' |
||||
|
}, |
||||
|
{ |
||||
|
index: 49, |
||||
|
chapterId: '50', |
||||
|
name: '第50章 临时小队' |
||||
|
}, |
||||
|
{ |
||||
|
index: 50, |
||||
|
chapterId: '51', |
||||
|
name: '第51章 梦中警示' |
||||
|
}, |
||||
|
{ |
||||
|
index: 51, |
||||
|
chapterId: '52', |
||||
|
name: '第52章 封印松动' |
||||
|
}, |
||||
|
{ |
||||
|
index: 52, |
||||
|
chapterId: '53', |
||||
|
name: '第53章 血脉真相' |
||||
|
}, |
||||
|
{ |
||||
|
index: 53, |
||||
|
chapterId: '54', |
||||
|
name: '第54章 故人重逢' |
||||
|
}, |
||||
|
{ |
||||
|
index: 54, |
||||
|
chapterId: '55', |
||||
|
name: '第55章 青帝遗藏' |
||||
|
}, |
||||
|
{ |
||||
|
index: 55, |
||||
|
chapterId: '56', |
||||
|
name: '第56章 青帝归来' |
||||
|
}, |
||||
|
{ |
||||
|
index: 56, |
||||
|
chapterId: '57', |
||||
|
name: '第57章 继承' |
||||
|
}, |
||||
|
{ |
||||
|
index: 57, |
||||
|
chapterId: '58', |
||||
|
name: '第58章 月食' |
||||
|
}, |
||||
|
{ |
||||
|
index: 58, |
||||
|
chapterId: '59', |
||||
|
name: '第59章 异变' |
||||
|
}, |
||||
|
{ |
||||
|
index: 59, |
||||
|
chapterId: '60', |
||||
|
name: '第60章 躁动' |
||||
|
}, |
||||
|
{ |
||||
|
index: 60, |
||||
|
chapterId: '61', |
||||
|
name: '第61章 血池' |
||||
|
}, |
||||
|
{ |
||||
|
index: 61, |
||||
|
chapterId: '62', |
||||
|
name: '第62章 共鸣' |
||||
|
}, |
||||
|
{ |
||||
|
index: 62, |
||||
|
chapterId: '63', |
||||
|
name: '第63章 轮回' |
||||
|
}, |
||||
|
{ |
||||
|
index: 63, |
||||
|
chapterId: '64', |
||||
|
name: '第64章 囚徒' |
||||
|
}, |
||||
|
{ |
||||
|
index: 64, |
||||
|
chapterId: '65', |
||||
|
name: '第65章 分裂的同盟' |
||||
|
}, |
||||
|
{ |
||||
|
index: 65, |
||||
|
chapterId: '66', |
||||
|
name: '第66章 祭祀' |
||||
|
}, |
||||
|
{ |
||||
|
index: 66, |
||||
|
chapterId: '67', |
||||
|
name: '第67章 千毒窟救援' |
||||
|
}, |
||||
|
{ |
||||
|
index: 67, |
||||
|
chapterId: '68', |
||||
|
name: '第68章 白帝城' |
||||
|
}, |
||||
|
{ |
||||
|
index: 68, |
||||
|
chapterId: '69', |
||||
|
name: '第69章 白帝疑云' |
||||
|
}, |
||||
|
{ |
||||
|
index: 69, |
||||
|
chapterId: '70', |
||||
|
name: '第70章 白帝七卫' |
||||
|
}, |
||||
|
{ |
||||
|
index: 70, |
||||
|
chapterId: '71', |
||||
|
name: '第71章 核心' |
||||
|
}, |
||||
|
{ |
||||
|
index: 71, |
||||
|
chapterId: '72', |
||||
|
name: '第72章 平衡' |
||||
|
}, |
||||
|
{ |
||||
|
index: 72, |
||||
|
chapterId: '73', |
||||
|
name: '第73章 数据' |
||||
|
}, |
||||
|
{ |
||||
|
index: 73, |
||||
|
chapterId: '74', |
||||
|
name: '第74章 中枢密室' |
||||
|
}, |
||||
|
{ |
||||
|
index: 74, |
||||
|
chapterId: '75', |
||||
|
name: '第75章 崩塌' |
||||
|
}, |
||||
|
{ |
||||
|
index: 75, |
||||
|
chapterId: '76', |
||||
|
name: '第76章 异星迷途' |
||||
|
}, |
||||
|
{ |
||||
|
index: 76, |
||||
|
chapterId: '77', |
||||
|
name: '第77章 卫星避难所' |
||||
|
}, |
||||
|
{ |
||||
|
index: 77, |
||||
|
chapterId: '78', |
||||
|
name: '第78章 启动' |
||||
|
}, |
||||
|
{ |
||||
|
index: 78, |
||||
|
chapterId: '79', |
||||
|
name: '第79章 玄冰劫火' |
||||
|
}, |
||||
|
{ |
||||
|
index: 79, |
||||
|
chapterId: '80', |
||||
|
name: '第80章 晶劫之战' |
||||
|
}, |
||||
|
{ |
||||
|
index: 80, |
||||
|
chapterId: '81', |
||||
|
name: '第81章 归墟之秘' |
||||
|
}, |
||||
|
{ |
||||
|
index: 81, |
||||
|
chapterId: '82', |
||||
|
name: '第82章 两界之子' |
||||
|
}, |
||||
|
{ |
||||
|
index: 82, |
||||
|
chapterId: '83', |
||||
|
name: '第83章 北冥寒渊' |
||||
|
}, |
||||
|
{ |
||||
|
index: 83, |
||||
|
chapterId: '84', |
||||
|
name: '第84章 最后一块' |
||||
|
}, |
||||
|
{ |
||||
|
index: 84, |
||||
|
chapterId: '85', |
||||
|
name: '第85章 终局之战' |
||||
|
}, |
||||
|
{ |
||||
|
index: 85, |
||||
|
chapterId: '86', |
||||
|
name: '第86章 异常' |
||||
|
}, |
||||
|
{ |
||||
|
index: 86, |
||||
|
chapterId: '87', |
||||
|
name: '第87章 遗址' |
||||
|
}, |
||||
|
{ |
||||
|
index: 87, |
||||
|
chapterId: '88', |
||||
|
name: '第88章 灵晶潮汐' |
||||
|
}, |
||||
|
{ |
||||
|
index: 88, |
||||
|
chapterId: '89', |
||||
|
name: '第89章 晶界迷踪' |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
// 正确导出模块
|
||||
|
export default { |
||||
|
chaptersList |
||||
|
}; |
||||
|
|
||||
|
// 也支持默认导出
|
||||
|
export { chaptersList }; |
@ -0,0 +1,124 @@ |
|||||
|
<template> |
||||
|
<view class="progress-container"> |
||||
|
<view class="progress-container2" id="progress" @touchstart="touchstart" @touchend="touchend" @touchmove="touchmove"> |
||||
|
<view class="progress-box"> |
||||
|
<progress :percent="percent" activeColor="#000" backgroundColor="#1c1c1c" stroke-width="3"/> |
||||
|
</view> |
||||
|
<view class="ball-box" :class="{bigger: isTouch, shadow: isTouch}" :style="{left: `${percent}%`}"></view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props:{ |
||||
|
total: { |
||||
|
type: Number, |
||||
|
default: 1 |
||||
|
}, |
||||
|
index: { |
||||
|
type: Number, |
||||
|
default: 0 |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
left: 0, //进度条最左侧位置 |
||||
|
right: 0, //进度条最右侧位置 |
||||
|
isTouch: false, |
||||
|
// touchTimer: null, //用于触摸节流 |
||||
|
percent: 0, |
||||
|
} |
||||
|
}, |
||||
|
watch:{ |
||||
|
index() { |
||||
|
this.percent = this.index / this.total * 100 |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.percent = this.index / this.total * 100 |
||||
|
this.getLocation() |
||||
|
}, |
||||
|
methods: { |
||||
|
|
||||
|
getLocation() { |
||||
|
const query = uni.createSelectorQuery().in(this); |
||||
|
query.select('#progress').boundingClientRect(data => { |
||||
|
this.left = data.left |
||||
|
this.right = data.right |
||||
|
}).exec(); |
||||
|
}, |
||||
|
|
||||
|
touchstart() { |
||||
|
this.isTouch = true |
||||
|
this.$emit('progressStart') |
||||
|
}, |
||||
|
|
||||
|
touchend(e) { |
||||
|
this.isTouch = false |
||||
|
let index = this.calcIndex(e.changedTouches[0].clientX) |
||||
|
this.$emit('progressEnd', index) |
||||
|
this.percent = index / this.total * 100 |
||||
|
}, |
||||
|
|
||||
|
touchmove(e) { |
||||
|
// if (!this.touchTimer) { |
||||
|
let index = this.calcIndex(e.touches[0].clientX) |
||||
|
this.$emit('indexChange', index) |
||||
|
this.percent = index / this.total * 100 |
||||
|
// this.touchTimer = setTimeout(() => { |
||||
|
// this.touchTimer = null; |
||||
|
// }, 100) |
||||
|
// } |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 输入位置计算index |
||||
|
**/ |
||||
|
calcIndex(px) { |
||||
|
let single = (this.right - this.left) / this.total |
||||
|
let index = Math.round((px - this.left) / single) |
||||
|
index = index < 0 ? 0 : index |
||||
|
index = index > this.total ? this.total : index |
||||
|
return index |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.progress-container{ |
||||
|
padding: 0 10px; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
.progress-container2{ |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
.progress-box{ |
||||
|
width: 100%; |
||||
|
} |
||||
|
.ball-box{ |
||||
|
position: absolute; |
||||
|
width: 10px; |
||||
|
height: 10px; |
||||
|
border-radius: 50%; |
||||
|
background-color: #000; |
||||
|
transform: translateX(-50%); |
||||
|
} |
||||
|
.shadow{ |
||||
|
box-shadow: 0px 0px 1px 5px rgba(#888,.4); |
||||
|
} |
||||
|
.bigger{ |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
</style> |
File diff suppressed because one or more lines are too long
@ -0,0 +1,134 @@ |
|||||
|
<template> |
||||
|
<view class="virtual-list" style="position: relative;"> |
||||
|
<movable-area style="position: absolute;right: 0;width: 30px;height: 100%;"> |
||||
|
<movable-view class="action-bar-box" direction="vertical" @change="change" :y="y" :animation="false"> |
||||
|
<view style="border-bottom: #000 solid 2px;width: 100%;"></view> |
||||
|
<view style="border-bottom: #000 solid 2px;width: 100%;"></view> |
||||
|
</movable-view> |
||||
|
</movable-area> |
||||
|
<scroll-view scroll-y="true" |
||||
|
:style="{ |
||||
|
'height': scrollHeight + 'px', |
||||
|
'position': 'relative', |
||||
|
'zIndex': 1 |
||||
|
}" |
||||
|
@scroll="handleScroll" :scroll-top="scrollTop" :show-scrollbar="false"> |
||||
|
|
||||
|
<view class="scroll-bar" |
||||
|
:style="{ |
||||
|
'height': localHeight + 'px' |
||||
|
}"></view> |
||||
|
<view class="list" |
||||
|
:style="{ |
||||
|
'transform': `translateY(${offset}px)` |
||||
|
}"> |
||||
|
<view class="item-wrap" |
||||
|
v-for="(item, index) in visibleData" |
||||
|
:key="index"> |
||||
|
<slot :item="item" :active="active"></slot> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'VirtualList', |
||||
|
props: { |
||||
|
// 所有的items |
||||
|
items: Array, |
||||
|
// 可视区域的item数量 |
||||
|
remain: Number, |
||||
|
// item大小 |
||||
|
size: Number, |
||||
|
// 当前章节 |
||||
|
active: Number, |
||||
|
// 可使区域高度 |
||||
|
scrollHeight: Number |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
// 起始 |
||||
|
start: 0, |
||||
|
// 结束 |
||||
|
end: this.remain, |
||||
|
// list 偏移量 |
||||
|
offset: 0, |
||||
|
scrollTop: 0, |
||||
|
y: 0 |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
//当前章节滚动至顶部 |
||||
|
this.scrollTop = this.size * this.active |
||||
|
}, |
||||
|
computed: { |
||||
|
// 预留项 |
||||
|
preCount() { |
||||
|
return Math.min(this.start, this.remain); |
||||
|
}, |
||||
|
nextCount() { |
||||
|
return Math.min(this.items.length - this.end, this.remain); |
||||
|
}, |
||||
|
// 可视区域的item |
||||
|
visibleData() { |
||||
|
const start = this.start - this.preCount; |
||||
|
const end = this.end + this.nextCount; |
||||
|
console.log(this.items,'this.items.slice(start, end)'); |
||||
|
return this.items.slice(start, end); |
||||
|
}, |
||||
|
localHeight() { |
||||
|
return this.items.length * this.size |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
change(e) { |
||||
|
if (e.detail.source !== 'touch') { |
||||
|
return |
||||
|
} |
||||
|
let y = e.detail.y; |
||||
|
let scroll = y/(this.scrollHeight-40)*(this.localHeight-this.scrollHeight); |
||||
|
scroll = scroll < 0 ? 0 : scroll; |
||||
|
this.scrollTop = scroll; |
||||
|
}, |
||||
|
handleScroll(ev) { |
||||
|
const scrollTop = ev.detail.scrollTop; |
||||
|
this.y = scrollTop/(this.localHeight-this.scrollHeight)*(this.scrollHeight-40) |
||||
|
// 开始位置 |
||||
|
const start = Math.floor(scrollTop / this.size) |
||||
|
this.start = start < 0 ? 0 : start; |
||||
|
// 结束位置 |
||||
|
this.end = this.start + this.remain; |
||||
|
// 计算偏移 |
||||
|
const offset = scrollTop - (scrollTop % this.size) - this.preCount * this.size |
||||
|
this.offset = offset < 0 ? 0 : offset; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
|
||||
|
.list { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
.action-bar-box{ |
||||
|
padding: 3px; |
||||
|
display: flex; |
||||
|
flex-flow: column; |
||||
|
justify-content: space-around; |
||||
|
align-items: center; |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
background-color: transparent; |
||||
|
border-radius: 10rpx; |
||||
|
box-shadow: 0 0 5px #000; |
||||
|
width: 20px; |
||||
|
height: 40px; |
||||
|
z-index:2; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,249 @@ |
|||||
|
// ... existing code ... |
||||
|
<template> |
||||
|
<view class="content"> |
||||
|
<!-- 顶部导航 --> |
||||
|
<!-- <view class="top-nav"> |
||||
|
<view class="back-btn" @click="goBack">返回</view> |
||||
|
<view class="add-book-btn" @click="addToBookshelf">+ 加入书架</view> |
||||
|
</view> --> |
||||
|
<BackButton /> |
||||
|
<!-- 封面图 --> |
||||
|
<image class="cover-image" :src="showImg('/uploads/20250918/478322390dfe8befd6fb30643e1b5cb1.png')" |
||||
|
mode="aspectFill"></image> |
||||
|
|
||||
|
<!-- 小说信息 --> |
||||
|
<view class="book-info"> |
||||
|
<text class="title">{{ bookTitle }}</text> |
||||
|
<view class="info-row"> |
||||
|
<text class="info-text">{{ updateInfo }} | 字数: {{ wordCount }}万字</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 更新进度 --> |
||||
|
<view class="progress-row"> |
||||
|
<text class="progress-label">更新进度:</text> |
||||
|
<text class="progress-text">{{ progressText }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 分类标签 --> |
||||
|
<view class="tags-container"> |
||||
|
<view class="tag-item" v-for="tag in tags" :key="tag">{{ tag }}</view> |
||||
|
</view> |
||||
|
<view class="line"> |
||||
|
|
||||
|
</view> |
||||
|
<!-- 简介 --> |
||||
|
<view class="intro-container"> |
||||
|
<text class="intro-text">{{ intro }}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 按钮组 --> |
||||
|
<view class="button-group"> |
||||
|
<view class="read-btn" @click="startReading"> |
||||
|
<text class="btn-icon">📖</text> |
||||
|
<text class="btn-text">开始阅读</text> |
||||
|
</view> |
||||
|
<view class="read-btn" @click="playAudio"> |
||||
|
<text class="btn-icon">🎧</text> |
||||
|
<text class="btn-text">点击播放</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
bookTitle: '《园门修真传》', |
||||
|
updateInfo: '1天前更新', |
||||
|
wordCount: '20', |
||||
|
progressText: '第八十九章(未完结)', |
||||
|
tags: ['穿越玄幻', '修真进阶', '古典园林', '热血爽文'], |
||||
|
intro: '张锁仙意外穿越修仙世界,凭借着自己的扫地圣体本以为会在宗门大开杀戒,没想到竟被扔到山门做个杂役弟子?从此过上了钓鱼种田的不被卷生活。可偏偏宗门大会要拉他一个没有灵力的人上场看笑话?' |
||||
|
} |
||||
|
}, |
||||
|
onLoad() { |
||||
|
|
||||
|
}, |
||||
|
methods: { |
||||
|
goBack() { |
||||
|
uni.navigateBack(); |
||||
|
}, |
||||
|
addToBookshelf() { |
||||
|
uni.showToast({ |
||||
|
title: '已加入书架', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
}, |
||||
|
startReading() { |
||||
|
uni.navigateTo({ |
||||
|
url: '/subPackages/other/read' |
||||
|
}); |
||||
|
}, |
||||
|
playAudio() { |
||||
|
uni.navigateTo({ |
||||
|
url: '/subPackages/other/novelCatalog' |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 20rpx; |
||||
|
background-color: #F6EBD4; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.top-nav { |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 20rpx 30rpx; |
||||
|
position: relative; |
||||
|
z-index: 10; |
||||
|
background-color: #ffffff; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.back-btn { |
||||
|
font-size: 32rpx; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.add-book-btn { |
||||
|
font-size: 28rpx; |
||||
|
color: #e64340; |
||||
|
background-color: #fff; |
||||
|
border: 1rpx solid #e64340; |
||||
|
border-radius: 20rpx; |
||||
|
padding: 8rpx 16rpx; |
||||
|
} |
||||
|
|
||||
|
.cover-image { |
||||
|
width: 300rpx; |
||||
|
height: 400rpx; |
||||
|
margin: 30rpx auto; |
||||
|
border-radius: 16rpx; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.book-info { |
||||
|
width: 100%; |
||||
|
text-align: center; |
||||
|
padding: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 48rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
line-height: 1.4; |
||||
|
} |
||||
|
|
||||
|
.info-row { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin: 10rpx 0; |
||||
|
} |
||||
|
|
||||
|
.progress-row { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
font-size: 28rpx; |
||||
|
color: #999; |
||||
|
margin-top: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.progress-label { |
||||
|
margin-right: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.progress-text { |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.tags-container { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
justify-content: center; |
||||
|
gap: 15rpx; |
||||
|
margin: 30rpx 0; |
||||
|
} |
||||
|
|
||||
|
.tag-item { |
||||
|
background-color: #76352D; |
||||
|
color: #fff; |
||||
|
padding: 6rpx 12rpx; |
||||
|
border-radius: 12rpx; |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.line { |
||||
|
width: 100%; |
||||
|
height: 1rpx; |
||||
|
/* border: 1rpx solid #d3d3d3; */ |
||||
|
background: #d3d3d3; |
||||
|
margin: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.intro-container { |
||||
|
width: 100%; |
||||
|
padding: 20rpx 40rpx; |
||||
|
/* background-color: #ffffff; */ |
||||
|
border-radius: 16rpx; |
||||
|
/* box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1); */ |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.intro-text { |
||||
|
font-size: 22rpx; |
||||
|
color: #333; |
||||
|
line-height: 1.8; |
||||
|
text-align: left; |
||||
|
} |
||||
|
|
||||
|
.button-group { |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: space-around; |
||||
|
gap: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.read-btn, |
||||
|
.play-btn { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 300rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 40rpx; |
||||
|
font-size: 32rpx; |
||||
|
color: #333; |
||||
|
background-color: #ffffff; |
||||
|
border: 1rpx solid #ddd; |
||||
|
} |
||||
|
|
||||
|
.read-btn { |
||||
|
border-color: #e64340; |
||||
|
color: #e64340; |
||||
|
} |
||||
|
|
||||
|
.play-btn { |
||||
|
border-color: #888; |
||||
|
color: #888; |
||||
|
} |
||||
|
|
||||
|
.btn-icon { |
||||
|
font-size: 36rpx; |
||||
|
margin-right: 10rpx; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,48 @@ |
|||||
|
<template> |
||||
|
<view class="content"> |
||||
|
<!-- <BackButton /> |
||||
|
<view class=""> |
||||
|
园门修真转 |
||||
|
</view> --> |
||||
|
|
||||
|
<scroll-view scroll-y="true" @scroll="handleScroll" :scroll-top="scrollTop" :show-scrollbar="false"> |
||||
|
<view class="chapter" v-for="item in directoryList" :key="item.chapterId" @click="changePlay"> |
||||
|
<view class=""> |
||||
|
{{item.name}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import directoryModule from './components/directory.js' |
||||
|
// 正确解构 directoryList |
||||
|
const directoryListJs = directoryModule.chaptersList || directoryModule.default?.chaptersList || []; |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
directoryList: directoryListJs, |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
changePlay(){ |
||||
|
uni.navigateTo({ |
||||
|
url:'/subPackages/other/playNovel' |
||||
|
}) |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.content{ |
||||
|
padding: 30rpx; |
||||
|
background: #595959; |
||||
|
color: #fff; |
||||
|
.chapter{ |
||||
|
padding-bottom: 30rpx; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,448 @@ |
|||||
|
<template> |
||||
|
<view class="novel-player-container"> |
||||
|
<!-- 1. 小说封面图区域(适配多端屏幕比例) --> |
||||
|
<view class="novel-cover-wrapper"> |
||||
|
<!-- 封面图:加载态+失败备用 --> |
||||
|
<image |
||||
|
class="novel-cover" |
||||
|
:class="{ 'cover-loading': isCoverLoading }" |
||||
|
:src="showImg('/uploads/20250918/478322390dfe8befd6fb30643e1b5cb1.png')" |
||||
|
mode="widthFix" |
||||
|
@load="isCoverLoading = false" |
||||
|
@error="handleCoverError" |
||||
|
lazy-load |
||||
|
></image> |
||||
|
<!-- 封面加载失败备用视图 --> |
||||
|
<view class="cover-fallback" v-if="isCoverError"> |
||||
|
<uni-icons type="image" size="40" color="#999"></uni-icons> |
||||
|
<text class="fallback-text">封面加载失败</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 2. 小说信息区域 --> |
||||
|
<view class="novel-info"> |
||||
|
<text class="novel-title">园界修真传</text> |
||||
|
<text class="novel-chapter">第1章:考研失败是有原因的</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 3. 音频核心控制区域 --> |
||||
|
<view class="audio-player"> |
||||
|
<!-- Uniapp 音频组件(替代原生audio,支持多端) --> |
||||
|
<uni-audio |
||||
|
ref="audioRef" |
||||
|
:src="audioUrl" |
||||
|
:preload="preloadMode" |
||||
|
:initial-time="0" |
||||
|
@play="updatePlayStatus(true)" |
||||
|
@pause="updatePlayStatus(false)" |
||||
|
@ended="handleAudioEnd" |
||||
|
@timeupdate="updateProgress" |
||||
|
@error="handleAudioError" |
||||
|
hidden |
||||
|
></uni-audio> |
||||
|
|
||||
|
<!-- 播放/暂停按钮 --> |
||||
|
<button |
||||
|
class="play-btn" |
||||
|
:disabled="isAudioLoading" |
||||
|
@click="togglePlayPause" |
||||
|
hover-class="play-btn-hover" |
||||
|
> |
||||
|
<uni-icons |
||||
|
:type="isPlaying ? 'pause' : 'play'" |
||||
|
size="24" |
||||
|
color="#fff" |
||||
|
></uni-icons> |
||||
|
</button> |
||||
|
|
||||
|
<!-- 进度条控制(支持点击+拖动) --> |
||||
|
<view class="progress-container" @click="handleProgressClick"> |
||||
|
<!-- 已播放进度 --> |
||||
|
<view |
||||
|
class="progress-played" |
||||
|
:style="{ width: `${progressPercent}%` }" |
||||
|
></view> |
||||
|
<!-- 进度滑块 --> |
||||
|
<view |
||||
|
class="progress-thumb" |
||||
|
:style="{ left: `${progressPercent}%` }" |
||||
|
@touchstart="startDragProgress" |
||||
|
@touchmove="onDragProgress" |
||||
|
@touchend="endDragProgress" |
||||
|
></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 时间显示(已播放/总时长) --> |
||||
|
<view class="time-display"> |
||||
|
<text>{{ formatTime(currentTime) }}</text> |
||||
|
<text class="time-split">/</text> |
||||
|
<text>{{ formatTime(totalTime) }}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 音量控制 --> |
||||
|
<view class="volume-control"> |
||||
|
<uni-icons |
||||
|
:type="isMuted ? 'volume-off' : 'volume-up'" |
||||
|
size="18" |
||||
|
color="#666" |
||||
|
@click="toggleMute" |
||||
|
></uni-icons> |
||||
|
<slider |
||||
|
class="volume-slider" |
||||
|
min="0" |
||||
|
max="100" |
||||
|
:value="currentVolume" |
||||
|
@change="adjustVolume" |
||||
|
activeColor="#32c5ff" |
||||
|
></slider> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 清晰度选择(模拟多音质切换) --> |
||||
|
<view class="quality-select"> |
||||
|
<text class="quality-label">清晰度:</text> |
||||
|
<picker |
||||
|
class="quality-picker" |
||||
|
:value="selectedQuality" |
||||
|
:range="qualityOptions" |
||||
|
:range-key="'label'" |
||||
|
@change="switchAudioQuality" |
||||
|
> |
||||
|
<text>{{ qualityOptions.find(item => item.value === selectedQuality).label }}</text> |
||||
|
<uni-icons type="down" size="14" color="#666" class="picker-icon"></uni-icons> |
||||
|
</picker> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
name: 'PlayNovel', |
||||
|
data() { |
||||
|
return { |
||||
|
// 图片配置 |
||||
|
coverUrl: '/static/image.png', // 封面图路径(Uniapp 静态资源放static目录) |
||||
|
isCoverLoading: true, // 封面加载状态 |
||||
|
isCoverError: false, // 封面加载失败状态 |
||||
|
|
||||
|
// 音频配置 |
||||
|
audioUrl: 'https://des.js-dyyj.com/data/2025/09/05/9875a62d-14ef-481e-b88f-19c061478ce6.MP3', // 音频文件路径(需替换为实际地址,支持本地/CDN) |
||||
|
preloadMode: 'auto', // 预加载策略:auto/metadata/none |
||||
|
isAudioLoading: false, // 音频加载状态 |
||||
|
isPlaying: false, // 播放状态 |
||||
|
currentTime: 0, // 当前播放时间(秒) |
||||
|
totalTime: 0, // 音频总时长(秒) |
||||
|
progressPercent: 0, // 播放进度百分比(0-100) |
||||
|
currentVolume: 80, // 当前音量(0-100) |
||||
|
isMuted: false, // 静音状态 |
||||
|
isDragging: false, // 进度条拖动状态 |
||||
|
|
||||
|
// 清晰度配置(需后端提供对应音质的音频地址) |
||||
|
qualityOptions: [ |
||||
|
{ label: '标准', value: 'low' }, |
||||
|
{ label: '高清', value: 'medium' }, |
||||
|
{ label: '无损', value: 'high' } |
||||
|
], |
||||
|
selectedQuality: 'medium' // 默认高清 |
||||
|
}; |
||||
|
}, |
||||
|
onReady() { |
||||
|
// Uniapp 组件就绪后获取音频实例 |
||||
|
this.audioRef = this.$refs.audioRef; |
||||
|
// 初始化音量 |
||||
|
this.audioRef.setVolume(this.currentVolume / 100); |
||||
|
// 初始化音频地址(可根据清晰度默认值设置) |
||||
|
this.switchAudioQuality({ detail: { value: 'medium' } }); |
||||
|
}, |
||||
|
onUnload() { |
||||
|
// 页面卸载时停止播放,释放资源 |
||||
|
if (this.audioRef) { |
||||
|
this.audioRef.pause(); |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// ---------------------- 图片相关方法 ---------------------- |
||||
|
handleCoverError() { |
||||
|
this.isCoverLoading = false; |
||||
|
this.isCoverError = true; |
||||
|
}, |
||||
|
|
||||
|
// ---------------------- 音频核心控制 ---------------------- |
||||
|
// 切换播放/暂停 |
||||
|
togglePlayPause() { |
||||
|
if (this.isPlaying) { |
||||
|
this.audioRef.pause(); |
||||
|
} else { |
||||
|
this.isAudioLoading = true; |
||||
|
this.audioRef.play().catch(err => { |
||||
|
console.error('播放失败:', err); |
||||
|
this.isAudioLoading = false; |
||||
|
uni.showToast({ title: '音频播放失败', icon: 'none' }); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 更新播放状态 |
||||
|
updatePlayStatus(isPlaying) { |
||||
|
this.isPlaying = isPlaying; |
||||
|
this.isAudioLoading = false; |
||||
|
}, |
||||
|
|
||||
|
// 音频播放完毕(可扩展下一章逻辑) |
||||
|
handleAudioEnd() { |
||||
|
this.isPlaying = false; |
||||
|
this.currentTime = 0; |
||||
|
this.progressPercent = 0; |
||||
|
uni.showToast({ title: '本章播放完毕', icon: 'none' }); |
||||
|
// 如需自动下一章,可在此处调用章节切换逻辑 |
||||
|
}, |
||||
|
|
||||
|
// 音频加载失败 |
||||
|
handleAudioError() { |
||||
|
this.isAudioLoading = false; |
||||
|
uni.showToast({ title: '音频加载失败', icon: 'none' }); |
||||
|
}, |
||||
|
|
||||
|
// ---------------------- 进度条控制 ---------------------- |
||||
|
// 实时更新进度 |
||||
|
updateProgress() { |
||||
|
if (!this.isDragging) { |
||||
|
this.currentTime = this.audioRef.currentTime; |
||||
|
this.totalTime = this.audioRef.duration || 0; |
||||
|
this.progressPercent = (this.currentTime / this.totalTime) * 100 || 0; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 点击进度条跳转 |
||||
|
handleProgressClick(e) { |
||||
|
const containerWidth = e.currentTarget.offsetWidth; |
||||
|
const clickLeft = e.touches[0].clientX - e.currentTarget.offsetLeft; |
||||
|
const percent = (clickLeft / containerWidth) * 100; |
||||
|
this.setProgress(percent); |
||||
|
}, |
||||
|
|
||||
|
// 开始拖动进度条 |
||||
|
startDragProgress() { |
||||
|
this.isDragging = true; |
||||
|
}, |
||||
|
|
||||
|
// 拖动进度条中 |
||||
|
onDragProgress(e) { |
||||
|
if (!this.isDragging) return; |
||||
|
const containerWidth = e.currentTarget.offsetWidth; |
||||
|
const dragLeft = e.touches[0].clientX - e.currentTarget.offsetLeft; |
||||
|
let percent = (dragLeft / containerWidth) * 100; |
||||
|
// 限制进度在0-100之间 |
||||
|
percent = Math.max(0, Math.min(100, percent)); |
||||
|
this.progressPercent = percent; |
||||
|
}, |
||||
|
|
||||
|
// 结束拖动进度条 |
||||
|
endDragProgress() { |
||||
|
this.isDragging = false; |
||||
|
this.setProgress(this.progressPercent); |
||||
|
}, |
||||
|
|
||||
|
// 设置进度(通用方法) |
||||
|
setProgress(percent) { |
||||
|
const targetTime = (percent / 100) * this.totalTime; |
||||
|
this.audioRef.seek(targetTime); |
||||
|
this.currentTime = targetTime; |
||||
|
this.progressPercent = percent; |
||||
|
}, |
||||
|
|
||||
|
// ---------------------- 音量控制 ---------------------- |
||||
|
// 切换静音 |
||||
|
toggleMute() { |
||||
|
this.isMuted = !this.isMuted; |
||||
|
this.audioRef.setMuted(this.isMuted); |
||||
|
}, |
||||
|
|
||||
|
// 调节音量 |
||||
|
adjustVolume(e) { |
||||
|
this.currentVolume = e.detail.value; |
||||
|
this.audioRef.setVolume(this.currentVolume / 100); |
||||
|
// 调节音量时自动取消静音 |
||||
|
if (this.isMuted && this.currentVolume > 0) { |
||||
|
this.isMuted = false; |
||||
|
this.audioRef.setMuted(false); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// ---------------------- 清晰度切换 ---------------------- |
||||
|
switchAudioQuality(e) { |
||||
|
const quality = e.detail.value; |
||||
|
this.selectedQuality = quality; |
||||
|
// 此处需替换为实际音质对应的音频地址(示例格式) |
||||
|
const audioMap = { |
||||
|
low: '/static/audio/chapter1-low.mp3', |
||||
|
medium: '/static/audio/chapter1-medium.mp3', |
||||
|
high: '/static/audio/chapter1-high.mp3' |
||||
|
}; |
||||
|
this.audioUrl = audioMap[quality]; |
||||
|
// 切换音频后保持播放状态 |
||||
|
if (this.isPlaying) { |
||||
|
this.audioRef.play(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// ---------------------- 工具方法 ---------------------- |
||||
|
// 格式化时间(秒 → 分:秒,如 8:16) |
||||
|
formatTime(seconds) { |
||||
|
if (!seconds) return '00:00'; |
||||
|
const min = Math.floor(seconds / 60); |
||||
|
const sec = Math.floor(seconds % 60); |
||||
|
// 补零处理(如 1 → 01) |
||||
|
return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
/* 容器整体样式 */ |
||||
|
.novel-player-container { |
||||
|
padding: 20rpx; |
||||
|
background: #595959; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
/* 封面图样式 */ |
||||
|
.novel-cover-wrapper { |
||||
|
width: 100%; |
||||
|
border-radius: 20rpx; |
||||
|
overflow: hidden; |
||||
|
margin-bottom: 30rpx; |
||||
|
position: relative; |
||||
|
} |
||||
|
.novel-cover { |
||||
|
width: 100%; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
.cover-loading { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
.cover-fallback { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
.fallback-text { |
||||
|
margin-top: 20rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 小说信息样式 */ |
||||
|
.novel-info { |
||||
|
margin-bottom: 30rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
.novel-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #fff; |
||||
|
margin-bottom: 10rpx; |
||||
|
display: block; |
||||
|
} |
||||
|
.novel-chapter { |
||||
|
font-size: 28rpx; |
||||
|
color: #fff; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
/* 音频播放器样式 */ |
||||
|
.audio-player { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 25rpx; |
||||
|
} |
||||
|
|
||||
|
/* 播放按钮样式 */ |
||||
|
.play-btn { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
background-color: #32c5ff; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
.play-btn-hover { |
||||
|
background-color: #28a4e0; |
||||
|
} |
||||
|
|
||||
|
/* 进度条样式 */ |
||||
|
.progress-container { |
||||
|
width: 100%; |
||||
|
height: 12rpx; |
||||
|
background-color: #eee; |
||||
|
border-radius: 6rpx; |
||||
|
position: relative; |
||||
|
touch-action: none; /* 防止移动端默认触摸行为 */ |
||||
|
} |
||||
|
.progress-played { |
||||
|
height: 100%; |
||||
|
background-color: #32c5ff; |
||||
|
border-radius: 6rpx; |
||||
|
} |
||||
|
.progress-thumb { |
||||
|
width: 24rpx; |
||||
|
height: 24rpx; |
||||
|
border-radius: 50%; |
||||
|
background-color: #32c5ff; |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%); |
||||
|
margin-left: -12rpx; |
||||
|
box-shadow: 0 0 10rpx rgba(50, 197, 255, 0.5); |
||||
|
} |
||||
|
|
||||
|
/* 时间显示样式 */ |
||||
|
.time-display { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
.time-split { |
||||
|
margin: 0 10rpx; |
||||
|
} |
||||
|
|
||||
|
/* 音量控制样式 */ |
||||
|
.volume-control { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 15rpx; |
||||
|
} |
||||
|
.volume-slider { |
||||
|
flex: 1; |
||||
|
height: 8rpx; |
||||
|
} |
||||
|
|
||||
|
/* 清晰度选择样式 */ |
||||
|
.quality-select { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 15rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
.quality-picker { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8rpx; |
||||
|
color: #32c5ff; |
||||
|
} |
||||
|
.picker-icon { |
||||
|
margin-top: 4rpx; |
||||
|
} |
||||
|
</style> |
File diff suppressed because it is too large
Loading…
Reference in new issue