164 lines
4.4 KiB
Vue
164 lines
4.4 KiB
Vue
<template>
|
|
<div class="article-about">
|
|
<div v-if="article">
|
|
<markdown-view class="content" :content="article.data" />
|
|
<p
|
|
class="update-at gray"
|
|
v-text="'最后编辑时间:' + Time.toPassedDateTime(article.updatedAt || article.createdAt)"
|
|
></p>
|
|
</div>
|
|
<div class="spectrum" @click="toggleBGM" v-popup:text="'点击暂停/播放'">
|
|
<canvas ref="canvas" :width="canvasWidth" height="80"></canvas>
|
|
</div>
|
|
<audio ref="player" autoplay>
|
|
<source src="@/assets/media/fragile.mp3" type="audio/mpeg" />
|
|
</audio>
|
|
<p class="survival-time light-gray" v-text="survivalTime"></p>
|
|
<comment
|
|
v-if="SettingMapper.is(SettingKey.ENABLE_COMMENT) && article && article.showComment"
|
|
:bizType="CommentBizType.ARTICLE"
|
|
:bizId="1"
|
|
:titleStickyOffset="49"
|
|
:canComment="article.canComment"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ArticleView, CommentBizType, MarkdownView, SettingKey, SettingMapper, Time } from "timi-web";
|
|
import ArticleAPI from "@/api/ArticleAPI";
|
|
import { Comment } from "timi-tdesign-pc";
|
|
|
|
// 文章
|
|
const article = ref<ArticleView<any>>();
|
|
onMounted(async () => article.value = await ArticleAPI.view(1));
|
|
|
|
// 运行时间
|
|
const survivalTime = ref<string>("网站已运行 ---- 年 --- 天 -- 小时 -- 分钟 -- 秒");
|
|
const survivalTimer = ref();
|
|
onMounted(async () => {
|
|
const begin = new Date("2017/10/9 22:32:52");
|
|
clearInterval(survivalTimer.value);
|
|
survivalTimer.value = setInterval(() => {
|
|
const r = Time.between(begin);
|
|
survivalTime.value = `网站已运行 ${r.y} 年 ${r.d} 天 ${r.h} 小时 ${r.m.toString().padStart(2, "0")} 分钟 ${r.s.toString().padStart(2, "0")} 秒`;
|
|
}, 1000);
|
|
});
|
|
onBeforeUnmount(() => clearInterval(survivalTimer.value));
|
|
|
|
// 音乐
|
|
const player = ref<HTMLAudioElement>();
|
|
const canvas = ref<HTMLCanvasElement>();
|
|
const canvasWidth = ref(480);
|
|
const spectrumRender = ref<number>();
|
|
|
|
const toggleBGM = () => {
|
|
if (player.value) {
|
|
if (player.value.paused) {
|
|
player.value.play();
|
|
} else {
|
|
player.value.pause();
|
|
}
|
|
}
|
|
};
|
|
|
|
const drawSpectrum = () => {
|
|
if (!canvas.value || !player.value) {
|
|
return;
|
|
}
|
|
let ctx: any = new AudioContext();
|
|
const analyser = ctx.createAnalyser();
|
|
const audioSrc = ctx.createMediaElementSource(<HTMLMediaElement>player.value);
|
|
audioSrc.connect(analyser);
|
|
analyser.connect(ctx.destination);
|
|
|
|
const cWidth = canvas.value.width,
|
|
meterSize = 7,
|
|
cHeight = canvas.value.height - 2,
|
|
meterWidth = 6,
|
|
capHeight = 2,
|
|
meterNum = 2400 / meterSize,
|
|
capYPositionArray = [];
|
|
|
|
ctx = canvas.value.getContext("2d");
|
|
// 渐变
|
|
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
|
gradient.addColorStop(1, "#A67D7B");
|
|
gradient.addColorStop(0.5, "#A67D7B");
|
|
gradient.addColorStop(0, "#A67D7B");
|
|
|
|
const capStyle = "#A67D7B";
|
|
// 动画
|
|
(function renderFrame() {
|
|
const array = new Uint8Array(analyser.frequencyBinCount);
|
|
analyser.getByteFrequencyData(array);
|
|
const step = Math.round(array.length / meterNum);
|
|
ctx.clearRect(0, 0, cWidth, cHeight);
|
|
for (let i = 0; i < meterNum; i++) {
|
|
const value = array[i * step];
|
|
if (capYPositionArray.length < Math.round(meterNum)) {
|
|
capYPositionArray.push(value);
|
|
}
|
|
ctx.fillStyle = capStyle;
|
|
if (value < capYPositionArray[i]) {
|
|
ctx.fillRect(4 + i * meterSize, cHeight - (--capYPositionArray[i] / 3.2), meterWidth, capHeight);
|
|
} else {
|
|
ctx.fillRect(4 + i * meterSize, cHeight - value / 3.2, meterWidth, capHeight);
|
|
capYPositionArray[i] = value;
|
|
}
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(4 + i * meterSize, cHeight - value / 3.2 + capHeight, meterWidth, cHeight);
|
|
}
|
|
spectrumRender.value = requestAnimationFrame(renderFrame);
|
|
})();
|
|
};
|
|
onMounted(async () => {
|
|
if (player.value) {
|
|
player.value.volume = .2;
|
|
player.value.addEventListener("loadeddata", () => {
|
|
if (player.value && 2 <= player.value.readyState) {
|
|
// TODO: 成功 play 才允许绘制
|
|
drawSpectrum();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
onBeforeUnmount(() => {
|
|
if (spectrumRender.value) {
|
|
cancelAnimationFrame(spectrumRender.value);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.article-about {
|
|
|
|
.content {
|
|
width: calc(100% - 2rem);
|
|
padding: .5rem 1rem;
|
|
}
|
|
|
|
.update-at {
|
|
text-align: right;
|
|
padding-right: 1rem;
|
|
}
|
|
|
|
.spectrum {
|
|
width: 20rem;
|
|
height: 320px;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
background: url("@/assets/img/nagiasu.png") bottom no-repeat;
|
|
flex-direction: column;
|
|
justify-content: end;
|
|
background-size: 100%;
|
|
}
|
|
|
|
.survival-time {
|
|
height: 16px;
|
|
font-size: 13px;
|
|
text-align: center;
|
|
}
|
|
}
|
|
</style>
|