Initial project
This commit is contained in:
164
src/components/article/template/ArticleAbout.vue
Normal file
164
src/components/article/template/ArticleAbout.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<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 { CommentBizType, MarkdownView, SettingKey, SettingMapper, Time } from "timi-web";
|
||||
import ArticleAPI from "@/api/ArticleAPI";
|
||||
import { ArticleView } from "@/types/Article";
|
||||
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>
|
||||
Reference in New Issue
Block a user