change: remove SSR
This commit is contained in:
parent
8c4e97c6f1
commit
c2e3002bbc
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -16,7 +16,6 @@
|
||||||
"Codepen",
|
"Codepen",
|
||||||
"commonmark",
|
"commonmark",
|
||||||
"cout",
|
"cout",
|
||||||
"crossorigin",
|
|
||||||
"dompurify",
|
"dompurify",
|
||||||
"fontawesome",
|
"fontawesome",
|
||||||
"galgame",
|
"galgame",
|
||||||
|
@ -62,7 +61,6 @@
|
||||||
"non-moe",
|
"non-moe",
|
||||||
"nord",
|
"nord",
|
||||||
"nprogress",
|
"nprogress",
|
||||||
"nuxt",
|
|
||||||
"okaidia",
|
"okaidia",
|
||||||
"Otome",
|
"Otome",
|
||||||
"Owaru",
|
"Owaru",
|
||||||
|
|
14
index.html
14
index.html
|
@ -10,18 +10,12 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="KUN Visual Novel | 鲲 Galgame. The CUTEST Visual Novel Forum!世界上最萌的 Galgame 论坛. Topic, Technique. NO ADs Forever. Free Forever"
|
content="The CUTEST Visual Novel Forum!世界上最萌的 Galgame 论坛. Topic, Technique. NO ADs Forever. Free Forever"
|
||||||
/>
|
/>
|
||||||
<title>KUN Visual Novel | 鲲 Galgame</title>
|
<title>KUNGalgame</title>
|
||||||
<!--preload-links-->
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"><!--ssr-outlet--></div>
|
<div id="app"></div>
|
||||||
<div id="teleported"><!--teleports--></div>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
<script type="module" src="/src/entry-client.ts"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window.__INITIAL_STATE__ = '__pinia'
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
19
package.json
19
package.json
|
@ -13,11 +13,9 @@
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ts-node-esm server-dev.ts",
|
"dev": "vite",
|
||||||
"prod": "ts-node-esm server-prod.ts",
|
"build": "vue-tsc && vite build",
|
||||||
"build:client": "vite build --outDir dist/client --ssrManifest",
|
"preview": "vite preview"
|
||||||
"build:server": "vite build --outDir dist/server --ssr src/entry-server.ts",
|
|
||||||
"build": "pnpm build:client && pnpm build:server"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@milkdown/core": "^7.3.1",
|
"@milkdown/core": "^7.3.1",
|
||||||
|
@ -36,12 +34,10 @@
|
||||||
"@milkdown/utils": "^7.3.1",
|
"@milkdown/utils": "^7.3.1",
|
||||||
"@milkdown/vue": "^7.3.1",
|
"@milkdown/vue": "^7.3.1",
|
||||||
"@prosemirror-adapter/vue": "^0.2.6",
|
"@prosemirror-adapter/vue": "^0.2.6",
|
||||||
"@vue/server-renderer": "^3.3.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",
|
"localforage": "^1.10.0",
|
||||||
"node-fetch": "^3.3.2",
|
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pinia-plugin-persistedstate": "^3.2.0",
|
"pinia-plugin-persistedstate": "^3.2.0",
|
||||||
|
@ -52,22 +48,13 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/vue": "^4.1.1",
|
"@iconify/vue": "^4.1.1",
|
||||||
"@koa/router": "^12.0.1",
|
|
||||||
"@nuxt/devalue": "^2.0.2",
|
|
||||||
"@types/dompurify": "^3.0.4",
|
"@types/dompurify": "^3.0.4",
|
||||||
"@types/js-cookie": "^3.0.5",
|
"@types/js-cookie": "^3.0.5",
|
||||||
"@types/koa": "^2.13.10",
|
|
||||||
"@types/koa-send": "^4.1.5",
|
|
||||||
"@types/node": "^20.8.10",
|
"@types/node": "^20.8.10",
|
||||||
"@types/nprogress": "^0.2.2",
|
"@types/nprogress": "^0.2.2",
|
||||||
"@vitejs/plugin-vue": "^4.4.0",
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
"express": "^4.18.2",
|
|
||||||
"koa": "^2.14.2",
|
|
||||||
"koa-connect": "^2.1.0",
|
|
||||||
"koa-send": "^5.0.1",
|
|
||||||
"rollup-plugin-visualizer": "^5.9.2",
|
"rollup-plugin-visualizer": "^5.9.2",
|
||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.5.0",
|
"vite": "^4.5.0",
|
||||||
"vue-tsc": "^1.8.22"
|
"vue-tsc": "^1.8.22"
|
||||||
|
|
936
pnpm-lock.yaml
936
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,2 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Allow: /
|
|
|
@ -1,96 +0,0 @@
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
|
|
||||||
import Koa from 'koa'
|
|
||||||
import koaConnect from 'koa-connect'
|
|
||||||
|
|
||||||
import { createServer as createViteServer } from 'vite'
|
|
||||||
|
|
||||||
const HOST_NAME = '127.0.0.1'
|
|
||||||
const APP_PORT = 1007
|
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
||||||
|
|
||||||
// Inject teleports in template
|
|
||||||
const injectTeleports = (
|
|
||||||
html: string,
|
|
||||||
teleports: {
|
|
||||||
'#teleported': string
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
// return html
|
|
||||||
return html.replace('<!--teleports-->', teleports['#teleported'])
|
|
||||||
}
|
|
||||||
|
|
||||||
;(async (hmrPort) => {
|
|
||||||
const app = new Koa()
|
|
||||||
|
|
||||||
const vite = await createViteServer({
|
|
||||||
server: {
|
|
||||||
middlewareMode: true,
|
|
||||||
watch: {
|
|
||||||
// During tests we edit the files too fast and sometimes chokidar
|
|
||||||
// misses change events, so enforce polling for consistency
|
|
||||||
usePolling: true,
|
|
||||||
interval: 107,
|
|
||||||
},
|
|
||||||
hmr: {
|
|
||||||
port: hmrPort,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
appType: 'custom',
|
|
||||||
})
|
|
||||||
|
|
||||||
// Parse accept-language
|
|
||||||
const parseAcceptLanguage = (acceptLanguage: string) => {
|
|
||||||
const languages = acceptLanguage.split(',')
|
|
||||||
const language = languages[0]
|
|
||||||
const country = language.split('-')[1]
|
|
||||||
return { language, country }
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(koaConnect(vite.middlewares))
|
|
||||||
|
|
||||||
app.use(async (ctx) => {
|
|
||||||
const { language, country } = parseAcceptLanguage(
|
|
||||||
ctx.request.headers['accept-language'] as string
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
let template = fs.readFileSync(
|
|
||||||
path.resolve(__dirname, 'index.html'),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
|
|
||||||
template = await vite.transformIndexHtml(ctx.path, template)
|
|
||||||
|
|
||||||
const { render } = await vite.ssrLoadModule('/src/entry-server.ts')
|
|
||||||
|
|
||||||
const [renderedHtml, renderedPinia, renderedLinks, renderedTeleports] =
|
|
||||||
await render(
|
|
||||||
ctx,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
language,
|
|
||||||
country,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const html = injectTeleports(template, renderedTeleports)
|
|
||||||
.replace('<!--preload-links-->', renderedLinks)
|
|
||||||
.replace('<!--ssr-outlet-->', renderedHtml)
|
|
||||||
.replace('__pinia', renderedPinia)
|
|
||||||
|
|
||||||
ctx.type = 'text/html'
|
|
||||||
ctx.body = html
|
|
||||||
} catch (e) {
|
|
||||||
vite && vite.ssrFixStacktrace(e as Error)
|
|
||||||
ctx.throw(500, e as Error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(APP_PORT, () => {
|
|
||||||
console.log(`Server is listening on http://${HOST_NAME}:${APP_PORT}`)
|
|
||||||
})
|
|
||||||
})()
|
|
|
@ -1,46 +0,0 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
||||||
|
|
||||||
import Koa from 'koa'
|
|
||||||
import sendFile from 'koa-send'
|
|
||||||
|
|
||||||
import path from 'path'
|
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
|
|
||||||
const HOST_NAME = '127.0.0.1'
|
|
||||||
const APP_PORT = 7777
|
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
||||||
const resolve = (pathName: string) => path.resolve(__dirname, pathName)
|
|
||||||
|
|
||||||
const clientRoot = resolve('dist/client')
|
|
||||||
const template = fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
|
|
||||||
// @ts-ignore
|
|
||||||
import { render } from './dist/server/entry-server.js'
|
|
||||||
// @ts-ignore
|
|
||||||
import manifest from './dist/client/ssr-manifest.json' assert { type: 'json' }
|
|
||||||
;(async () => {
|
|
||||||
const app = new Koa()
|
|
||||||
|
|
||||||
app.use(async (ctx) => {
|
|
||||||
if (ctx.path.startsWith('/kun')) {
|
|
||||||
await sendFile(ctx, ctx.path, { root: clientRoot })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const [renderedHtml, state, preloadLinks] = await render(ctx, manifest)
|
|
||||||
|
|
||||||
const html = template
|
|
||||||
.replace('<!--preload-links-->', preloadLinks)
|
|
||||||
.replace('<!--ssr-outlet-->', renderedHtml)
|
|
||||||
.replace('__pinia', state)
|
|
||||||
|
|
||||||
ctx.type = 'text/html'
|
|
||||||
ctx.body = html
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(APP_PORT, () => {
|
|
||||||
console.log(`Server is listening on http://${HOST_NAME}:${APP_PORT}`)
|
|
||||||
})
|
|
||||||
})()
|
|
|
@ -18,23 +18,21 @@ const handleConfirm = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="#teleported" :disabled="showAlert">
|
<Teleport to="body" :disabled="showAlert">
|
||||||
<Transition name="alert">
|
<Transition name="alert">
|
||||||
<div>
|
<div v-if="showAlert" class="mask">
|
||||||
<div v-if="showAlert" class="mask">
|
<div class="container">
|
||||||
<div class="container">
|
<div class="header">
|
||||||
<div class="header">
|
<h3>{{ $t(`${alertMsg}`) }}</h3>
|
||||||
<h3>{{ $t(`${alertMsg}`) }}</h3>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button v-if="isShowCancel" class="button" @click="handleClose">
|
<button v-if="isShowCancel" class="button" @click="handleClose">
|
||||||
{{ $tm('ComponentAlert.cancel') }}
|
{{ $tm('ComponentAlert.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="button" @click="handleConfirm">
|
<button class="button" @click="handleConfirm">
|
||||||
{{ $tm('ComponentAlert.confirm') }}
|
{{ $tm('ComponentAlert.confirm') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,37 +15,35 @@ const handleClose = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="#teleported" :disabled="showInfo">
|
<Teleport to="body" :disabled="showInfo">
|
||||||
<Transition
|
<Transition
|
||||||
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
||||||
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
||||||
>
|
>
|
||||||
<div>
|
<div class="container" v-if="showInfo">
|
||||||
<div class="container" v-if="showInfo">
|
<Transition
|
||||||
<Transition
|
enter-active-class="animate__animated animate__swing"
|
||||||
enter-active-class="animate__animated animate__swing"
|
appear
|
||||||
appear
|
>
|
||||||
>
|
<div class="lass">
|
||||||
<div class="lass">
|
<span>{{ name }}</span>
|
||||||
<span>{{ name }}</span>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
|
|
||||||
<div class="avatar">
|
|
||||||
<img :src="loli" />
|
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
<Transition
|
<div class="avatar">
|
||||||
enter-active-class="animate__animated animate__bounceInRight animate__faster"
|
<img :src="loli" />
|
||||||
appear
|
</div>
|
||||||
>
|
|
||||||
<!-- A ha ha ha! You probably didn't expect that this was inspired by しゅがてん!-Sugarfull tempering- -->
|
|
||||||
<div class="info">{{ `「 ${$t(`${infoMsg}`)} 」` }}</div>
|
|
||||||
</Transition>
|
|
||||||
|
|
||||||
<div class="close" @click="handleClose">
|
<Transition
|
||||||
<Icon icon="line-md:close" />
|
enter-active-class="animate__animated animate__bounceInRight animate__faster"
|
||||||
</div>
|
appear
|
||||||
|
>
|
||||||
|
<!-- A ha ha ha! You probably didn't expect that this was inspired by しゅがてん!-Sugarfull tempering- -->
|
||||||
|
<div class="info">{{ `「 ${$t(`${infoMsg}`)} 」` }}</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<div class="close" @click="handleClose">
|
||||||
|
<Icon icon="line-md:close" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
|
@ -119,67 +119,65 @@ const handleCloseCapture = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="#teleported" :disabled="isShowCapture">
|
<Teleport to="body" :disabled="isShowCapture">
|
||||||
<Transition name="capture">
|
<Transition name="capture">
|
||||||
<div>
|
<!-- Mask -->
|
||||||
<!-- Mask -->
|
<div
|
||||||
<div
|
class="mask"
|
||||||
class="mask"
|
@keydown="checkKeyPress($event)"
|
||||||
@keydown="checkKeyPress($event)"
|
tabindex="0"
|
||||||
tabindex="0"
|
v-if="isShowCapture"
|
||||||
v-if="isShowCapture"
|
>
|
||||||
>
|
<div class="validate">
|
||||||
<div class="validate">
|
<!-- Title -->
|
||||||
<!-- Title -->
|
<div class="title">
|
||||||
<div class="title">
|
<!-- <span>{{ `❮` }}</span> -->
|
||||||
<!-- <span>{{ `❮` }}</span> -->
|
<h2>{{ $tm('AlertInfo.capture.title') }}</h2>
|
||||||
<h2>{{ $tm('AlertInfo.capture.title') }}</h2>
|
<!-- <span>{{ `❯` }}</span> -->
|
||||||
<!-- <span>{{ `❯` }}</span> -->
|
</div>
|
||||||
</div>
|
<p class="question">{{ currentQuestion.text }}</p>
|
||||||
<p class="question">{{ currentQuestion.text }}</p>
|
|
||||||
|
|
||||||
<!-- Options -->
|
<!-- Options -->
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<label
|
<label
|
||||||
v-for="(option, index) in currentQuestion.options"
|
v-for="(option, index) in currentQuestion.options"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
>
|
||||||
|
<input type="radio" v-model="userAnswers" :value="option" />
|
||||||
|
{{ option }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit buttons -->
|
||||||
|
<div class="btn">
|
||||||
|
<button @click="submitAnswer">
|
||||||
|
{{ $tm('AlertInfo.capture.submit') }}
|
||||||
|
</button>
|
||||||
|
<button @click="handleCloseCapture">
|
||||||
|
{{ $tm('AlertInfo.capture.close') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hints -->
|
||||||
|
<!-- tabindex allows this element to be focused on the page -->
|
||||||
|
<div class="hint-container">
|
||||||
|
<div v-if="isShowHint" class="hint">
|
||||||
|
<div>{{ $tm('AlertInfo.capture.hint1') }}</div>
|
||||||
|
<div>
|
||||||
|
{{ $tm('AlertInfo.capture.hint2') }}
|
||||||
|
<span>kun</span>
|
||||||
|
{{ $tm('AlertInfo.capture.hint3') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isShowAnswer" class="answer">
|
||||||
|
<div>{{ $tm('AlertInfo.capture.hint4') }}</div>
|
||||||
|
<a
|
||||||
|
href="https://github.com/KUN1007/kun-galgame-vue/tree/remove-server/src/components/capture"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<input type="radio" v-model="userAnswers" :value="option" />
|
{{ $tm('AlertInfo.capture.answer') }}
|
||||||
{{ option }}
|
</a>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Submit buttons -->
|
|
||||||
<div class="btn">
|
|
||||||
<button @click="submitAnswer">
|
|
||||||
{{ $tm('AlertInfo.capture.submit') }}
|
|
||||||
</button>
|
|
||||||
<button @click="handleCloseCapture">
|
|
||||||
{{ $tm('AlertInfo.capture.close') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hints -->
|
|
||||||
<!-- tabindex allows this element to be focused on the page -->
|
|
||||||
<div class="hint-container">
|
|
||||||
<div v-if="isShowHint" class="hint">
|
|
||||||
<div>{{ $tm('AlertInfo.capture.hint1') }}</div>
|
|
||||||
<div>
|
|
||||||
{{ $tm('AlertInfo.capture.hint2') }}
|
|
||||||
<span>kun</span>
|
|
||||||
{{ $tm('AlertInfo.capture.hint3') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="isShowAnswer" class="answer">
|
|
||||||
<div>{{ $tm('AlertInfo.capture.hint4') }}</div>
|
|
||||||
<a
|
|
||||||
href="https://github.com/KUN1007/kun-galgame-vue/tree/remove-server/src/components/capture"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{{ $tm('AlertInfo.capture.answer') }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Message from '@/components/alert/Message'
|
||||||
|
|
||||||
import { backgroundImages } from './background'
|
import { backgroundImages } from './background'
|
||||||
import { getBackgroundURL } from '@/hooks/useBackgroundPicture'
|
import { getBackgroundURL } from '@/hooks/useBackgroundPicture'
|
||||||
|
import { restoreBackground } from '@/hooks/useBackgroundPicture'
|
||||||
|
|
||||||
const imageArray = ref<string[]>([])
|
const imageArray = ref<string[]>([])
|
||||||
// Use the settings panel store
|
// Use the settings panel store
|
||||||
|
@ -27,11 +28,6 @@ const handleChangeImage = (index: number) => {
|
||||||
showKUNGalgameBackground.value = `bg${index}`
|
showKUNGalgameBackground.value = `bg${index}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore the blank background
|
|
||||||
const restoreBackground = () => {
|
|
||||||
showKUNGalgameBackground.value = 'bg0'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom background
|
// Custom background
|
||||||
const url = ref('')
|
const url = ref('')
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineAsyncComponent, onMounted, ref } from 'vue'
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import 'animate.css'
|
import 'animate.css'
|
||||||
import { topBarItem } from './topBarItem'
|
import { topBarItem } from './topBarItem'
|
||||||
|
@ -25,8 +25,6 @@ const showKUNGalgamePanel = ref(false)
|
||||||
const showKUNGalgameHamburger = ref(false)
|
const showKUNGalgameHamburger = ref(false)
|
||||||
// Show user panel when clicking on the user's avatar
|
// Show user panel when clicking on the user's avatar
|
||||||
const showKUNGalgameUserPanel = ref(false)
|
const showKUNGalgameUserPanel = ref(false)
|
||||||
// User avatar image link, ssr friendly
|
|
||||||
const userAvatar = ref('')
|
|
||||||
|
|
||||||
// Set the navigation bar width based on the number of navigation items
|
// Set the navigation bar width based on the number of navigation items
|
||||||
const navItemNum = topBarItem.length
|
const navItemNum = topBarItem.length
|
||||||
|
@ -37,102 +35,90 @@ onBeforeRouteLeave(() => {
|
||||||
showKUNGalgamePanel.value = false
|
showKUNGalgamePanel.value = false
|
||||||
showKUNGalgameHamburger.value = false
|
showKUNGalgameHamburger.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
userAvatar.value = avatarMin.value
|
|
||||||
? avatarMin.value
|
|
||||||
: '/src/assets/images/favicon.webp'
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="header">
|
||||||
<div class="header">
|
<!-- Top left interactive bar -->
|
||||||
<!-- Top left interactive bar -->
|
<div class="nav-top">
|
||||||
<div class="nav-top">
|
<div class="hamburger">
|
||||||
<div class="hamburger">
|
<Icon
|
||||||
<Icon
|
icon="line-md:menu-fold-right"
|
||||||
icon="line-md:menu-fold-right"
|
v-if="!showKUNGalgameHamburger"
|
||||||
v-if="!showKUNGalgameHamburger"
|
@click="showKUNGalgameHamburger = !showKUNGalgameHamburger"
|
||||||
@click="showKUNGalgameHamburger = !showKUNGalgameHamburger"
|
|
||||||
/>
|
|
||||||
<Transition name="hamburger">
|
|
||||||
<Hamburger
|
|
||||||
v-if="showKUNGalgameHamburger"
|
|
||||||
@showKUNGalgameHamburger="showKUNGalgameHamburger = false"
|
|
||||||
/>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Website name and logo -->
|
|
||||||
<div class="kungalgame">
|
|
||||||
<RouterLink to="/kun">
|
|
||||||
<img
|
|
||||||
src="@/assets/images/favicon.webp"
|
|
||||||
alt="KUN Visual Novel 鲲 Galgame"
|
|
||||||
/>
|
|
||||||
<span>{{ $tm('header.name') }}</span>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation bar -->
|
|
||||||
<div class="top-bar">
|
|
||||||
<!-- Top individual sections -->
|
|
||||||
<span v-for="kun in topBarItem" :key="kun.index">
|
|
||||||
<RouterLink :to="{ path: kun.router }">
|
|
||||||
{{ $tm(`header['${kun.name}']`) }}
|
|
||||||
</RouterLink>
|
|
||||||
</span>
|
|
||||||
<!-- Hover effect under the top section -->
|
|
||||||
<div class="box"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="kungalgamer-info">
|
|
||||||
<!-- showKUNGalgamePanel is a boolean value in the store, true/false controls the display and close of the settings panel -->
|
|
||||||
<span
|
|
||||||
class="settings"
|
|
||||||
@click="showKUNGalgamePanel = !showKUNGalgamePanel"
|
|
||||||
>
|
|
||||||
<Icon icon="uiw:setting-o" />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="avatar">
|
|
||||||
<img
|
|
||||||
@click="showKUNGalgameUserPanel = true"
|
|
||||||
:src="userAvatar"
|
|
||||||
:alt="name"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- For SSR -->
|
|
||||||
<span
|
|
||||||
@click="showKUNGalgameUserPanel = true"
|
|
||||||
v-if="userAvatar === '/src/assets/images/favicon.webp'"
|
|
||||||
>
|
|
||||||
{{ name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<KUNGalgameUserInfo
|
|
||||||
v-if="showKUNGalgameUserPanel"
|
|
||||||
@close="showKUNGalgameUserPanel = false"
|
|
||||||
/>
|
/>
|
||||||
|
<Transition name="hamburger">
|
||||||
|
<Hamburger
|
||||||
|
v-if="showKUNGalgameHamburger"
|
||||||
|
@showKUNGalgameHamburger="showKUNGalgameHamburger = false"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Website name and logo -->
|
||||||
|
<div class="kungalgame">
|
||||||
|
<RouterLink to="/kun">
|
||||||
|
<img
|
||||||
|
src="@/assets/images/favicon.webp"
|
||||||
|
alt="KUN Visual Novel 鲲 Galgame"
|
||||||
|
/>
|
||||||
|
<span>{{ $tm('header.name') }}</span>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation bar -->
|
||||||
|
<div class="top-bar">
|
||||||
|
<!-- Top individual sections -->
|
||||||
|
<span v-for="kun in topBarItem" :key="kun.index">
|
||||||
|
<RouterLink :to="{ path: kun.router }">
|
||||||
|
{{ $tm(`header['${kun.name}']`) }}
|
||||||
|
</RouterLink>
|
||||||
|
</span>
|
||||||
|
<!-- Hover effect under the top section -->
|
||||||
|
<div class="box"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-panel">
|
<div class="kungalgamer-info">
|
||||||
<transition
|
<!-- showKUNGalgamePanel is a boolean value in the store, true/false controls the display and close of the settings panel -->
|
||||||
enter-active-class="animate__animated animate__jackInTheBox animate__faster"
|
<span
|
||||||
leave-active-class="animate__animated animate__fadeOutRight animate__faster"
|
class="settings"
|
||||||
|
@click="showKUNGalgamePanel = !showKUNGalgamePanel"
|
||||||
>
|
>
|
||||||
<KeepAlive :exclude="['PageWidth', 'Font']">
|
<Icon icon="uiw:setting-o" />
|
||||||
<KUNGalgameSettingsPanel
|
</span>
|
||||||
v-if="showKUNGalgamePanel"
|
|
||||||
@close="showKUNGalgamePanel = false"
|
<div class="avatar">
|
||||||
/>
|
<img
|
||||||
</KeepAlive>
|
v-if="avatarMin"
|
||||||
</transition>
|
@click="showKUNGalgameUserPanel = true"
|
||||||
|
:src="avatarMin"
|
||||||
|
alt="KUN"
|
||||||
|
/>
|
||||||
|
<span @click="showKUNGalgameUserPanel = true" v-if="!avatarMin">
|
||||||
|
{{ name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<KUNGalgameUserInfo
|
||||||
|
v-if="showKUNGalgameUserPanel"
|
||||||
|
@close="showKUNGalgameUserPanel = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-panel">
|
||||||
|
<transition
|
||||||
|
enter-active-class="animate__animated animate__jackInTheBox animate__faster"
|
||||||
|
leave-active-class="animate__animated animate__fadeOutRight animate__faster"
|
||||||
|
>
|
||||||
|
<KeepAlive :exclude="['PageWidth', 'Font']">
|
||||||
|
<KUNGalgameSettingsPanel
|
||||||
|
v-if="showKUNGalgamePanel"
|
||||||
|
@close="showKUNGalgamePanel = false"
|
||||||
|
/>
|
||||||
|
</KeepAlive>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
15
src/directives/index.ts
Normal file
15
src/directives/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Currently, all directives are not enabled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { type App } from 'vue'
|
||||||
|
// Directive for enlarging images on click
|
||||||
|
import { zoom } from './zoom/zoom'
|
||||||
|
// Permission directive
|
||||||
|
import { permission } from './permission/permission'
|
||||||
|
|
||||||
|
// Mount directives
|
||||||
|
export function setupKUNGalgameDirectives(app: App) {
|
||||||
|
app.directive('zoom', zoom)
|
||||||
|
app.directive('permission', permission)
|
||||||
|
}
|
62
src/directives/permission/permission.ts
Normal file
62
src/directives/permission/permission.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import type { Directive, DirectiveBinding } from 'vue'
|
||||||
|
import Message from '@/components/alert/Message'
|
||||||
|
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
const { roles, uid } = storeToRefs(useKUNGalgameUserStore())
|
||||||
|
|
||||||
|
// User roles: Guest 0, User 1, Admin 2, SuperAdmin 3, User Self 4
|
||||||
|
enum UserRole {
|
||||||
|
Guest = 0,
|
||||||
|
User = 1,
|
||||||
|
Admin = 2,
|
||||||
|
SuperAdmin = 3,
|
||||||
|
Self = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @roles: User types that can access this resource.
|
||||||
|
* @uid: User ID that needs authorization.
|
||||||
|
*/
|
||||||
|
interface BindingProps {
|
||||||
|
roles: UserRole[]
|
||||||
|
uid?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUnauthorizedAccess = (element: HTMLElement) => {
|
||||||
|
element.parentNode?.removeChild(element)
|
||||||
|
|
||||||
|
Message(
|
||||||
|
'You do not have sufficient permissions!',
|
||||||
|
'您没有足够的权限!',
|
||||||
|
'error',
|
||||||
|
5000
|
||||||
|
)
|
||||||
|
router.push('/kungalgame403')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const permission: Directive = {
|
||||||
|
mounted(element: HTMLElement, binding: DirectiveBinding<BindingProps>) {
|
||||||
|
const bindingRoles = [...binding.value.roles]
|
||||||
|
const bindingUid = binding.value.uid
|
||||||
|
|
||||||
|
const hasPermission = () => {
|
||||||
|
// User Self
|
||||||
|
if (bindingUid === uid.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// User has access permission
|
||||||
|
if (bindingRoles.includes(roles.value)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasPermission()) {
|
||||||
|
handleUnauthorizedAccess(element)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
20
src/directives/zoom/zoom.ts
Normal file
20
src/directives/zoom/zoom.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import type { Directive, DirectiveBinding } from 'vue'
|
||||||
|
|
||||||
|
/* This plugin is not enabled */
|
||||||
|
|
||||||
|
// Enlarge images in nodes rendered by v-html
|
||||||
|
export const zoom: Directive = {
|
||||||
|
// The binding refers to values passed to the directive; there are none here
|
||||||
|
mounted(element: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
const { scale, time } = binding.value || { scale: 1.2, time: 0.3 }
|
||||||
|
|
||||||
|
element.querySelectorAll('img').forEach((img: HTMLImageElement) => {
|
||||||
|
img.addEventListener('click', () => {
|
||||||
|
img.style.transition = `transform ${time}s ease`
|
||||||
|
img.style.transform = img.classList.contains('zoomed')
|
||||||
|
? 'scale(1)'
|
||||||
|
: `scale(${scale})`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
import { createApp } from './main'
|
|
||||||
import { createKUNGalgameRouter } from './router'
|
|
||||||
import { setupPinia } from './store'
|
|
||||||
import createI18n from '@/language/i18n'
|
|
||||||
import '@/styles/index.scss'
|
|
||||||
|
|
||||||
const router = createKUNGalgameRouter()
|
|
||||||
const pinia = setupPinia()
|
|
||||||
|
|
||||||
const { app } = createApp()
|
|
||||||
|
|
||||||
export const i18n = createI18n()
|
|
||||||
|
|
||||||
app.use(router).use(pinia).use(i18n)
|
|
||||||
|
|
||||||
if (window.__pinia) {
|
|
||||||
pinia.state.value = JSON.parse(JSON.stringify(window.__pinia))
|
|
||||||
}
|
|
||||||
|
|
||||||
router.isReady().then(() => {
|
|
||||||
app.mount('#app', true)
|
|
||||||
})
|
|
|
@ -1,84 +0,0 @@
|
||||||
import { createApp } from './main'
|
|
||||||
|
|
||||||
import { createKUNGalgameRouter } from './router'
|
|
||||||
import { setupPinia } from './store'
|
|
||||||
import createI18n from '@/language/i18n'
|
|
||||||
|
|
||||||
import { renderToString } from '@vue/server-renderer'
|
|
||||||
|
|
||||||
import type { ParameterizedContext } from 'koa'
|
|
||||||
|
|
||||||
import '@/styles/index.scss'
|
|
||||||
|
|
||||||
const renderPreloadLink = (file: string) => {
|
|
||||||
if (file.endsWith('.js')) {
|
|
||||||
return `<link rel="modulepreload" crossorigin href="${file}">`
|
|
||||||
} else if (file.endsWith('.css')) {
|
|
||||||
return `<link rel="stylesheet" href="${file}">`
|
|
||||||
} else if (file.endsWith('.png')) {
|
|
||||||
return ` <link rel="preload" href="${file}" as="image" type="image/png">`
|
|
||||||
} else {
|
|
||||||
// TODO
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderPreloadLinks = (
|
|
||||||
modules: undefined | string[],
|
|
||||||
manifest: Record<string, string[]>
|
|
||||||
) => {
|
|
||||||
let links = ''
|
|
||||||
const seen = new Set()
|
|
||||||
if (modules === undefined) throw new Error()
|
|
||||||
modules.forEach((id) => {
|
|
||||||
const files = manifest[id]
|
|
||||||
if (files) {
|
|
||||||
files.forEach((file) => {
|
|
||||||
if (!seen.has(file)) {
|
|
||||||
seen.add(file)
|
|
||||||
links += renderPreloadLink(file)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
export const render = async (
|
|
||||||
ctx: ParameterizedContext,
|
|
||||||
manifest: Record<string, string[]>,
|
|
||||||
options: { language: string; country: string }
|
|
||||||
) => {
|
|
||||||
const { app } = createApp()
|
|
||||||
const { language, country } = options
|
|
||||||
|
|
||||||
// router
|
|
||||||
const router = createKUNGalgameRouter()
|
|
||||||
app.use(router)
|
|
||||||
await router.push(ctx.path)
|
|
||||||
await router.isReady()
|
|
||||||
|
|
||||||
// pinia
|
|
||||||
const pinia = setupPinia()
|
|
||||||
app.use(pinia)
|
|
||||||
|
|
||||||
const renderedPinia = JSON.stringify(pinia.state.value)
|
|
||||||
|
|
||||||
// i18n
|
|
||||||
app.use(createI18n(language.includes('zh') ? 'zh' : 'en'))
|
|
||||||
|
|
||||||
const renderCtx: Record<string, string[] | {}> = {}
|
|
||||||
|
|
||||||
const renderedHtml = await renderToString(app, renderCtx)
|
|
||||||
|
|
||||||
const renderedLinks = renderPreloadLinks(
|
|
||||||
renderCtx.modules as string[],
|
|
||||||
manifest
|
|
||||||
)
|
|
||||||
|
|
||||||
const renderedTeleports = renderCtx.teleports as {
|
|
||||||
'#teleported': string
|
|
||||||
}
|
|
||||||
|
|
||||||
return [renderedHtml, renderedPinia, renderedLinks, renderedTeleports]
|
|
||||||
}
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
// Global message component (top)
|
// Global message component (top)
|
||||||
import Message from '@/components/alert/Message'
|
import Message from '@/components/alert/Message'
|
||||||
import { generateTokenByRefreshTokenApi } from '@/api'
|
import { generateTokenByRefreshTokenApi } from '@/api'
|
||||||
// Use the user store
|
// Use the user store
|
||||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||||
// Import the router
|
// Import the router
|
||||||
import { createKUNGalgameRouter } from '@/router'
|
import router from '@/router'
|
||||||
// Import known error handling functions
|
// Import known error handling functions
|
||||||
import { kungalgameErrorHandler } from './errorHandler'
|
import { kungalgameErrorHandler } from './errorHandler'
|
||||||
|
|
||||||
|
@ -29,9 +28,7 @@ export async function onRequestError(response: Response) {
|
||||||
if (accessTokenResponse.code === 200 && accessTokenResponse.data.token) {
|
if (accessTokenResponse.code === 200 && accessTokenResponse.data.token) {
|
||||||
useKUNGalgameUserStore().setToken(accessTokenResponse.data.token)
|
useKUNGalgameUserStore().setToken(accessTokenResponse.data.token)
|
||||||
// Set the page to reload with the new token applied
|
// Set the page to reload with the new token applied
|
||||||
if (typeof window !== 'undefined') {
|
location.reload()
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, prompt the user to log in again
|
// Otherwise, prompt the user to log in again
|
||||||
Message(
|
Message(
|
||||||
|
@ -40,7 +37,7 @@ export async function onRequestError(response: Response) {
|
||||||
'error'
|
'error'
|
||||||
)
|
)
|
||||||
useKUNGalgameUserStore().removeToken()
|
useKUNGalgameUserStore().removeToken()
|
||||||
useRouter().push('/login')
|
router.push('/login')
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
// Import the settings panel store
|
// Import the settings panel store
|
||||||
|
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||||
import { saveImage, getImage } from './useLocalforage'
|
import { saveImage, getImage } from './useLocalforage'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
|
// Use the settings panel store
|
||||||
|
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
|
||||||
|
storeToRefs(useKUNGalgameSettingsStore())
|
||||||
|
|
||||||
// Fetch background image data from the backend
|
// Fetch background image data from the backend
|
||||||
const fetchGetBackground = async (imageName: string): Promise<Blob> => {
|
const fetchGetBackground = async (imageName: string): Promise<Blob> => {
|
||||||
|
@ -33,4 +39,25 @@ const getBackgroundURL = async (imageName: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getBackgroundURL }
|
// The image names here are defined by the backend as bg1.webp (large image) and bg1-m.webp (preview image)
|
||||||
|
const getCurrentBackground = async () => {
|
||||||
|
if (
|
||||||
|
showKUNGalgameBackground.value === 'bg0' ||
|
||||||
|
showKUNGalgameBackground.value === 'none'
|
||||||
|
) {
|
||||||
|
return 'none'
|
||||||
|
}
|
||||||
|
if (showKUNGalgameBackground.value === 'bg1007') {
|
||||||
|
return `${showKUNGalgameCustomBackground.value}`
|
||||||
|
}
|
||||||
|
// Get the image's blob URL
|
||||||
|
const url = await getBackgroundURL(showKUNGalgameBackground.value)
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the blank background
|
||||||
|
const restoreBackground = () => {
|
||||||
|
showKUNGalgameBackground.value = 'bg0'
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getBackgroundURL, getCurrentBackground, restoreBackground }
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import { createI18n as _createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
// Get localeStorage language config
|
// 读取本地存储中的语言配置
|
||||||
import { KUNGalgameLanguage } from '@/utils/getDefaultEnv'
|
import { KUNGalgameLanguage } from '@/utils/getDefaultEnv'
|
||||||
|
|
||||||
// Import language file
|
// 引入语言文件
|
||||||
import zh from './zh'
|
import zh from './zh'
|
||||||
import en from './en'
|
import en from './en'
|
||||||
|
|
||||||
const createI18n = (language?: string) =>
|
const i18n = createI18n({
|
||||||
_createI18n({
|
locale: KUNGalgameLanguage,
|
||||||
locale: language || KUNGalgameLanguage,
|
// 支持 Vue3 composition API
|
||||||
legacy: false,
|
legacy: false,
|
||||||
messages: {
|
// 全局注册 t 方法
|
||||||
zh,
|
globalInjection: true,
|
||||||
en,
|
messages: {
|
||||||
},
|
zh,
|
||||||
})
|
en,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export default createI18n
|
export default i18n
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { onMounted, watch, ref } from 'vue'
|
import { onMounted, watch, ref } from 'vue'
|
||||||
// Import animations
|
// Import animations
|
||||||
import 'animate.css'
|
import 'animate.css'
|
||||||
import { getBackgroundURL } from '@/hooks/useBackgroundPicture'
|
import { getCurrentBackground } 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 { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||||
|
@ -10,20 +10,6 @@ import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
|
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
|
||||||
storeToRefs(useKUNGalgameSettingsStore())
|
storeToRefs(useKUNGalgameSettingsStore())
|
||||||
const getCurrentBackground = async () => {
|
|
||||||
if (
|
|
||||||
showKUNGalgameBackground.value === 'bg0' ||
|
|
||||||
showKUNGalgameBackground.value === 'none'
|
|
||||||
) {
|
|
||||||
return 'none'
|
|
||||||
}
|
|
||||||
if (showKUNGalgameBackground.value === 'bg1007') {
|
|
||||||
return `${showKUNGalgameCustomBackground.value}`
|
|
||||||
}
|
|
||||||
// Get the image's blob URL
|
|
||||||
const url = await getBackgroundURL(showKUNGalgameBackground.value)
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageURL = ref('')
|
const imageURL = ref('')
|
||||||
|
|
||||||
|
|
25
src/main.ts
25
src/main.ts
|
@ -1,7 +1,22 @@
|
||||||
import { createSSRApp } from 'vue'
|
// Vue core
|
||||||
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
// Import vue i18n
|
||||||
|
import i18n from '@/language/i18n'
|
||||||
|
|
||||||
export const createApp = () => {
|
import { setupRouterGuard } from '@/router/guard'
|
||||||
const app = createSSRApp(App)
|
import { setupPinia } from '@/store/index'
|
||||||
return { app }
|
|
||||||
}
|
// Import css styles, color, theme, etc.
|
||||||
|
import '@/styles/index.scss'
|
||||||
|
|
||||||
|
// Get vue App instance
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
// Setup router guard
|
||||||
|
setupRouterGuard(router)
|
||||||
|
// Setup pinia
|
||||||
|
setupPinia(app)
|
||||||
|
|
||||||
|
app.use(router).use(i18n).mount('#app')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Router } from 'vue-router'
|
import { Router } from 'vue-router'
|
||||||
import { createPermission } from './permission'
|
import { createPermission } from './permission'
|
||||||
import { i18n } from '@/entry-client'
|
import i18n from '@/language/i18n'
|
||||||
|
|
||||||
const createPageTitle = (router: Router) => {
|
const createPageTitle = (router: Router) => {
|
||||||
router.beforeEach((to) => {
|
router.beforeEach((to) => {
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
import type { RouteRecordRaw, Router } from 'vue-router'
|
import { type RouteRecordRaw, createWebHistory, createRouter } from 'vue-router'
|
||||||
import { createWebHistory, createRouter, createMemoryHistory } from 'vue-router'
|
|
||||||
import { constantRoutes } from './router'
|
import { constantRoutes } from './router'
|
||||||
import { asyncRoutes } from './router'
|
import { asyncRoutes } from './router'
|
||||||
|
|
||||||
export const createKUNGalgameRouter = (): Router =>
|
// Create a Vue Router instance
|
||||||
createRouter({
|
const router = createRouter({
|
||||||
history: import.meta.env.SSR
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
? createMemoryHistory(import.meta.env.BASE_URL)
|
routes: [...constantRoutes, ...asyncRoutes] as RouteRecordRaw[],
|
||||||
: createWebHistory(import.meta.env.BASE_URL),
|
// Scroll to the top of the page with a smooth animation on each page navigation
|
||||||
|
scrollBehavior(to, from, savedPosition) {
|
||||||
|
if (savedPosition) {
|
||||||
|
return savedPosition
|
||||||
|
} else {
|
||||||
|
return { top: 0, behavior: 'smooth' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
routes: [...constantRoutes, ...asyncRoutes] as RouteRecordRaw[],
|
export default router
|
||||||
|
|
||||||
// Scroll to the top of the page with a smooth animation on each page navigation
|
|
||||||
scrollBehavior(to, from, savedPosition) {
|
|
||||||
if (savedPosition) {
|
|
||||||
return savedPosition
|
|
||||||
} else {
|
|
||||||
return { top: 0, behavior: 'smooth' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
import type { App } from 'vue'
|
||||||
|
|
||||||
// Import store for the income and expense public disclosure page
|
// Import store for the income and expense public disclosure page
|
||||||
import { useKUNGalgameBalanceStore } from './modules/balance'
|
import { useKUNGalgameBalanceStore } from './modules/balance'
|
||||||
|
@ -32,19 +33,16 @@ import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||||
// Import store for the topic detail page
|
// Import store for the topic detail page
|
||||||
import { useKUNGalgameTopicStore } from './modules/topic'
|
import { useKUNGalgameTopicStore } from './modules/topic'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const store = createPinia()
|
||||||
|
|
||||||
// Function to set up Pinia, to be called in main.ts
|
// Function to set up Pinia, to be called in main.ts
|
||||||
export const setupPinia = () => {
|
export function setupPinia(app: App<Element>) {
|
||||||
if (!import.meta.env.SSR) {
|
store.use(piniaPluginPersistedstate)
|
||||||
pinia.use(piniaPluginPersistedstate)
|
app.use(store)
|
||||||
}
|
|
||||||
|
|
||||||
return pinia
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all stores, used for logging out
|
// Reset all stores, used for logging out
|
||||||
export const kungalgameStoreReset = () => {
|
export function kungalgameStoreReset() {
|
||||||
const balanceStore = useKUNGalgameBalanceStore()
|
const balanceStore = useKUNGalgameBalanceStore()
|
||||||
const editStore = useKUNGalgameEditStore()
|
const editStore = useKUNGalgameEditStore()
|
||||||
const homeStore = useKUNGalgameHomeStore()
|
const homeStore = useKUNGalgameHomeStore()
|
||||||
|
@ -65,3 +63,5 @@ export const kungalgameStoreReset = () => {
|
||||||
settingsStore.$reset()
|
settingsStore.$reset()
|
||||||
topicStore.$reset()
|
topicStore.$reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { store }
|
||||||
|
|
3
src/types/shims-global.d.ts
vendored
3
src/types/shims-global.d.ts
vendored
|
@ -1,3 +0,0 @@
|
||||||
interface Window {
|
|
||||||
__pinia: string
|
|
||||||
}
|
|
|
@ -3,19 +3,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Read language configuration from local storage
|
// Read language configuration from local storage
|
||||||
let localStorageString = ''
|
const localStorageString = localStorage.getItem('KUNGalgameSettings')
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const storage = localStorage.getItem('KUNGalgameSettings')
|
|
||||||
localStorageString = storage ? storage : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// To ensure compatibility with various browsers, some browsers have 'zh-CN' as navigator.language, which may cause errors.
|
// To ensure compatibility with various browsers, some browsers have 'zh-CN' as navigator.language, which may cause errors.
|
||||||
const getInitLanguage = () => {
|
const getInitLanguage = () => {
|
||||||
let userLanguage = ''
|
const userLanguage = navigator.language
|
||||||
|
|
||||||
if (typeof navigator !== 'undefined') {
|
|
||||||
userLanguage = navigator.language
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userLanguage.includes('en')) {
|
if (userLanguage.includes('en')) {
|
||||||
return 'en'
|
return 'en'
|
||||||
|
@ -32,11 +24,6 @@ export const KUNGalgameLanguage = localStorageString
|
||||||
: getInitLanguage()
|
: getInitLanguage()
|
||||||
|
|
||||||
// Read local day-night mode, this function will return 'true' if it's in dark mode.
|
// Read local day-night mode, this function will return 'true' if it's in dark mode.
|
||||||
export const mode = () => {
|
export const mode = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
if (typeof window !== 'undefined') {
|
? 'dark'
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
: ''
|
||||||
? 'dark'
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||||
|
|
||||||
const successResponseArray = [200, 201, 202, 204, 205, 206]
|
const successResponseArray = [200, 201, 202, 204, 205, 206]
|
||||||
|
|
||||||
export type FetchOptions = RequestInit & {
|
export type FetchOptions = {
|
||||||
method: HttpMethod
|
method: HttpMethod
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
headers?: Record<string, string>
|
headers?: Record<string, string>
|
||||||
|
|
|
@ -38,7 +38,7 @@ const mainPageWidth = computed(() => {
|
||||||
|
|
||||||
.content-container {
|
.content-container {
|
||||||
width: v-bind(mainPageWidth);
|
width: v-bind(mainPageWidth);
|
||||||
transition: width 0.2s;
|
transition: all 0.2s;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -48,6 +48,7 @@ const getRepliesCount = computed(() => {
|
||||||
<span>{{ title }}</span>
|
<span>{{ title }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Topic status, likes, etc. -->
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
|
@ -63,6 +64,7 @@ const getRepliesCount = computed(() => {
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<!-- Publication date of the topic -->
|
||||||
<div class="time">
|
<div class="time">
|
||||||
<span>
|
<span>
|
||||||
{{
|
{{
|
||||||
|
@ -76,6 +78,7 @@ const getRepliesCount = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Preview introduction of the topic -->
|
||||||
<div class="introduction">
|
<div class="introduction">
|
||||||
<p>
|
<p>
|
||||||
{{ plainText }}
|
{{ plainText }}
|
||||||
|
|
|
@ -47,13 +47,14 @@ const handleClosePanel = async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="#teleported" :disabled="isEdit">
|
<Teleport to="body" :disabled="isEdit">
|
||||||
<Transition
|
<Transition
|
||||||
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
||||||
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
||||||
>
|
>
|
||||||
<div class="root" v-if="isEdit">
|
<div class="root" v-if="isEdit">
|
||||||
<div class="container" :style="`width: ${panelWidth}`">
|
<div class="container" :style="`width: ${panelWidth}`">
|
||||||
|
<!-- Reply panel - reply to whom -->
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h3>
|
<h3>
|
||||||
<span>{{ $tm('topic.panel.to') + ' @' }}</span>
|
<span>{{ $tm('topic.panel.to') + ' @' }}</span>
|
||||||
|
@ -70,10 +71,12 @@ const handleClosePanel = async () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Reply editor -->
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<MilkdownEditor :is-show-menu="isShowAdvance" />
|
<MilkdownEditor :is-show-menu="isShowAdvance" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Reply footer -->
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<Tags
|
<Tags
|
||||||
style="margin-top: 10px; padding: 10px"
|
style="margin-top: 10px; padding: 10px"
|
||||||
|
|
|
@ -24,9 +24,7 @@
|
||||||
"src/**/*.d.ts",
|
"src/**/*.d.ts",
|
||||||
"src/**/**/*.d.ts",
|
"src/**/**/*.d.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"src/**/*.vue",
|
"src/**/*.vue"
|
||||||
"server-dev.ts",
|
|
||||||
"server-prod.ts"
|
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
|
|
@ -5,31 +5,26 @@ import vue from '@vitejs/plugin-vue'
|
||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ mode, ssrBuild, command }) => {
|
export default defineConfig({
|
||||||
return {
|
plugins: [vue(), visualizer() as PluginOption],
|
||||||
plugins: [vue(), visualizer() as PluginOption],
|
/* Set the 'src' alias to '@' */
|
||||||
/* Set the 'src' alias to '@' */
|
resolve: {
|
||||||
resolve: {
|
alias: {
|
||||||
alias: {
|
'@': path.resolve(__dirname, './src'),
|
||||||
'@': path.resolve(__dirname, './src'),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
esbuild:
|
},
|
||||||
command === 'serve'
|
esbuild: {
|
||||||
? {}
|
drop: ['console', 'debugger'],
|
||||||
: {
|
},
|
||||||
drop: ['console', 'debugger'],
|
build: {
|
||||||
},
|
// Dist dir name
|
||||||
build: {
|
assetsDir: 'kun',
|
||||||
// Dist dir name
|
},
|
||||||
assetsDir: 'kun',
|
server: { host: '127.0.0.1', port: 1007 },
|
||||||
},
|
// Suppress i18n warnings
|
||||||
server: { host: '127.0.0.1', port: 1007 },
|
define: {
|
||||||
// Suppress i18n warnings
|
__VUE_I18N_FULL_INSTALL__: true,
|
||||||
define: {
|
__VUE_I18N_LEGACY_API__: false,
|
||||||
__VUE_I18N_FULL_INSTALL__: true,
|
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||||
__VUE_I18N_LEGACY_API__: false,
|
},
|
||||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue