feat: background image cache indexdb

This commit is contained in:
KUN1007 2023-10-19 18:19:34 +08:00
parent 64ad8215ac
commit b5e222855e
12 changed files with 185 additions and 69 deletions

16
.vscode/settings.json vendored
View file

@ -2,12 +2,14 @@
"cSpell.words": [ "cSpell.words": [
"ACGNGAME", "ACGNGAME",
"Akai", "Akai",
"Amayui",
"arpa", "arpa",
"axios", "axios",
"azkhx", "azkhx",
"bangumi", "bangumi",
"Bilibili", "Bilibili",
"Bishoujo", "Bishoujo",
"Chuudoku",
"Codepen", "Codepen",
"cout", "cout",
"dompurify", "dompurify",
@ -16,11 +18,15 @@
"Galgame", "Galgame",
"Galworld", "Galworld",
"gsap", "gsap",
"Hana",
"Hikari", "Hikari",
"Hitomi", "Hitomi",
"Hokenshitsu",
"iconify", "iconify",
"INTLIFY", "INTLIFY",
"Irotoridori", "Irotoridori",
"Joshu",
"Karenai",
"kfmax", "kfmax",
"Koori", "Koori",
"kungal", "kungal",
@ -28,10 +34,12 @@
"kungalgamer", "kungalgamer",
"ldquo", "ldquo",
"Licence", "Licence",
"localforage",
"loli", "loli",
"majesticons", "majesticons",
"Mangekyou", "Mangekyou",
"Maniwa", "Maniwa",
"Meister",
"mingcute", "mingcute",
"Mirai", "Mirai",
"mockjs", "mockjs",
@ -40,22 +48,29 @@
"Murasame", "Murasame",
"Nanami", "Nanami",
"nawa", "nawa",
"NEKOPARA",
"non-moe", "non-moe",
"nprogress", "nprogress",
"okaidia", "okaidia",
"Otome", "Otome",
"Owaru",
"persistedstate", "persistedstate",
"Pinia", "Pinia",
"prismjs", "prismjs",
"rdquo", "rdquo",
"Roka", "Roka",
"Sahou", "Sahou",
"Sakura",
"Sekai", "Sekai",
"Senren", "Senren",
"Sensei",
"Shabondama",
"shinnku", "shinnku",
"Shugaten",
"signin", "signin",
"sina", "sina",
"SMEE", "SMEE",
"Somnium",
"tachi", "tachi",
"tada", "tada",
"tdesign", "tdesign",
@ -75,6 +90,7 @@
"Vite", "Vite",
"VNDB", "VNDB",
"vueup", "vueup",
"Wataridori",
"weixin", "weixin",
"Wenders", "Wenders",
"ymgal", "ymgal",

View file

@ -22,6 +22,7 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"dompurify": "^3.0.6", "dompurify": "^3.0.6",
"localforage": "^1.10.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.6", "pinia": "^2.1.6",
"pinia-plugin-persistedstate": "^3.2.0", "pinia-plugin-persistedstate": "^3.2.0",

View file

@ -17,6 +17,9 @@ dependencies:
dompurify: dompurify:
specifier: ^3.0.6 specifier: ^3.0.6
version: 3.0.6 version: 3.0.6
localforage:
specifier: ^1.10.0
version: 1.10.0
nprogress: nprogress:
specifier: ^0.2.0 specifier: ^0.2.0
version: 0.2.0 version: 0.2.0
@ -1475,6 +1478,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
dev: false
/immutable@4.3.4: /immutable@4.3.4:
resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==}
dev: true dev: true
@ -1569,6 +1576,18 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/lie@3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
dependencies:
immediate: 3.0.6
dev: false
/localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
dependencies:
lie: 3.1.1
dev: false
/lodash.clonedeep@4.5.0: /lodash.clonedeep@4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
dev: false dev: false

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
// vue // vue
import { ref } from 'vue' import { onMounted, ref } from 'vue'
// store // store
import { useKUNGalgameSettingsStore } from '@/store/modules/settings' import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
@ -9,46 +9,44 @@ import { storeToRefs } from 'pinia'
// //
import Message from '@/components/alert/Message' import Message from '@/components/alert/Message'
import backgroundImages from './background' import { backgroundImages } from './background'
import { getBackgroundURL } from '@/hooks/useBackgroundPicture'
import { restoreBackground } from '@/hooks/useBackgroundPicture' import { restoreBackground } from '@/hooks/useBackgroundPicture'
const imageArray = ref<string[]>([])
// 使 store // 使 store
const settingsStore = useKUNGalgameSettingsStore()
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } = const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
storeToRefs(settingsStore) storeToRefs(useKUNGalgameSettingsStore())
//
const getBackground = async (imageNumber: number) => {
return await getBackgroundURL(`bg${imageNumber}-m`)
}
// //
const handelChangeImage = (index: number) => { const handelChangeImage = (index: number) => {
showKUNGalgameBackground.value = index.toString() showKUNGalgameBackground.value = `bg${index}`
} }
/* :
https://s3.bmp.ovh/imgs/2023/05/30/1ee99996d0eb2646.png
https://s3.bmp.ovh/imgs/2023/05/30/87d94be5e004547a.png
https://s3.bmp.ovh/imgs/2023/05/30/2a639bd15113b570.png
https://s3.bmp.ovh/imgs/2023/05/30/b7c73a1643bdc55b.png
https://s3.bmp.ovh/imgs/2023/05/30/ee67fdadd4104bbd.png
https://s3.bmp.ovh/imgs/2023/05/30/30aacd3045496498.png
https://s3.bmp.ovh/imgs/2023/05/30/ab2da01971cc1629.png
https://s3.bmp.ovh/imgs/2023/05/30/ed196495796482e4.png
https://s3.bmp.ovh/imgs/2023/05/30/a6dcdae0afe118f0.png
https://s3.bmp.ovh/imgs/2023/05/30/7aa57120cc6977a1.png
*/
// //
const url = ref('') const url = ref('')
const handleCustomBackground = () => { const handleCustomBackground = () => {
if (url.value) { if (url.value) {
showKUNGalgameCustomBackground.value = url.value showKUNGalgameCustomBackground.value = url.value
showKUNGalgameBackground.value = '1007' showKUNGalgameBackground.value = 'bg1007'
url.value = '' url.value = ''
} else { } else {
Message('Please input valid image url', '请输入合法的图片链接', 'warn') Message('Please input valid image url', '请输入合法的图片链接', 'warn')
} }
} }
//
onMounted(async () => {
for (const background of backgroundImages) {
const backgroundURL = await getBackground(background.index)
imageArray.value.push(backgroundURL)
}
})
</script> </script>
<template> <template>
@ -61,7 +59,7 @@ const handleCustomBackground = () => {
<ul class="kungalgame-restore-bg"> <ul class="kungalgame-restore-bg">
<li v-for="kun in backgroundImages" :key="kun.index"> <li v-for="kun in backgroundImages" :key="kun.index">
<img <img
:src="kun.image" :src="imageArray[kun.index - 1]"
:alt="kun.alt" :alt="kun.alt"
@click="handelChangeImage(kun.index)" @click="handelChangeImage(kun.index)"
/> />

View file

@ -1,56 +1,57 @@
/* :
https://s3.bmp.ovh/imgs/2023/05/30/1ee99996d0eb2646.png
https://s3.bmp.ovh/imgs/2023/05/30/87d94be5e004547a.png
https://s3.bmp.ovh/imgs/2023/05/30/2a639bd15113b570.png
https://s3.bmp.ovh/imgs/2023/05/30/b7c73a1643bdc55b.png
https://s3.bmp.ovh/imgs/2023/05/30/ee67fdadd4104bbd.png
https://s3.bmp.ovh/imgs/2023/05/30/30aacd3045496498.png
https://s3.bmp.ovh/imgs/2023/05/30/ab2da01971cc1629.png
https://s3.bmp.ovh/imgs/2023/05/30/ed196495796482e4.png
https://s3.bmp.ovh/imgs/2023/05/30/a6dcdae0afe118f0.png
https://s3.bmp.ovh/imgs/2023/05/30/7aa57120cc6977a1.png
*/
interface background { interface background {
index: number index: number
image: string
alt: string alt: string
} }
// 定义背景图片列表 // 定义背景图片列表
const backgroundImages: background[] = [ export const backgroundImages: background[] = [
{ {
index: 1, index: 1,
image: '/src/assets/images/bg/bg1-m.png', alt: 'Akai Hitomi ni Utsuru Sekai 紅い瞳に映るセカイ 红瞳映入的世界',
alt: 'azkhx',
}, },
{ {
index: 2, index: 2,
image: '/src/assets/images/bg/bg2-m.png', alt: 'Shugaten! しゅがてん! 糖调',
alt: 'azkhx',
}, },
{ {
index: 3, index: 3,
image: '/src/assets/images/bg/bg3-m.png', alt: 'Amayui Castle Meister 天結いキャッスルマイスター 天结神缘',
alt: 'azkhx',
}, },
{ {
index: 4, index: 4,
image: '/src/assets/images/bg/bg4-m.png', alt: 'Pieces Wataridori no Somnium 渡り鳥のソムニウム 渡鸟的梦',
alt: 'azkhx',
}, },
{ {
index: 5, index: 5,
image: '/src/assets/images/bg/bg5-m.png', alt: 'Karenai Sekai to Owaru Hana 枯れない世界と終わる花 不败世界与终焉之花',
alt: 'azkhx',
}, },
{ {
index: 6, index: 6,
image: '/src/assets/images/bg/bg6-m.png', alt: 'NEKOPARA ネコぱら 猫娘乐园',
alt: 'azkhx',
}, },
{ {
index: 7, index: 7,
image: '/src/assets/images/bg/bg7-m.png', alt: 'Sakura no Uta サクラノ詩 樱之诗',
alt: 'azkhx',
}, },
{ {
index: 8, index: 8,
image: '/src/assets/images/bg/bg8-m.png', alt: 'Hokenshitsu no Sensei to Shabondama Chuudoku no Joshu 保健室のセンセーとシャボン玉中毒の助手 保健室的老师与肥皂泡中毒的助手',
alt: 'azkhx',
}, },
{ {
index: 9, index: 9,
image: '/src/assets/images/bg/bg9-m.png', alt: 'Senren * Banka 千恋*万花 千恋*万花',
alt: 'azkhx',
}, },
] ]
export default backgroundImages

View file

@ -1,28 +1,76 @@
// 导入设置面板 store // 导入设置面板 store
import { useKUNGalgameSettingsStore } from '@/store/modules/settings' import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
import { saveImage, getImage } from './useLocalforage'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed } from 'vue'
// 使用设置面板的 store // 使用设置面板的 store
const settingsStore = useKUNGalgameSettingsStore() const settingsStore = useKUNGalgameSettingsStore()
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } = const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
storeToRefs(settingsStore) storeToRefs(settingsStore)
// 恢复空白背景 // 从后端获取背景图片数据
export const restoreBackground = () => { const fetchGetBackground = async (imageName: string): Promise<Blob> => {
showKUNGalgameBackground.value = '0' const baseUrl = import.meta.env.VITE_API_BASE_URL
const url = `/uploads/image/bg/${imageName}.webp`
const fullUrl = `${baseUrl}${url}`
const response = await fetch(fullUrl, {
method: 'GET',
headers: {
Authorization: `Bearer ${useKUNGalgameUserStore().getToken()}`,
},
})
return await response.blob()
} }
export const currBackground = computed(() => { // 根据图片的序号获取图片链接
const getBackgroundURL = async (imageName: string) => {
// 本地保存的图片 blob 数据
const backgroundImageBlobData = await getImage(imageName)
// 有数据则从本地创建 blob 链接
if (backgroundImageBlobData) {
return URL.createObjectURL(backgroundImageBlobData)
} else {
// 本地数据没有图片 blob 数据则从后端获取图片 blob 数据并存储在本地
const imageBlob = await fetchGetBackground(`${imageName}`)
console.log('调用 fetch get blob')
await saveImage(imageBlob, imageName)
return URL.createObjectURL(imageBlob)
}
}
// 这里的图片名后端定义的是 bg1.webp大图, bg1-m.webp预览图
const getCurrentBackground = async () => {
if ( if (
showKUNGalgameBackground.value === '0' || showKUNGalgameBackground.value === 'bg0' ||
showKUNGalgameBackground.value === 'none' showKUNGalgameBackground.value === 'none'
) { ) {
return 'none' return 'none'
} else if (showKUNGalgameBackground.value === '1007') {
return `url(${showKUNGalgameCustomBackground.value})`
} else {
// TODO: 替换为后端接口
return `url(/src/assets/images/bg/bg${showKUNGalgameBackground.value}.png)`
} }
}) if (showKUNGalgameBackground.value === 'bg1007') {
return `${showKUNGalgameCustomBackground.value}`
}
// 获取图片的 blob url
const url = await getBackgroundURL(showKUNGalgameBackground.value)
return url
}
// 获取所有图片的预览图好像。。。min 比 thumbnail 萌一点
// const getCurrentBackgroundMin = async () => {
// let imageArray: string[] = []
// for (let i = 0; i < 9; i++) {
// const url = await getBackgroundURL(`bg${i}-m`)
// imageArray.push(url)
// }
// return imageArray
// }
// 恢复空白背景
const restoreBackground = () => {
showKUNGalgameBackground.value = 'bg0'
}
export { getBackgroundURL, getCurrentBackground, restoreBackground }

View file

@ -0,0 +1,16 @@
import localforage from 'localforage'
// 保存图片
const saveImage = async (imageData: Blob, imageName: string): Promise<void> => {
await localforage.setItem(imageName, imageData)
}
// 获取图片
const getImage = async (imageName: string): Promise<Blob | null> => {
return await localforage.getItem(imageName)
}
// 删除图片
const deleteImage = async (imageName: string): Promise<void> => {
await localforage.removeItem(imageName)
}
export { saveImage, getImage, deleteImage }

View file

@ -1,16 +1,34 @@
<!-- 先放一个 Layout 在这里后面应该用得到 --> <!-- 先放一个 Layout 在这里后面应该用得到 -->
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, watch, ref } from 'vue'
// //
import 'animate.css' import 'animate.css'
import { getCurrentBackground } from '@/hooks/useBackgroundPicture'
import { currBackground } from '@/hooks/useBackgroundPicture'
import KUNGalgameTopBar from '@/components/top-bar/KUNGalgameTopBar.vue' import KUNGalgameTopBar from '@/components/top-bar/KUNGalgameTopBar.vue'
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
import { storeToRefs } from 'pinia'
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
storeToRefs(useKUNGalgameSettingsStore())
const imageURL = ref('')
onMounted(async () => {
imageURL.value = await getCurrentBackground()
})
watch(
() => [showKUNGalgameBackground.value, showKUNGalgameCustomBackground.value],
async () => {
imageURL.value = await getCurrentBackground()
}
)
</script> </script>
<template> <template>
<!-- #default v-slot 的简写route 就是路由Component 是一个 v-node --> <!-- #default v-slot 的简写route 就是路由Component 是一个 v-node -->
<div class="app" :style="{ backgroundImage: currBackground }"> <div class="app" :style="{ backgroundImage: `url(${imageURL})` }">
<div class="top-bar"> <div class="top-bar">
<KUNGalgameTopBar /> <KUNGalgameTopBar />
</div> </div>

View file

@ -81,7 +81,7 @@ export const constantRoutes: RouteRecordRaw[] = [
}, },
}, },
// KUNGalgame 403 TODO: // KUNGalgame 403
{ {
name: '403', name: '403',
path: '/kungalgame403', path: '/kungalgame403',

View file

@ -70,15 +70,14 @@ onBeforeRouteLeave(async (to, from, next) => {
<!-- 版权 --> <!-- 版权 -->
<KUNGalgameFooter style="margin: 0 auto; padding-top: 10px" /> <KUNGalgameFooter style="margin: 0 auto; padding-top: 10px" />
<span style="margin: 0 auto; color: var(--kungalgame-font-color-3)" <span style="margin: 0 auto; color: var(--kungalgame-font-color-3)">
>Editor powered by quill</span Editor powered by quill
> </span>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.root { .root {
margin-top: 20px;
height: calc(100vh - 65px); height: calc(100vh - 65px);
min-height: 1000px; min-height: 1000px;
display: flex; display: flex;

View file

@ -2,8 +2,6 @@
import { Icon } from '@iconify/vue' import { Icon } from '@iconify/vue'
const props = defineProps(['data']) const props = defineProps(['data'])
// TODO:
</script> </script>
<template> <template>

View file

@ -118,8 +118,10 @@ const handleClickComment = (
<!-- 顶部左侧名字 --> <!-- 顶部左侧名字 -->
<div class="name"> <div class="name">
{{ `${comment.c_user.name} ${$tm('topic.content.comment')}` }} {{ `${comment.c_user.name} ${$tm('topic.content.comment')}` }}
<!-- 跳转到用户主页 TODO: --> <!-- 跳转到用户主页 -->
<a href="#">{{ comment.to_user.name }}</a> <RouterLink :to="`/kungalgamer/${comment.to_user.uid}/info`">
{{ comment.to_user.name }}
</RouterLink>
</div> </div>
<!-- 顶部右侧点赞 --> <!-- 顶部右侧点赞 -->
<div class="operate"> <div class="operate">