old
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||||
17
index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>5大潜能测试</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- 公众号 JSSDK -->
|
||||||
|
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
|
||||||
|
<!-- 云开发 Web SDK -->
|
||||||
|
<script src="https://res.wx.qq.com/open/js/cloudbase/1.1.0/cloud.js"></script>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1797
package-lock.json
generated
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "loreal-survey",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc -b && vite build --emptyOutDir",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^22.10.1",
|
||||||
|
"dedent": "^1.5.3",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-i18n": "^10.0.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"sass-embedded": "^1.82.0",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"vite": "^6.0.1",
|
||||||
|
"vue-tsc": "^2.1.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
164
src/App.vue
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Home from './views/Home.vue';
|
||||||
|
import imgs from './assets/imgs'
|
||||||
|
|
||||||
|
|
||||||
|
import { DimensionColors, DimensionName, findAllTraitByDimension, getRandomDimensionRating, getRandomTraitRating, TraitName } from './config';
|
||||||
|
import Game from './views/Game.vue';
|
||||||
|
import { computed, Ref, ref } from 'vue';
|
||||||
|
import Result from './views/Result.vue';
|
||||||
|
|
||||||
|
const pIndex = ref(0)
|
||||||
|
|
||||||
|
const result: Ref<Record<DimensionName, number>> = ref(getRandomDimensionRating())
|
||||||
|
const trait: Ref<Record<TraitName, number>> = ref(getRandomTraitRating())
|
||||||
|
|
||||||
|
function resetGame() {
|
||||||
|
result.value = getRandomDimensionRating()
|
||||||
|
trait.value = getRandomTraitRating()
|
||||||
|
pIndex.value = 1
|
||||||
|
rarity.value = Math.floor(Math.random() * 11) + 80
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipGame() {
|
||||||
|
pIndex.value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishGame(r: Record<DimensionName, number>, t: Record<TraitName, number>) {
|
||||||
|
pIndex.value = 2
|
||||||
|
result.value = r
|
||||||
|
trait.value = t
|
||||||
|
}
|
||||||
|
|
||||||
|
import lang from './locates'
|
||||||
|
import Report from './views/Report.vue';
|
||||||
|
import PersonalInfo from './views/PersonalInfo.vue';
|
||||||
|
import ReportMask from './views/ReportMask.vue';
|
||||||
|
|
||||||
|
const GraphDimensions = computed(() => {
|
||||||
|
const average = Object.values(result.value).reduce((acc, cur) => acc + cur, 0) / 5;
|
||||||
|
const p = 1.1
|
||||||
|
const sign = (num: number) => num > 0 ? 1 : -1
|
||||||
|
|
||||||
|
const newScores = Object.entries(result.value).map(([dimName, score]) => {
|
||||||
|
const mean = average
|
||||||
|
const newScore = Math.max(8, score + sign(score - mean) * Math.abs(score - mean) ** p)
|
||||||
|
return {
|
||||||
|
name_zh: lang.zh_CN.questionData.DimensionName[dimName as DimensionName],
|
||||||
|
name_en: lang.en.questionData.DimensionName[dimName as DimensionName],
|
||||||
|
value: newScore,
|
||||||
|
color: DimensionColors[dimName as DimensionName],
|
||||||
|
iconUrl: imgs.icons[dimName as DimensionName],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newScores
|
||||||
|
})
|
||||||
|
|
||||||
|
//Random from 80 to 99
|
||||||
|
const rarity: Ref<number> = ref(Math.floor(Math.random() * 19) + 80)
|
||||||
|
|
||||||
|
const topFiveTraits = computed(() => {
|
||||||
|
const traitsSet = new Set<TraitName>();
|
||||||
|
|
||||||
|
const dimensions = Object.entries(result.value) as [DimensionName, number][];
|
||||||
|
const sortedDimensions = dimensions.sort((a, b) => b[1] - a[1]);
|
||||||
|
|
||||||
|
let targetScore = 5;
|
||||||
|
while (targetScore > 0 && traitsSet.size < 5) {
|
||||||
|
for (const [dimName] of sortedDimensions) {
|
||||||
|
const dimTraits = findAllTraitByDimension(dimName);
|
||||||
|
|
||||||
|
dimTraits.forEach(traitName => {
|
||||||
|
if (trait.value[traitName] === targetScore) {
|
||||||
|
traitsSet.add(traitName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (traitsSet.size >= 5) break;
|
||||||
|
}
|
||||||
|
targetScore--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(traitsSet).slice(0, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const gender = ref('_gender_')
|
||||||
|
const username = ref('_username_')
|
||||||
|
const age = ref(0)
|
||||||
|
|
||||||
|
function submitUserInfo(data: { gender: string, username: string, age: number }) {
|
||||||
|
gender.value = data.gender
|
||||||
|
username.value = data.username
|
||||||
|
age.value = data.age
|
||||||
|
pIndex.value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
const showDebug = ref(1)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main">
|
||||||
|
<div class="debug" v-if="showDebug > 0 ">
|
||||||
|
<button v-for="i in [0, 1, 2, 3, 4,5]" @click="pIndex = i">qIndex = {{ i
|
||||||
|
}}</button>
|
||||||
|
<button
|
||||||
|
@click="result = getRandomDimensionRating(); trait = getRandomTraitRating()">RandomResult</button>
|
||||||
|
<button @click="$i18n.locale = $i18n.locale === 'en' ? 'zh' : 'en'">Lang:
|
||||||
|
{{ $i18n.locale }}</button>
|
||||||
|
</div>
|
||||||
|
<TransitionGroup name="fade">
|
||||||
|
<Home key="home" v-if="pIndex == 0" @next="pIndex = 1" />
|
||||||
|
<Game key="game" v-if="pIndex == 1" @skip="skipGame"
|
||||||
|
@finish="finishGame" />
|
||||||
|
<PersonalInfo key="personalInfo" v-if="pIndex == 2"
|
||||||
|
@submit="submitUserInfo" />
|
||||||
|
<Result key="result" v-if="pIndex == 3" :result="result" :trait="trait"
|
||||||
|
:top-five-traits="topFiveTraits" @next="pIndex++"
|
||||||
|
:graph="GraphDimensions" @retry="resetGame" :rarity="rarity" />
|
||||||
|
<Report key="report" v-if="pIndex == 4" :result="result" :trait="trait"
|
||||||
|
:top-five-traits="topFiveTraits" :username="username"
|
||||||
|
:graph="GraphDimensions" @retry="resetGame" :rarity="rarity" @next="pIndex++" />
|
||||||
|
<ReportMask key="report-mask" v-if="pIndex == 5" :result="result" :trait="trait"
|
||||||
|
:top-five-traits="topFiveTraits" :username="username"
|
||||||
|
:graph="GraphDimensions" @retry="resetGame" :rarity="rarity"@back="pIndex--" />
|
||||||
|
</TransitionGroup>
|
||||||
|
<img :src="imgs.logo" alt="" class="logo" @click="showDebug++"
|
||||||
|
v-if="pIndex == 0 || pIndex == 1 || pIndex == 2"
|
||||||
|
:style="{ left: pIndex == 0 ? '50%' : '10%', transform: pIndex == 0 ? 'translate3d(-50%, -50%, 0)' : 'translate3d(0, -50%, 0)', transition: '0.3s ease-in-out' }">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use './mixin.scss' as *;
|
||||||
|
|
||||||
|
.main {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
@include full-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug {
|
||||||
|
position: absolute;
|
||||||
|
left: .5rem;
|
||||||
|
bottom: .5rem;
|
||||||
|
z-index: 99;
|
||||||
|
width: max-content;
|
||||||
|
display: flex;
|
||||||
|
gap: .125rem;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: .6rem;
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: absolute;
|
||||||
|
top: 4%;
|
||||||
|
z-index: 99;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
src/assets/fonts/ARNewcuheiGB-Bold.woff2
Normal file
BIN
src/assets/fonts/LOREALEssentielle-Bold.woff2
Normal file
BIN
src/assets/fonts/LOREALEssentielle-BoldItalic.woff2
Normal file
BIN
src/assets/fonts/MXiangHeHeiSCStd-Book.woff2
Normal file
40
src/assets/fonts/stylesheet.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'LOREAL Essentielle';
|
||||||
|
src: url('LOREALEssentielle-Bold.woff2') format('woff2');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'LOREAL Essentielle';
|
||||||
|
src: url('LOREALEssentielle-BoldItalic.woff2') format('woff2');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'M XiangHe Hei SC Std Book';
|
||||||
|
src: url('MXiangHeHeiSCStd-Book.woff2') format('woff2');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'AR NewcuheiGB';
|
||||||
|
src: url('ARNewcuheiGB-Bold.woff2') format('woff2');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'HarmonyOS Sans SC';
|
||||||
|
src: url('subset-HarmonyOS_Sans_SC_Light.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
BIN
src/assets/fonts/subset-HarmonyOS_Sans_SC_Light.woff2
Normal file
BIN
src/assets/imgs/back.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/imgs/bg/A1.webp
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
src/assets/imgs/bg/A2.webp
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/imgs/bg/A3.webp
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
src/assets/imgs/bg/A4.webp
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
src/assets/imgs/bg/A5.webp
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
src/assets/imgs/bg/B1.webp
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
src/assets/imgs/bg/B2.webp
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/imgs/bg/B3.webp
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
src/assets/imgs/bg/B4.webp
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
src/assets/imgs/bg/B5.webp
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
src/assets/imgs/bg/C1.webp
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
src/assets/imgs/bg/C2.webp
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
src/assets/imgs/bg/C3.webp
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
src/assets/imgs/bg/C4.webp
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
src/assets/imgs/bg/C5.webp
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/imgs/bg/D1.webp
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
src/assets/imgs/bg/D2.webp
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
src/assets/imgs/bg/D3.webp
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
src/assets/imgs/bg/D4.webp
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
src/assets/imgs/bg/D5.webp
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
src/assets/imgs/bg/E1.webp
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
src/assets/imgs/bg/E2.webp
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
src/assets/imgs/bg/E3.webp
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
src/assets/imgs/bg/E4.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
src/assets/imgs/bg/E5.webp
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
src/assets/imgs/bg/ambition.webp
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
src/assets/imgs/bg/empathy.webp
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
src/assets/imgs/bg/judgement.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
src/assets/imgs/bg/learningAgility.webp
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
src/assets/imgs/bg/resilience.webp
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
src/assets/imgs/game/决断力.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/imgs/game/复原力.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/imgs/game/学习敏捷力.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/imgs/game/感知力.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/imgs/game/渴望力.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/imgs/home/button_en.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
src/assets/imgs/home/button_zh.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
src/assets/imgs/home/intro_en.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/imgs/home/intro_zh.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/imgs/home/title_en.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/imgs/home/title_zh.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/imgs/icons/ambition.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/assets/imgs/icons/empathy.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/imgs/icons/judgement.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/assets/imgs/icons/learningAgility.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/assets/imgs/icons/resilience.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
65
src/assets/imgs/index.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
logo: new URL("@/assets/imgs/logo.png", import.meta.url).href,
|
||||||
|
slideDown: new URL("@/assets/imgs/slide-down.png", import.meta.url).href,
|
||||||
|
back: new URL("@/assets/imgs/back.png", import.meta.url).href,
|
||||||
|
game: {
|
||||||
|
决断力: new URL("@/assets/imgs/game/决断力.png", import.meta.url).href,
|
||||||
|
感知力: new URL("@/assets/imgs/game/感知力.png", import.meta.url).href,
|
||||||
|
渴望力: new URL("@/assets/imgs/game/渴望力.png", import.meta.url).href,
|
||||||
|
学习敏捷力: new URL("@/assets/imgs/game/学习敏捷力.png", import.meta.url).href,
|
||||||
|
复原力: new URL("@/assets/imgs/game/复原力.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
Ambition: new URL("@/assets/imgs/icons/ambition.png", import.meta.url).href,
|
||||||
|
Judgement: new URL("@/assets/imgs/icons/judgement.png", import.meta.url).href,
|
||||||
|
Learning_Agility: new URL("@/assets/imgs/icons/learningAgility.png", import.meta.url).href,
|
||||||
|
Empathy: new URL("@/assets/imgs/icons/empathy.png", import.meta.url).href,
|
||||||
|
Resilience: new URL("@/assets/imgs/icons/resilience.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
Ambition: new URL("@/assets/imgs/report/ambition.webp", import.meta.url).href,
|
||||||
|
Judgement: new URL("@/assets/imgs/report/judgement.webp", import.meta.url).href,
|
||||||
|
Learning_Agility: new URL("@/assets/imgs/report/learningAgility.webp", import.meta.url).href,
|
||||||
|
Empathy: new URL("@/assets/imgs/report/empathy.webp", import.meta.url).href,
|
||||||
|
Resilience: new URL("@/assets/imgs/report/resilience.webp", import.meta.url).href,
|
||||||
|
bottomRightShutter: new URL("@/assets/imgs/report/bottom-right-shutter.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
bg: {
|
||||||
|
Ambition: new URL("@/assets/imgs/bg/ambition.webp", import.meta.url).href,
|
||||||
|
Judgement: new URL("@/assets/imgs/bg/judgement.webp", import.meta.url).href,
|
||||||
|
Learning_Agility: new URL("@/assets/imgs/bg/learningAgility.webp", import.meta.url).href,
|
||||||
|
Empathy: new URL("@/assets/imgs/bg/empathy.webp", import.meta.url).href,
|
||||||
|
Resilience: new URL("@/assets/imgs/bg/resilience.webp", import.meta.url).href,
|
||||||
|
A1: new URL("@/assets/imgs/bg/A1.webp", import.meta.url).href,
|
||||||
|
A2: new URL("@/assets/imgs/bg/A2.webp", import.meta.url).href,
|
||||||
|
A3: new URL("@/assets/imgs/bg/A3.webp", import.meta.url).href,
|
||||||
|
A4: new URL("@/assets/imgs/bg/A4.webp", import.meta.url).href,
|
||||||
|
A5: new URL("@/assets/imgs/bg/A5.webp", import.meta.url).href,
|
||||||
|
B1: new URL("@/assets/imgs/bg/B1.webp", import.meta.url).href,
|
||||||
|
B2: new URL("@/assets/imgs/bg/B2.webp", import.meta.url).href,
|
||||||
|
B3: new URL("@/assets/imgs/bg/B3.webp", import.meta.url).href,
|
||||||
|
B4: new URL("@/assets/imgs/bg/B4.webp", import.meta.url).href,
|
||||||
|
B5: new URL("@/assets/imgs/bg/B5.webp", import.meta.url).href,
|
||||||
|
C1: new URL("@/assets/imgs/bg/C1.webp", import.meta.url).href,
|
||||||
|
C2: new URL("@/assets/imgs/bg/C2.webp", import.meta.url).href,
|
||||||
|
C3: new URL("@/assets/imgs/bg/C3.webp", import.meta.url).href,
|
||||||
|
C4: new URL("@/assets/imgs/bg/C4.webp", import.meta.url).href,
|
||||||
|
C5: new URL("@/assets/imgs/bg/C5.webp", import.meta.url).href,
|
||||||
|
D1: new URL("@/assets/imgs/bg/D1.webp", import.meta.url).href,
|
||||||
|
D2: new URL("@/assets/imgs/bg/D2.webp", import.meta.url).href,
|
||||||
|
D3: new URL("@/assets/imgs/bg/D3.webp", import.meta.url).href,
|
||||||
|
D4: new URL("@/assets/imgs/bg/D4.webp", import.meta.url).href,
|
||||||
|
D5: new URL("@/assets/imgs/bg/D5.webp", import.meta.url).href,
|
||||||
|
E1: new URL("@/assets/imgs/bg/E1.webp", import.meta.url).href,
|
||||||
|
E2: new URL("@/assets/imgs/bg/E2.webp", import.meta.url).href,
|
||||||
|
E3: new URL("@/assets/imgs/bg/E3.webp", import.meta.url).href,
|
||||||
|
E4: new URL("@/assets/imgs/bg/E4.webp", import.meta.url).href,
|
||||||
|
E5: new URL("@/assets/imgs/bg/E5.webp", import.meta.url).href,
|
||||||
|
},
|
||||||
|
personalInfo: {
|
||||||
|
male: new URL("@/assets/imgs/personalInfo/male.png", import.meta.url).href,
|
||||||
|
female: new URL("@/assets/imgs/personalInfo/female.png", import.meta.url).href,
|
||||||
|
}
|
||||||
|
};
|
||||||
BIN
src/assets/imgs/logo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/imgs/personalInfo/female.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/imgs/personalInfo/male.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/imgs/personalInfo/submit_en.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/imgs/personalInfo/submit_zh.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/imgs/report/ambition.webp
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
src/assets/imgs/report/bottom-left_en.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src/assets/imgs/report/bottom-left_zh.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/imgs/report/bottom-right-shutter.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/imgs/report/bottom-right_en.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/imgs/report/bottom-right_zh.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/imgs/report/bottom_en.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
src/assets/imgs/report/bottom_zh.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/imgs/report/empathy.webp
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
src/assets/imgs/report/judgement.webp
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
src/assets/imgs/report/learningAgility.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
src/assets/imgs/report/resilience.webp
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
src/assets/imgs/report/share-mask_en.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
src/assets/imgs/report/share-mask_zh.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
src/assets/imgs/report/top.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
src/assets/imgs/result/rating-explanation_en.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
src/assets/imgs/result/rating-explanation_zh.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
src/assets/imgs/result/slide-up-notice_en.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/imgs/result/slide-up-notice_zh.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/imgs/slide-down.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/imgs/switch-lang_en.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/imgs/switch-lang_zh.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
1
src/assets/vue.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
117
src/components/FloatingIcons.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// FloatingIcons.vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
interface Position {
|
||||||
|
x: number; // 百分比位置 0-100
|
||||||
|
y: number; // 百分比位置 0-100
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IconConfig {
|
||||||
|
src: string; // 图标路径
|
||||||
|
size: number; // 图标大小(px)
|
||||||
|
blur?: number; // 模糊程度
|
||||||
|
position?: Position; // 初始位置
|
||||||
|
floatRange?: number; // 浮动范围(px)
|
||||||
|
duration?: number; // 动画周期(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
icons: IconConfig[];
|
||||||
|
globalBlur?: number; // 全局模糊度
|
||||||
|
floatAmplitude?: number; // 全局浮动幅度
|
||||||
|
breatheScale?: number; // 呼吸缩放比例
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
globalBlur: 4,
|
||||||
|
floatAmplitude: 10,
|
||||||
|
breatheScale: 1.05,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 为每个图标生成随机的动画延迟和初始位置
|
||||||
|
const getRandomDelay = (): number => Math.random() * 5;
|
||||||
|
const getRandomPosition = (): Position => ({
|
||||||
|
x: Math.random() * 80 + 10, // 保持在10%-90%的范围内
|
||||||
|
y: Math.random() * 80 + 10
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理图标配置,补充默认值
|
||||||
|
const processedIcons = computed(() => props.icons.map(icon => ({
|
||||||
|
...icon,
|
||||||
|
blur: icon.blur ?? props.globalBlur,
|
||||||
|
position: icon.position ?? getRandomPosition(),
|
||||||
|
floatRange: icon.floatRange ?? props.floatAmplitude,
|
||||||
|
duration: icon.duration ?? 3 + Math.random() * 2, // 3-5秒的随机周期
|
||||||
|
})));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="floating-icons-container">
|
||||||
|
<div v-for="(icon, index) in processedIcons" :key="index"
|
||||||
|
class="floating-icon" :style="{
|
||||||
|
width: `${icon.size}px`,
|
||||||
|
height: `${icon.size}px`,
|
||||||
|
top: `calc(${icon.position.y}% - ${icon.size / 2}px)`,
|
||||||
|
left: `calc(${icon.position.x}% - ${icon.size / 2}px)`,
|
||||||
|
filter: `blur(${icon.blur}px)`,
|
||||||
|
animationDuration: `${icon.duration}s`,
|
||||||
|
'--float-range': `${icon.floatRange}px`,
|
||||||
|
'--breathe-scale': breatheScale,
|
||||||
|
animationDelay: `-${getRandomDelay()}s`
|
||||||
|
}">
|
||||||
|
<img :src="icon.src" :alt="`floating icon ${index}`"
|
||||||
|
class="icon-image" draggable="false" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.floating-icons-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-icon {
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: center;
|
||||||
|
animation: float var(--animation-duration, 4s) infinite ease-in-out,
|
||||||
|
breathe var(--animation-duration, 4s) infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translateY(calc(var(--float-range) * -1)) scale(var(--breathe-scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translateY(var(--float-range)) scale(var(--breathe-scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes breathe {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
284
src/components/Graph.vue
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
import { createLinear2CubicBezier, forEachFrameInMilliseconds } from '../utils';
|
||||||
|
|
||||||
|
interface Dimension {
|
||||||
|
name_en: string;
|
||||||
|
name_zh: string;
|
||||||
|
value: number;
|
||||||
|
color: string;
|
||||||
|
iconUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义 props
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
dimensions: Dimension[];
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
innerRadius?: number;
|
||||||
|
labelRadius?: number;
|
||||||
|
outerRadius?: number;
|
||||||
|
gap?: number;
|
||||||
|
iconSize?: number;
|
||||||
|
scoreRadio?: number;
|
||||||
|
usePercentages?: boolean;
|
||||||
|
iconsFilter?: string;
|
||||||
|
enableAnimation?: boolean;
|
||||||
|
}>(), {
|
||||||
|
innerRadius: 80,
|
||||||
|
outerRadius: 100,
|
||||||
|
labelRadius: 101,
|
||||||
|
gap: 6,
|
||||||
|
iconSize: 40,
|
||||||
|
scoreRadio: 1,
|
||||||
|
usePercentages: true,
|
||||||
|
enableAnimation: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const partAngle = computed(() => {
|
||||||
|
const total = props.dimensions.reduce((acc, cur) => acc + cur.value, 0);
|
||||||
|
return props.dimensions.map(dim => dim.value / total * 360)
|
||||||
|
})
|
||||||
|
const finalPartRange = computed(() => {
|
||||||
|
let startAngle = 0;
|
||||||
|
return partAngle.value.map(angle => {
|
||||||
|
const range = [startAngle, startAngle + angle];
|
||||||
|
startAngle += angle;
|
||||||
|
return range;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
const animationCurrentDuration = ref(0)
|
||||||
|
const opacityDuration = ref(0)
|
||||||
|
const timingFunction = createLinear2CubicBezier()
|
||||||
|
onMounted(() => {
|
||||||
|
if (!props.enableAnimation) {
|
||||||
|
animationCurrentDuration.value = 1
|
||||||
|
opacityDuration.value = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
forEachFrameInMilliseconds((elapsedTime: number, totalTime: number) => {
|
||||||
|
animationCurrentDuration.value = timingFunction(elapsedTime / totalTime);
|
||||||
|
}, 1000)
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
forEachFrameInMilliseconds((elapsedTime: number, totalTime: number) => {
|
||||||
|
opacityDuration.value = timingFunction(elapsedTime / totalTime);
|
||||||
|
}, 500)
|
||||||
|
}, 780);
|
||||||
|
})
|
||||||
|
|
||||||
|
const partRange = computed(() => finalPartRange.value.map(range => range.map(angle => angle * animationCurrentDuration.value)))
|
||||||
|
|
||||||
|
const total = computed(() => props.dimensions.reduce((acc, cur) => acc + cur.value, 0))
|
||||||
|
|
||||||
|
const labelPos = computed(() => {
|
||||||
|
|
||||||
|
return finalPartRange.value.map((range, index) => {
|
||||||
|
return ({
|
||||||
|
dim: props.dimensions[index],
|
||||||
|
top: getLabelPosition((range[0] + range[1]) / 2 * animationCurrentDuration.value, props.outerRadius * 1.0 * props.labelRadius / 100).y / 4,
|
||||||
|
left: getLabelPosition((range[0] + range[1]) / 2 * animationCurrentDuration.value, props.outerRadius * 1.1 * props.labelRadius / 100).x / 4,
|
||||||
|
finalTop: getLabelPosition((range[0] + range[1]) / 2, props.outerRadius * 1.0 * props.labelRadius / 100).y / 4,
|
||||||
|
finalLeft: getLabelPosition((range[0] + range[1]) / 2, props.outerRadius * 1.1 * props.labelRadius / 100).x / 4
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 极坐标转笛卡尔坐标
|
||||||
|
const polarToCartesian = (centerX: number, centerY: number, radius: number, angleInDegrees: number) => {
|
||||||
|
const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
|
||||||
|
return {
|
||||||
|
x: centerX + (radius * Math.cos(angleInRadians)),
|
||||||
|
y: centerY + (radius * Math.sin(angleInRadians))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算扇形路径
|
||||||
|
const calculateSegmentPath = (startAngle: number, endAngle: number, innerRadius: number, outerRadius: number): string => {
|
||||||
|
const innerStart = polarToCartesian(200, 200, innerRadius, startAngle);
|
||||||
|
const innerEnd = polarToCartesian(200, 200, innerRadius, endAngle);
|
||||||
|
const outerStart = polarToCartesian(200, 200, outerRadius, startAngle);
|
||||||
|
const outerEnd = polarToCartesian(200, 200, outerRadius, endAngle);
|
||||||
|
|
||||||
|
const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'M', outerStart.x, outerStart.y, // 移动到外圈起点
|
||||||
|
'A', outerRadius, outerRadius, 0, largeArcFlag, 1, outerEnd.x, outerEnd.y, // 画外圈弧
|
||||||
|
'L', innerEnd.x, innerEnd.y, // 连线到内圈终点
|
||||||
|
'A', innerRadius, innerRadius, 0, largeArcFlag, 0, innerStart.x, innerStart.y, // 画内圈弧
|
||||||
|
'L', outerStart.x, outerStart.y, // 连回外圈起点
|
||||||
|
'Z' // 闭合路径
|
||||||
|
].join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算标签位置
|
||||||
|
const getLabelPosition = (angle: number, radius: number) => {
|
||||||
|
return polarToCartesian(200, 200, radius + 40, angle);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<svg viewBox="0 0 400 400" class="">
|
||||||
|
<!-- 中心标题 -->
|
||||||
|
|
||||||
|
<!-- 绘制扇形和标签 -->
|
||||||
|
<g v-for="(dim, index) in dimensions" :key="dim.name_zh">
|
||||||
|
<!-- 扇形区域 -->
|
||||||
|
<path :d="calculateSegmentPath(
|
||||||
|
partRange[index][0] + gap * animationCurrentDuration / 2,
|
||||||
|
partRange[index][1] - gap * animationCurrentDuration / 2,
|
||||||
|
innerRadius,
|
||||||
|
outerRadius
|
||||||
|
)" :fill="dim.color" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div class="labels">
|
||||||
|
<div class="label" v-for="pos in labelPos" :key="pos.dim.name_en"
|
||||||
|
:style="{
|
||||||
|
top: `${pos.top}%`,
|
||||||
|
left: `calc(${pos.left}% - .25em)`,
|
||||||
|
opacity: opacityDuration,
|
||||||
|
}" :class="{
|
||||||
|
right: pos.finalLeft > 50,
|
||||||
|
top: pos.finalTop < 50,
|
||||||
|
}">
|
||||||
|
<div class="label-value">
|
||||||
|
<img :src="pos.dim.iconUrl" :alt="pos.dim.name_zh"
|
||||||
|
:style="{ filter: iconsFilter }" />
|
||||||
|
<span class="value">
|
||||||
|
{{ usePercentages
|
||||||
|
? Math.round(pos.dim.value / total * 100)
|
||||||
|
: pos.dim.value * scoreRadio }}
|
||||||
|
</span>
|
||||||
|
<span class="percent" v-if="usePercentages">%</span>
|
||||||
|
</div>
|
||||||
|
<div class="label-name" :lang="$i18n.locale">
|
||||||
|
<span class="zh">{{ pos.dim.name_zh }}</span>
|
||||||
|
<span class="en">{{ pos.dim.name_en }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="center-title label">
|
||||||
|
<div>{{ title }}</div>
|
||||||
|
<div>{{ subtitle }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.container {
|
||||||
|
svg {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labels {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.center-title {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: .68em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
/* width: 7em; */
|
||||||
|
width: max-content;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.center-title {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.top {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
|
.label-name {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.label-value {
|
||||||
|
line-height: 1.0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2.6em;
|
||||||
|
height: 2.25em;
|
||||||
|
object-fit: cover;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.value {
|
||||||
|
font-size: 2.1em;
|
||||||
|
vertical-align: bottom;
|
||||||
|
margin-inline-end: .05em;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.percent {
|
||||||
|
font-size: 1em;
|
||||||
|
position: relative;
|
||||||
|
top: .15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-name {
|
||||||
|
/* padding: 0 8px; */
|
||||||
|
align-self: flex-end;
|
||||||
|
position: relative;
|
||||||
|
right: .125em;
|
||||||
|
|
||||||
|
&:lang(en) {
|
||||||
|
.zh {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.en {
|
||||||
|
font-size: .9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.en {
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zh {
|
||||||
|
font-size: .8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.font-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
93
src/components/LightGrow.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Position {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Shape {
|
||||||
|
color: string;
|
||||||
|
size: number;
|
||||||
|
aspectRatio?: number;
|
||||||
|
position: Position;
|
||||||
|
delay: number;
|
||||||
|
blurAmount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
blurAmount: number;
|
||||||
|
animationDuration: number;
|
||||||
|
shapes: Shape[];
|
||||||
|
background: string;
|
||||||
|
screenShadow?: {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
bottom: number;
|
||||||
|
right: number;
|
||||||
|
color: string;
|
||||||
|
blur: number;
|
||||||
|
};
|
||||||
|
filter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="blur-background" :style="{ background, filter }">
|
||||||
|
<div class="screen-shadow" v-if="screenShadow" :style="{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: '0',
|
||||||
|
zIndex: 9,
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
borderTop: `${screenShadow.top}px solid ${screenShadow.color}`,
|
||||||
|
borderLeft: `${screenShadow.left}px solid ${screenShadow.color}`,
|
||||||
|
borderBottom: `${screenShadow.bottom}px solid ${screenShadow.color}`,
|
||||||
|
borderRight: `${screenShadow.right}px solid ${screenShadow.color}`,
|
||||||
|
filter: `blur(${screenShadow.blur}px)`,
|
||||||
|
}">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div v-for="(shape, index) in shapes" :key="index" class="blur-shape"
|
||||||
|
:style="{
|
||||||
|
width: `${shape.size}px`,
|
||||||
|
height: `${shape.size / (shape.aspectRatio ? shape.aspectRatio : 1)}px`,
|
||||||
|
background: shape.color,
|
||||||
|
top: `calc(${shape.position.y}% - ${shape.size / 2}px)`,
|
||||||
|
left: `calc(${shape.position.x}% - ${shape.size / 2}px)`,
|
||||||
|
filter: `blur(${shape.blurAmount ? shape.blurAmount : blurAmount}px)`,
|
||||||
|
animationDuration: `${animationDuration}s`,
|
||||||
|
animationDelay: `${shape.delay}s`,
|
||||||
|
}" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.blur-background {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blur-shape {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: .8s ease-in-out;
|
||||||
|
animation: breathe var(--animation-duration, 4s) infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes breathe {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2);
|
||||||
|
opacity: .9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
161
src/components/Tags.vue
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { DimensionColors, findTraitDimension, TraitName, } from '../config';
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import type { MessageSchema } from '../locates'
|
||||||
|
const { t } = useI18n<{ message: MessageSchema }>({ useScope: 'global' })
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
topFiveTraits: [TraitName, number][]
|
||||||
|
rarity: number,
|
||||||
|
borderColor: string,
|
||||||
|
username: string,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="tags" :style="{ '--border-color': borderColor }">
|
||||||
|
<span class="label" :lang="$i18n.locale">
|
||||||
|
<span class="pre"></span>
|
||||||
|
<span class="content" :style="{ color: borderColor }">
|
||||||
|
{{ username }}{{ t('result.tagLabel') }}</span>
|
||||||
|
<span class="after"></span>
|
||||||
|
</span>
|
||||||
|
<!-- <span class="only-for-you">{{ t('result.onlyForYou') }}</span> -->
|
||||||
|
<div class="traits">
|
||||||
|
<div class="trait" :lang="$i18n.locale" v-for="trt in topFiveTraits"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: DimensionColors[findTraitDimension(trt[0])!]
|
||||||
|
}">{{
|
||||||
|
t(`questionData.trait.${trt[0]}`) }}</div>
|
||||||
|
</div>
|
||||||
|
<span class="footnote" :lang="$i18n.locale">
|
||||||
|
<span class="text">{{ t('result.footnote') }}</span>
|
||||||
|
<span class="rarity" :style="{ color: borderColor }">
|
||||||
|
{{ rarity }}%
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="footnote2" :lang="$i18n.locale">
|
||||||
|
{{ t('result.footnote2') }} {{ 100 - rarity }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use '../mixin.scss' as *;
|
||||||
|
|
||||||
|
.footnote {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: .35rem;
|
||||||
|
|
||||||
|
&:lang(en) {}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
position: relative;
|
||||||
|
top: .15rem;
|
||||||
|
font-size: .8rem;
|
||||||
|
white-space: pre;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: end;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rarity {
|
||||||
|
font-size: 2.1rem;
|
||||||
|
line-height: 1;
|
||||||
|
position: relative;
|
||||||
|
top: -.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footnote2 {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + .25rem);
|
||||||
|
right: 0;
|
||||||
|
font-size: .6rem;
|
||||||
|
|
||||||
|
&:lang(en) {
|
||||||
|
font-size: .55rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
position: relative;
|
||||||
|
border-left: .8px solid var(--border-color);
|
||||||
|
border-right: .8px solid var(--border-color);
|
||||||
|
border-bottom: .8px solid var(--border-color);
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0%;
|
||||||
|
left: 50%;
|
||||||
|
width: max-content;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
@include font(.9rem, 1, normal, center);
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: .5rem;
|
||||||
|
|
||||||
|
&:lang(en) {
|
||||||
|
.content {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
height: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
line-height: 1rem;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
transform: translateY(-.5lh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre,
|
||||||
|
.after {
|
||||||
|
flex: 1;
|
||||||
|
border-top: .8px solid var(--border-color);
|
||||||
|
border-left: .8px solid var(--border-color);
|
||||||
|
border-right: .8px solid var(--border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.only-for-you {
|
||||||
|
position: absolute;
|
||||||
|
top: .125rem;
|
||||||
|
right: .125rem;
|
||||||
|
color: #ffffffEE;
|
||||||
|
@include font(.6rem, 1.2, );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.traits {
|
||||||
|
padding: 1rem .75rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trait {
|
||||||
|
height: 1.05rem;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
margin-right: .5rem;
|
||||||
|
border-radius: .7rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
@include font(.75rem, 1, normal, center);
|
||||||
|
|
||||||
|
&:lang(en) {
|
||||||
|
@include font(.72rem, 1, normal, center);
|
||||||
|
}
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
298
src/config.ts
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
const ThemeColor = {
|
||||||
|
'yellow': '#F6C532',
|
||||||
|
'pink': '#FF796F',
|
||||||
|
'purple': '#B285B7',
|
||||||
|
'blue': '#066CF7',
|
||||||
|
'green': '#00D78D'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AssessmentItem {
|
||||||
|
id: string;
|
||||||
|
dimension: DimensionName;
|
||||||
|
trait: TraitName;
|
||||||
|
scores: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DimensionNames = ["Learning_Agility", "Ambition", "Empathy", "Judgement", "Resilience"] as const;
|
||||||
|
type DimensionName = (typeof DimensionNames)[number];
|
||||||
|
const DimensionColors: Record<DimensionName, string> = {
|
||||||
|
"Learning_Agility": ThemeColor.yellow,
|
||||||
|
"Ambition": ThemeColor.pink,
|
||||||
|
"Empathy": ThemeColor.purple,
|
||||||
|
"Judgement": ThemeColor.blue,
|
||||||
|
"Resilience": ThemeColor.green,
|
||||||
|
}
|
||||||
|
const GamePageDimensionColors: Record<DimensionName, string> = {
|
||||||
|
"Learning_Agility": '#F7C632',
|
||||||
|
"Resilience": '#00D88E',
|
||||||
|
"Empathy": '#B386B8',
|
||||||
|
"Ambition": '#FF796F',
|
||||||
|
"Judgement": '#066CF8'
|
||||||
|
}
|
||||||
|
const Traits = [
|
||||||
|
// Learning_Agility
|
||||||
|
"Pioneer",
|
||||||
|
"Knowledge_Seeker",
|
||||||
|
"Change_Pioneer",
|
||||||
|
"Curious_Mind",
|
||||||
|
"Trial_Warrior",
|
||||||
|
|
||||||
|
// Resilience
|
||||||
|
"Giants_Shoulders",
|
||||||
|
"Stress_Master",
|
||||||
|
"Roly-Poly",
|
||||||
|
"Steady_Anchor",
|
||||||
|
"Planner",
|
||||||
|
|
||||||
|
// Empathy
|
||||||
|
"Communication_Expert",
|
||||||
|
"Sincere_Partner",
|
||||||
|
"Mind_Hunter",
|
||||||
|
"Active_Listener",
|
||||||
|
"Emotion_Management_Master",
|
||||||
|
|
||||||
|
// Ambition
|
||||||
|
"Future_Prophet",
|
||||||
|
"Helmsman",
|
||||||
|
"Owner",
|
||||||
|
"Strategic_Leader",
|
||||||
|
"Perfectionist",
|
||||||
|
|
||||||
|
// Judgement
|
||||||
|
"Simplifier",
|
||||||
|
"Problem_Sniper",
|
||||||
|
"Eagle_Eye",
|
||||||
|
"Thought_Challenger",
|
||||||
|
"Rational_and_Intuitive"
|
||||||
|
] as const;
|
||||||
|
type TraitName = (typeof Traits)[number];
|
||||||
|
|
||||||
|
|
||||||
|
interface AssessmentDimension {
|
||||||
|
name: DimensionName;
|
||||||
|
items: AssessmentItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const PersonalityAssessment: AssessmentDimension[] = [
|
||||||
|
{
|
||||||
|
name: "Learning_Agility",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "A1",
|
||||||
|
dimension: "Learning_Agility",
|
||||||
|
trait: "Pioneer",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "A2",
|
||||||
|
dimension: "Learning_Agility",
|
||||||
|
trait: "Knowledge_Seeker",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "A3",
|
||||||
|
dimension: "Learning_Agility",
|
||||||
|
trait: "Change_Pioneer",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "A4",
|
||||||
|
dimension: "Learning_Agility",
|
||||||
|
trait: "Curious_Mind",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "A5",
|
||||||
|
dimension: "Learning_Agility",
|
||||||
|
trait: "Trial_Warrior",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Resilience",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "B1",
|
||||||
|
dimension: "Resilience",
|
||||||
|
trait: "Giants_Shoulders",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "B2",
|
||||||
|
dimension: "Resilience",
|
||||||
|
trait: "Stress_Master",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "B3",
|
||||||
|
dimension: "Resilience",
|
||||||
|
trait: "Roly-Poly",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "B4",
|
||||||
|
dimension: "Resilience",
|
||||||
|
trait: "Steady_Anchor",
|
||||||
|
scores: [5, 4, 3, 2, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "B5",
|
||||||
|
dimension: "Resilience",
|
||||||
|
trait: "Planner",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empathy",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "C1",
|
||||||
|
dimension: "Empathy",
|
||||||
|
trait: "Communication_Expert",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "C2",
|
||||||
|
dimension: "Empathy",
|
||||||
|
trait: "Sincere_Partner",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "C3",
|
||||||
|
dimension: "Empathy",
|
||||||
|
trait: "Mind_Hunter",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "C4",
|
||||||
|
dimension: "Empathy",
|
||||||
|
trait: "Active_Listener",
|
||||||
|
scores: [5, 4, 3, 2, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "C5",
|
||||||
|
dimension: "Empathy",
|
||||||
|
trait: "Emotion_Management_Master",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ambition",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "D1",
|
||||||
|
dimension: "Ambition",
|
||||||
|
trait: "Future_Prophet",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "D2",
|
||||||
|
dimension: "Ambition",
|
||||||
|
trait: "Helmsman",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "D3",
|
||||||
|
dimension: "Ambition",
|
||||||
|
trait: "Owner",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "D4",
|
||||||
|
dimension: "Ambition",
|
||||||
|
trait: "Strategic_Leader",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "D5",
|
||||||
|
dimension: "Ambition",
|
||||||
|
trait: "Perfectionist",
|
||||||
|
scores: [5, 4, 3, 2, 1]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Judgement",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "E1",
|
||||||
|
dimension: "Judgement",
|
||||||
|
trait: "Simplifier",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "E2",
|
||||||
|
dimension: "Judgement",
|
||||||
|
trait: "Problem_Sniper",
|
||||||
|
scores: [5, 4, 3, 2, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "E3",
|
||||||
|
dimension: "Judgement",
|
||||||
|
trait: "Eagle_Eye",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "E4",
|
||||||
|
dimension: "Judgement",
|
||||||
|
trait: "Thought_Challenger",
|
||||||
|
scores: [5, 4, 3, 2, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "E5",
|
||||||
|
dimension: "Judgement",
|
||||||
|
trait: "Rational_and_Intuitive",
|
||||||
|
scores: [1, 2, 3, 4, 5]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
function shuffle(array: any[]) {
|
||||||
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPersonalityAssessment(): AssessmentItem[] {
|
||||||
|
return shuffle(Object.values(PersonalityAssessment).map(dimension => dimension.items).flat());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDimensionQuestionLength(dimension: DimensionName): number {
|
||||||
|
return Object.values(PersonalityAssessment).find(d => d.name === dimension)?.items.length || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomDimensionRating(): Record<DimensionName, number> {
|
||||||
|
const random = (start: number, end: number) => Math.floor(Math.random() * (end - start + 1) + start);
|
||||||
|
return DimensionNames.reduce((acc, q) => ({
|
||||||
|
...acc,
|
||||||
|
[q]: random(getDimensionQuestionLength(q) * 1, getDimensionQuestionLength(q) * 5)
|
||||||
|
}), {} as Record<DimensionName, number>)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomTraitRating(): Record<TraitName, number> {
|
||||||
|
const random = (start: number, end: number) => Math.floor(Math.random() * (end - start + 1) + start);
|
||||||
|
return Traits.reduce((acc, q) => ({
|
||||||
|
...acc,
|
||||||
|
[q]: random(1, 5)
|
||||||
|
}), {} as Record<TraitName, number>)
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTraitDimension(trait: TraitName): DimensionName | undefined {
|
||||||
|
return Object.values(PersonalityAssessment).find(d => d.items.some(i => i.trait === trait))?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAllTraitByDimension(dimension: DimensionName): TraitName[] {
|
||||||
|
return Object.values(PersonalityAssessment).find(d => d.name === dimension)?.items.map(i => i.trait) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ThemeColor, PersonalityAssessment, GamePageDimensionColors,
|
||||||
|
getPersonalityAssessment, findTraitDimension, findAllTraitByDimension, getRandomTraitRating,
|
||||||
|
DimensionNames, DimensionColors, getDimensionQuestionLength, getRandomDimensionRating
|
||||||
|
}
|
||||||
|
export type { DimensionName, TraitName }
|
||||||
861
src/locates/index.ts
Normal file
@ -0,0 +1,861 @@
|
|||||||
|
/**
|
||||||
|
* Date: 2024/12/19
|
||||||
|
*
|
||||||
|
* Note: 多行文本可以使用 `` 符包裹,其中换行将保留在页面中,前需要使用 dedent 函数去除行首多余的空格。
|
||||||
|
* Note: 为了此文件的可读性,不需要换行的行尾请尾随 \ 符转义。
|
||||||
|
* Note: 较短的多行文本可以直接使用引号,其中换行用 \n 表示。
|
||||||
|
*
|
||||||
|
* Note: 必须保证中英文键名对应。本文件用 Typescript 进行检查,若键名不对应会标红并在问题栏目提示。
|
||||||
|
*
|
||||||
|
* Note: 关于上传和编译:
|
||||||
|
* Note: 需要先使用上传将编辑好的文件上传至服务器,再点击编译在服务器端执行编译任务,编译成功后会直接更新至生产环境。
|
||||||
|
* Note: 本编辑器没有自动保存功能,请在编辑后及时点击上传,已修改但未上传的内容会在刷新后失效。
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import dedent from "dedent";
|
||||||
|
interface Dedent {
|
||||||
|
(literals: string): string;
|
||||||
|
(strings: TemplateStringsArray, ...values: unknown[]): string;
|
||||||
|
}
|
||||||
|
declare const dedent: Dedent;
|
||||||
|
|
||||||
|
const zh_CN = {
|
||||||
|
switchLang: new URL("@/assets/imgs/switch-lang_zh.png", import.meta.url).href,
|
||||||
|
you: "你",
|
||||||
|
home: {
|
||||||
|
title: new URL("@/assets/imgs/home/title_zh.png", import.meta.url).href,
|
||||||
|
button: new URL("@/assets/imgs/home/button_zh.png", import.meta.url).href,
|
||||||
|
intro: new URL("@/assets/imgs/home/intro_zh.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
game: {
|
||||||
|
agree: "非常准确",
|
||||||
|
disagree: "非常不准确",
|
||||||
|
},
|
||||||
|
questionData: {
|
||||||
|
DimensionName: {
|
||||||
|
"Learning_Agility": "学习敏捷力",
|
||||||
|
"Resilience": "复原力",
|
||||||
|
"Empathy": "感知力",
|
||||||
|
"Ambition": "渴望力",
|
||||||
|
"Judgement": "决断力"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"A1": "在工作中,我总会主动寻找新的项目和挑战,哪怕意味着要学习新的技能。",
|
||||||
|
"A2": "工作完成后,我总会主动寻求同事和上级的反馈,并认真思考如何改进。",
|
||||||
|
"A3": "面对失败的项目或任务,我倾向于将其视为学习和成长的机会",
|
||||||
|
"A4": "我总是充满好奇心,会主动寻找和利用各种学习资源。",
|
||||||
|
"A5": "在实践中,我倾向于使用熟悉的技能和稳妥的方法来解决问题。",
|
||||||
|
|
||||||
|
"B1": "我总是勇于承担挫折和失败的责任。",
|
||||||
|
"B2": "遇到困难时,我很容易感到气馁,并且需要很长时间才能恢复过来。",
|
||||||
|
"B3": "哪怕经历项目失败或重大挫折,我仍会充满信心,并坚信自己能够克服困难。",
|
||||||
|
"B4": "当遇到挑战或挫折时,我很容易感到焦虑、沮丧,并且难以恢复。",
|
||||||
|
"B5": "我懂得接受无法避免的失败,并俯瞰全局,尝试走好下一步棋。",
|
||||||
|
|
||||||
|
"C1": "我擅长与他人建立良好关系,并积极促进团队合作,如组织团队活动、解决冲突等。",
|
||||||
|
"C2": "当同事完成一项出色工作时,我总会及时给予肯定和赞扬,并表达我的欣赏。",
|
||||||
|
"C3": "我善于分析市场数据、用户反馈等,并能准确洞察消费者需求。",
|
||||||
|
"C4": "我倾向于将注意力集中在自己,而非他人的情绪和需求上,这能帮助我保持客观与专注。",
|
||||||
|
"C5": "我会克制并有效引导自己的负面情绪,避免将其传递给他人。",
|
||||||
|
|
||||||
|
"D1": "我关注行业发展趋势,并能将其转化为创新的想法和项目。",
|
||||||
|
"D2": "我有明确的职业目标,并喜欢设定有挑战性的目标来推动自己不断进步。",
|
||||||
|
"D3": "我具备极强的主观能动性,以主人翁精神引领合作,推进想法与项目。",
|
||||||
|
"D4": "我喜欢成为引领变革的人,设定突破性目标,并坚持不懈地努力实现。",
|
||||||
|
"D5": "相比于不断改进,我更容易安于现状,我认为每时每刻的精益求精会导致效率低下。",
|
||||||
|
|
||||||
|
"E1": "在处理复杂任务时,我能够快速识别关键信息,并简化流程以提高效率。",
|
||||||
|
"E2": "面对问题时,我更关注如何解决问题,而不是寻找问题出现的根本原因。",
|
||||||
|
"E3": "我能够有效地安排工作优先级,并优先处理重要且紧急的任务。",
|
||||||
|
"E4": "在团队讨论中,我很少反驳他人的观点,即使我有不一样的想法或顾虑。",
|
||||||
|
"E5": "我能平衡直觉和理性,兼顾长短期利益及重要细节,并高效做出关键决策。"
|
||||||
|
},
|
||||||
|
trait: {
|
||||||
|
Pioneer: "开拓者",
|
||||||
|
"Knowledge_Seeker": "求知者",
|
||||||
|
"Change_Pioneer": "变革先锋",
|
||||||
|
"Curious_Mind": "好奇宝宝",
|
||||||
|
"Trial_Warrior": "试错勇士",
|
||||||
|
|
||||||
|
"Giants_Shoulders": "巨人的肩膀",
|
||||||
|
"Stress_Master": "抗压达人",
|
||||||
|
"Roly-Poly": "不倒翁",
|
||||||
|
"Steady_Anchor": "定海神针",
|
||||||
|
"Planner": "规划者",
|
||||||
|
|
||||||
|
"Communication_Expert": "沟通高手",
|
||||||
|
"Sincere_Partner": "诚以待人",
|
||||||
|
"Mind_Hunter": "心灵捕手",
|
||||||
|
"Active_Listener": "倾听者",
|
||||||
|
"Emotion_Management_Master": "情绪管理大师",
|
||||||
|
|
||||||
|
"Future_Prophet": "未来预言家",
|
||||||
|
"Helmsman": "掌舵人",
|
||||||
|
"Owner": "主人翁",
|
||||||
|
"Strategic_Leader": "战略领袖",
|
||||||
|
"Perfectionist": "完美主义者",
|
||||||
|
|
||||||
|
"Simplifier": "化繁为简",
|
||||||
|
"Problem_Sniper": "问题狙击手",
|
||||||
|
"Eagle_Eye": "鹰眼",
|
||||||
|
"Thought_Challenger": "思想碰撞者",
|
||||||
|
"Rational_and_Intuitive": "理性与感性兼备"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
personalInfo: {
|
||||||
|
title: '感谢你的耐心作答\n接下来最后一步',
|
||||||
|
placeholder: {
|
||||||
|
username: "请输入你的昵称",
|
||||||
|
age: "请输入你的年龄",
|
||||||
|
gender: "请选择你的性别",
|
||||||
|
},
|
||||||
|
gender: {
|
||||||
|
male: "男性",
|
||||||
|
female: "女性",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
username: "请输入你的昵称",
|
||||||
|
age: "请输入你的年龄",
|
||||||
|
gender: "请选择你的性别",
|
||||||
|
},
|
||||||
|
submit: new URL("@/assets/imgs/personalInfo/submit_zh.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
title: "你的 5 大潜能评分",
|
||||||
|
subtitle: "此报告旨在表现你各维度潜能的分布比例\n助你了解核心优势与短板",
|
||||||
|
tagLabel: "已获得如下潜能标签:",
|
||||||
|
footnote: "标签组合稀有度*",
|
||||||
|
footnote2: "*潜能标签组合出现的几率不超过",
|
||||||
|
rare: {
|
||||||
|
"SSR": "SSR",
|
||||||
|
"UR": "UR",
|
||||||
|
},
|
||||||
|
onlyForYou: "*仅供参考",
|
||||||
|
slideDown: "下滑查看欧莱雅 5 大潜能定义\n并获取你的最终报告",
|
||||||
|
slideUp: new URL("@/assets/imgs/result/slide-up-notice_zh.png", import.meta.url).href,
|
||||||
|
ratingExplanation: new URL("@/assets/imgs/result/rating-explanation_zh.png", import.meta.url).href,
|
||||||
|
actions: {
|
||||||
|
tryAgain: "再测一次",
|
||||||
|
generateReport: "生成个人报告",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
title: "的核心潜能是",
|
||||||
|
dimDescription: {
|
||||||
|
Learning_Agility: dedent`
|
||||||
|
你是一位天生的开拓者,对世界充满好奇,\
|
||||||
|
像个好奇宝宝一样对未知领域充满探索欲,\
|
||||||
|
拥有不断尝试新方法的魄力。
|
||||||
|
|
||||||
|
你是一位求知者,同时也是一位变革先锋,\
|
||||||
|
善于敏捷地学习、适应变化,并从成功和失败中汲取经验,\
|
||||||
|
不断精进自我。作为一名试错勇者,你乐于分享学习成果,\
|
||||||
|
营造积极的学习氛围。
|
||||||
|
`,
|
||||||
|
Resilience: dedent`
|
||||||
|
你是一位坚韧的不倒翁,拥有强大的内心和抗压能力,\
|
||||||
|
即使面对挫折也能够保持乐观积极的心态。
|
||||||
|
|
||||||
|
你不易被困难击倒,能够迅速调整心态、吸取经验,像团队的定海神针一般,\
|
||||||
|
找到解决问题的最佳方案。你始终保持乐观和积极的态度,勇于承担责任,\
|
||||||
|
也激励团队成员共同克服难关,是团队中值得信赖的伙伴。
|
||||||
|
`,
|
||||||
|
Empathy: dedent`
|
||||||
|
你是一位极具天赋的沟通高手,拥有卓越的同理心和敏锐的洞察力,\
|
||||||
|
能够深入理解他人的真实想法和感受,是团队中不可或缺的“心灵捕手”。
|
||||||
|
|
||||||
|
你真诚待人,善于倾听,这使得你能够与不同人群建立良好关系,\
|
||||||
|
并总能敏锐地捕捉到消费者的需求和痛点,最终创造共赢。\
|
||||||
|
你相信,真正的成功在于理解和尊重每一个人。
|
||||||
|
`,
|
||||||
|
Ambition: dedent`
|
||||||
|
你是一位富有远见的战略领袖,怀揣着“敢为敢超越”的精神,\
|
||||||
|
拥有远大的理想和抱负,并会为之付出不懈努力。
|
||||||
|
|
||||||
|
你从不满足于现状,勇于挑战自我,不断突破自身极限,\
|
||||||
|
积极探索新的可能性,像掌舵人一般带领团队乘风破浪,\
|
||||||
|
引领团队朝着目标前进。你拥有强烈的主人翁精神,并相信这能够创造无限可能。
|
||||||
|
`,
|
||||||
|
Judgement: dedent`
|
||||||
|
你是一位理性与感性兼备的决策者,拥有出色的判断力和决策能力,\
|
||||||
|
能够在复杂多变的情况下快速分析问题、抓住关键信息,并做出明智的决策。
|
||||||
|
|
||||||
|
你是一位问题狙击手,善于化繁为简,目光长远,能够平衡直觉和理性,\
|
||||||
|
权衡所有利弊,并带领团队走向成功。
|
||||||
|
`
|
||||||
|
},
|
||||||
|
top: new URL("@/assets/imgs/report/top.png", import.meta.url).href,
|
||||||
|
bottom: new URL("@/assets/imgs/report/bottom_zh.png", import.meta.url).href,
|
||||||
|
bottomLeft: new URL("@/assets/imgs/report/bottom-left_zh.png", import.meta.url).href,
|
||||||
|
bottomRight: new URL("@/assets/imgs/report/bottom-right_zh.png", import.meta.url).href,
|
||||||
|
shareMask: new URL("@/assets/imgs/report/share-mask_zh.png", import.meta.url).href,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const en: typeof zh_CN = {
|
||||||
|
switchLang: new URL("@/assets/imgs/switch-lang_en.png", import.meta.url).href,
|
||||||
|
you: "You",
|
||||||
|
home: {
|
||||||
|
title: new URL("@/assets/imgs/home/title_en.png", import.meta.url).href,
|
||||||
|
button: new URL("@/assets/imgs/home/button_en.png", import.meta.url).href,
|
||||||
|
intro: new URL("@/assets/imgs/home/intro_en.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
game: {
|
||||||
|
agree: "Agree",
|
||||||
|
disagree: "Disagree",
|
||||||
|
},
|
||||||
|
questionData: {
|
||||||
|
DimensionName: {
|
||||||
|
"Learning_Agility": "Learning Agility",
|
||||||
|
"Resilience": "Resilience",
|
||||||
|
"Empathy": "Empathy",
|
||||||
|
"Ambition": "Ambition",
|
||||||
|
"Judgement": "Judgement"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"A1": "In my work, I always take the initiative to seek out new projects and challenges, even if it requires learning new skills.",
|
||||||
|
"A2": "After completing a task, I always take the initiative to seek feedback from colleagues and leaders, and I reflect on areas for improvement.",
|
||||||
|
"A3": "When faced with a failed project or task, I tend to see it as an opportunity for learning and growth.",
|
||||||
|
"A4": "I am always full of curiosity and actively seek out and utilize various learning resources.",
|
||||||
|
"A5": "In practice, I tend to use familiar skills and proven methods to solve problems.",
|
||||||
|
|
||||||
|
"B1": "I am always willing to take responsibility for setbacks and failures.",
|
||||||
|
"B2": "When faced with difficulties, I easily feel discouraged and take a long time to recover.",
|
||||||
|
"B3": "Even after experiencing project failures or major setbacks, I remain confident in my ability to overcome.",
|
||||||
|
"B4": "When faced with challenges or setbacks, I easily feel anxious and frustrated, and I find it difficult to recover.",
|
||||||
|
"B5": "I understand how to accept unavoidable failures, keep a broader perspective, and try to make the next move wisely.",
|
||||||
|
|
||||||
|
"C1": "I am good at building positive relationships with others and actively promoting teamwork, such as organizing team activities and resolving conflicts.",
|
||||||
|
"C2": "When a colleague does excellent work, I always provide timely recognition and praise, expressing my appreciation.",
|
||||||
|
"C3": "I am skilled at analyzing market data and user feedback, and I can accurately identify consumer needs.",
|
||||||
|
"C4": "I tend to focus my attention on myself rather than on the emotions and needs of others, which helps me stay objective and focused.",
|
||||||
|
"C5": "I can control and effectively manage my negative emotions, avoiding passing them on to others.",
|
||||||
|
|
||||||
|
"D1": "I pay attention to industry development trends and can transform them into innovative ideas and projects.",
|
||||||
|
"D2": "I have clear career goals and enjoy setting challenging objectives to drive my continuous improvement.",
|
||||||
|
"D3": "I possess strong initiative and lead collaboration with a sense of ownership, advancing ideas and projects.",
|
||||||
|
"D4": "I enjoy being a change leader, setting groundbreaking goals, and persistently working to achieve them.",
|
||||||
|
"D5": "Compared to continuous improvement, I find it easier to settle for the status quo, as I believe striving for perfection at every moment can lead to inefficiency.",
|
||||||
|
|
||||||
|
"E1": "When handling complex tasks, I can quickly identify key information and streamline processes to improve efficiency.",
|
||||||
|
"E2": "When faced with problems, I focus more on how to solve the issue rather than seeking the root cause of its occurrence.",
|
||||||
|
"E3": "I can effectively prioritize work and focus on important and urgent tasks first.",
|
||||||
|
"E4": "In team discussions, I rarely contradict others' viewpoints, even if I have different ideas or concerns.",
|
||||||
|
"E5": "I can balance intuition and rationality, consider both short-term and long-term interests along with important details, and make key decisions efficiently."
|
||||||
|
},
|
||||||
|
trait: {
|
||||||
|
Pioneer: "Explorer",
|
||||||
|
"Knowledge_Seeker": "Learner",
|
||||||
|
"Change_Pioneer": "Champion of Change",
|
||||||
|
"Curious_Mind": "Curious Soul",
|
||||||
|
"Trial_Warrior": "Trial-and-Error Warrior",
|
||||||
|
|
||||||
|
"Giants_Shoulders": "Shoulders of Giants",
|
||||||
|
"Stress_Master": "Resilience Rockstar",
|
||||||
|
"Roly-Poly": "Bounder-Backer",
|
||||||
|
"Steady_Anchor": "Steady Hand",
|
||||||
|
"Planner": "Strategist",
|
||||||
|
|
||||||
|
"Communication_Expert": "Communication Expert",
|
||||||
|
"Sincere_Partner": "Sincere Individual",
|
||||||
|
"Mind_Hunter": "Astute Observer",
|
||||||
|
"Active_Listener": "Listener",
|
||||||
|
"Emotion_Management_Master": "Emotion Management Master",
|
||||||
|
|
||||||
|
"Future_Prophet": "Visionary",
|
||||||
|
"Helmsman": "Helmsman",
|
||||||
|
"Owner": "Go-Getter",
|
||||||
|
"Strategic_Leader": "Strategic Leader",
|
||||||
|
"Perfectionist": "Perfectionist",
|
||||||
|
|
||||||
|
"Simplifier": "Simplifier",
|
||||||
|
"Problem_Sniper": "Troubleshooter",
|
||||||
|
"Eagle_Eye": "Prioritizer",
|
||||||
|
"Thought_Challenger": "Brainstormer",
|
||||||
|
"Rational_and_Intuitive": "Integrated Thinker"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
personalInfo: {
|
||||||
|
title: 'Thank you for your patience in answering.\nThis is the final step.',
|
||||||
|
placeholder: {
|
||||||
|
username: "Your nickname is",
|
||||||
|
age: "Your age is",
|
||||||
|
gender: "Select your gender",
|
||||||
|
},
|
||||||
|
gender: {
|
||||||
|
male: "Male",
|
||||||
|
female: "Female",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
username: "Please enter your nickname",
|
||||||
|
age: "Please enter your age",
|
||||||
|
gender: "Please select your gender",
|
||||||
|
},
|
||||||
|
submit: new URL("@/assets/imgs/personalInfo/submit_en.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
title: "Your 5 DIMENSIONS\nof Potential<sup>©</sup> Rating",
|
||||||
|
subtitle: "This report aims to illustrate the distribution of your potential across various dimensions, helping you understand your core strengths and weaknesses.",
|
||||||
|
tagLabel: " have obtained the following pillar tags:",
|
||||||
|
footnote: "Tag combination\nrarity*",
|
||||||
|
footnote2: "*The probability of the combination of potential labels appearing is less than",
|
||||||
|
rare: {
|
||||||
|
"SSR": "SSR",
|
||||||
|
"UR": "UR",
|
||||||
|
},
|
||||||
|
onlyForYou: "*For reference only",
|
||||||
|
slideDown: dedent`
|
||||||
|
<span style='font-size: 1.3rem'>Scroll down</span>
|
||||||
|
<div style='font-size: .65rem;line-height: 1.2;'>to view official description about
|
||||||
|
The 5 L'ORÉAL DIMENSIONS of Potential and obtain your final report</div>
|
||||||
|
`,
|
||||||
|
slideUp: new URL("@/assets/imgs/result/slide-up-notice_en.png", import.meta.url).href,
|
||||||
|
ratingExplanation: new URL("@/assets/imgs/result/rating-explanation_en.png", import.meta.url).href,
|
||||||
|
actions: {
|
||||||
|
tryAgain: "Test again",
|
||||||
|
generateReport: "Generate report",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
title: "'s core pillar is",
|
||||||
|
top: new URL("@/assets/imgs/report/top.png", import.meta.url).href,
|
||||||
|
bottom: new URL("@/assets/imgs/report/bottom_en.png", import.meta.url).href,
|
||||||
|
bottomLeft: new URL("@/assets/imgs/report/bottom-left_en.png", import.meta.url).href,
|
||||||
|
bottomRight: new URL("@/assets/imgs/report/bottom-right_en.png", import.meta.url).href,
|
||||||
|
dimDescription: {
|
||||||
|
Learning_Agility: dedent`
|
||||||
|
You are a natural explorer, \
|
||||||
|
and you are curious about the world and eager to explore the unknown, \
|
||||||
|
constantly trying new approaches.
|
||||||
|
|
||||||
|
You are a learner and change agent, adapting quickly and learning from experience. \
|
||||||
|
You share your knowledge and foster a positive learning environment.
|
||||||
|
`,
|
||||||
|
Resilience: dedent`
|
||||||
|
You are resilient and strong, and you maintain optimism even in setbacks.
|
||||||
|
|
||||||
|
You recover quickly from challenges, learn from experience, \
|
||||||
|
and find the best solutions. Your positive attitude, \
|
||||||
|
responsibility, and ability to motivate make you a trusted partner.
|
||||||
|
`,
|
||||||
|
Empathy: dedent`
|
||||||
|
You are a gifted communicator with strong empathy and insight, \
|
||||||
|
and you understand others deeply.
|
||||||
|
|
||||||
|
Your sincerity and listening skills build strong relationships, \
|
||||||
|
enabling you to identify consumer needs and create win-win solutions. \
|
||||||
|
You believe success lies in understanding and respecting everyone.
|
||||||
|
`,
|
||||||
|
Ambition: dedent`
|
||||||
|
You are a visionary strategic leader, embodying the spirit of "freedom to go beyond."
|
||||||
|
|
||||||
|
You have grand ambitions and pursue them relentlessly. \
|
||||||
|
Never complacent, you challenge yourself, push boundaries, \
|
||||||
|
and explore new possibilities, leading your team like a skilled captain. \
|
||||||
|
Your strong ownership creates infinite potential.
|
||||||
|
`,
|
||||||
|
Judgement: dedent`
|
||||||
|
You are a decisive leader with both intuition and analytical skills, \
|
||||||
|
and you excel at navigating complex situations and making informed decisions.
|
||||||
|
|
||||||
|
Besides, you are a skilled problem-solver, \
|
||||||
|
simplifying complexity, thinking strategically, \
|
||||||
|
and guiding your team to success.
|
||||||
|
`
|
||||||
|
},
|
||||||
|
shareMask: new URL("@/assets/imgs/report/share-mask_en.png", import.meta.url).href,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
const zh_TW : typeof zh_CN = {
|
||||||
|
// 參照 zh 物件補全,並將圖片路徑改為 zhtw
|
||||||
|
switchLang: new URL("@/assets/imgs/switch-lang_zhtw.png", import.meta.url).href,
|
||||||
|
you: "你",
|
||||||
|
home: {
|
||||||
|
title: new URL("@/assets/imgs/home/title_zhtw.png", import.meta.url).href,
|
||||||
|
button: new URL("@/assets/imgs/home/button_zhtw.png", import.meta.url).href,
|
||||||
|
intro: new URL("@/assets/imgs/home/intro_zhtw.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
// 以下為您提供的譯文,已修正格式
|
||||||
|
game: {
|
||||||
|
agree: "非常準確",
|
||||||
|
disagree: "非常不準確",
|
||||||
|
},
|
||||||
|
questionData: {
|
||||||
|
DimensionName: {
|
||||||
|
"Learning_Agility": "學習敏捷度",
|
||||||
|
"Resilience": "韌性",
|
||||||
|
"Empathy": "同理心",
|
||||||
|
"Ambition": "企圖心",
|
||||||
|
"Judgement": "判斷力"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"A1": "我會努力探索自己的極限,勇於跨出舒適圈嘗試新事物和體驗。",
|
||||||
|
"A2": "我主動尋求並傾聽回饋,不斷提升自我。",
|
||||||
|
"A3": "我視危機為轉機,並視失敗為學習的良機。",
|
||||||
|
"A4": "我充滿好奇心,會主動尋找和利用各種學習資源。",
|
||||||
|
"A5": "我勇於在實踐中學習,即使不保證成功也會盡力嘗試新方法。",
|
||||||
|
|
||||||
|
"B1": "我勇於承擔挫折和失敗的責任。",
|
||||||
|
"B2": "我善於管理壓力,能迅速走出挫折,保持積極樂觀。",
|
||||||
|
"B3": "我遇到困難不輕易放棄,追尋並享受挑戰帶來的刺激與鼓勵。",
|
||||||
|
"B4": "在情勢不明朗時,我能保持冷靜與沉著,帶領成功到來。",
|
||||||
|
"B5": "我懂得接受無法避免的失敗,俯瞰全局,並嘗試規畫下一步。",
|
||||||
|
|
||||||
|
"C1": "我善於與他人建立關係,支持並推動團隊之間的合作。",
|
||||||
|
"C2": "我總以真誠待人,直接表達尊重和欣賞。",
|
||||||
|
"C3": "我能夠理解消費者行為背後的動機和心理,並將其應用到市場行銷策略中。",
|
||||||
|
"C4": "我待人友善,總能保持體貼,並懂得積極傾聽以瞭解他人的想法和感受。",
|
||||||
|
"C5": "我會克制並有效引導自己的負面情緒,避免將其傳遞給他人。",
|
||||||
|
|
||||||
|
"D1": "我目光長遠,善於發現新趨勢並分享具有創意的洞見與願景。",
|
||||||
|
"D2": "我具有企圖心且有抱負,會給自己設定極具挑戰的目標。",
|
||||||
|
"D3": "我具備強烈的主動性,以主人翁精神引領合作,推進想法與專案。",
|
||||||
|
"D4": "以卓越的企圖心引領變革,設定與利害相關者目標相符的突破性目標,並始終堅持誠信正直。",
|
||||||
|
"D5": "我追求卓越,不滿足於現狀,總是尋求改進的機會與突破的可能。",
|
||||||
|
|
||||||
|
"E1": "我時刻關注效率,善於簡化過程,突出重點。",
|
||||||
|
"E2": "我善於發現問題的根源,不斷從複雜的困境抽絲剝繭,尋求解決方案。",
|
||||||
|
"E3": "我總能區分輕重緩急,集中精力以解決關鍵挑戰。",
|
||||||
|
"E4": "勇於以建設性的方式挑戰並改善他人的想法。",
|
||||||
|
"E5": "全面思考,兼顧長短期利益及重要細節,並有效結合分析和直覺,高效做出關鍵決策。"
|
||||||
|
},
|
||||||
|
trait: {
|
||||||
|
Pioneer: "開拓者",
|
||||||
|
"Knowledge_Seeker": "求知者",
|
||||||
|
"Change_Pioneer": "變革先鋒",
|
||||||
|
"Curious_Mind": "好奇寶寶",
|
||||||
|
"Trial_Warrior": "試錯勇士",
|
||||||
|
|
||||||
|
"Giants_Shoulders": "巨人的肩膀",
|
||||||
|
"Stress_Master": "抗壓達人",
|
||||||
|
"Roly-Poly": "不倒翁",
|
||||||
|
"Steady_Anchor": "定海神針",
|
||||||
|
"Planner": "規劃者",
|
||||||
|
|
||||||
|
"Communication_Expert": "溝通高手",
|
||||||
|
"Sincere_Partner": "誠以待人",
|
||||||
|
"Mind_Hunter": "心靈捕手",
|
||||||
|
"Active_Listener": "傾聽者",
|
||||||
|
"Emotion_Management_Master": "情緒管理大師",
|
||||||
|
|
||||||
|
"Future_Prophet": "未來預言家",
|
||||||
|
"Helmsman": "掌舵人",
|
||||||
|
"Owner": "主人翁",
|
||||||
|
"Strategic_Leader": "戰略領袖",
|
||||||
|
"Perfectionist": "完美主義者",
|
||||||
|
|
||||||
|
"Simplifier": "化繁為簡",
|
||||||
|
"Problem_Sniper": "問題狙擊手",
|
||||||
|
"Eagle_Eye": "鷹眼",
|
||||||
|
"Thought_Challenger": "思想碰撞者",
|
||||||
|
"Rational_and_Intuitive": "理性與感性兼備"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
personalInfo: {
|
||||||
|
title: '感謝你的耐心作答\n接下來最後一步',
|
||||||
|
placeholder: {
|
||||||
|
username: "請輸入你的暱稱",
|
||||||
|
age: "請輸入你的年齡",
|
||||||
|
gender: "請選擇你的性別",
|
||||||
|
},
|
||||||
|
gender: {
|
||||||
|
male: "男性",
|
||||||
|
female: "女性",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
username: "請輸入你的暱稱",
|
||||||
|
age: "請輸入你的年齡",
|
||||||
|
gender: "請選擇你的性別",
|
||||||
|
},
|
||||||
|
// 參照 zh 物件補全
|
||||||
|
submit: new URL("@/assets/imgs/personalInfo/submit_zhtw.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
title: "你的 5 大潛能評分",
|
||||||
|
// 參照 zh 物件補全並翻譯
|
||||||
|
subtitle: "此報告旨在呈現你各維度潛能的分佈比例\n助你了解核心優勢與短板",
|
||||||
|
tagLabel: "你已獲得如下潛能標籤:",
|
||||||
|
footnote: "標籤組合稀有度*",
|
||||||
|
footnote2: "*潛能標籤組合出現的機率不超過",
|
||||||
|
rare: {
|
||||||
|
"SSR": "SSR",
|
||||||
|
"UR": "UR",
|
||||||
|
},
|
||||||
|
// 參照 zh 物件補全並翻譯
|
||||||
|
onlyForYou: "*僅供參考",
|
||||||
|
slideDown: "下滑查看萊雅 5 大潛能定義\n並獲取你的最終報告",
|
||||||
|
slideUp: new URL("@/assets/imgs/result/slide-up-notice_zhtw.png", import.meta.url).href,
|
||||||
|
ratingExplanation: new URL("@/assets/imgs/result/rating-explanation_zhtw.png", import.meta.url).href,
|
||||||
|
actions: {
|
||||||
|
tryAgain: "再測一次",
|
||||||
|
generateReport: "生成個人報告",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
// 參照 zh 物件補全
|
||||||
|
title: "的核心潛能是",
|
||||||
|
// 將您提供的譯文移至此處,並使用 dedent 整理
|
||||||
|
dimDescription: {
|
||||||
|
Learning_Agility: dedent`
|
||||||
|
你是一位天生的開拓者,對世界充滿好奇,\
|
||||||
|
像個好奇寶寶一樣對未知領域充滿探索欲,\
|
||||||
|
擁有不斷嘗試新方法的魄力。
|
||||||
|
|
||||||
|
你是一位求知者,同時也是一位變革先鋒,\
|
||||||
|
善於敏捷地學習、適應變化,並從成功和失敗中汲取經驗,\
|
||||||
|
不斷精進自我。作為一名試錯勇者,你樂於分享學習成果,\
|
||||||
|
營造積極的學習氛圍。
|
||||||
|
`,
|
||||||
|
Resilience: dedent`
|
||||||
|
你是一位堅韌的不倒翁,擁有強大的內心和抗壓能力,\
|
||||||
|
即使面對挫折也能夠保持樂觀積極的心態。
|
||||||
|
|
||||||
|
你不易被困難擊倒,能夠迅速調整心態、吸取經驗,像團隊的定海神針一般,\
|
||||||
|
找到解決問題的最佳方案。你始終保持樂觀和積極的態度,勇於承擔責任,\
|
||||||
|
也激勵團隊成員共同克服難關,是團隊中值得信賴的夥伴。
|
||||||
|
`,
|
||||||
|
Empathy: dedent`
|
||||||
|
你是一位極具天賦的溝通高手,擁有卓越的同理心和敏銳的洞察力,\
|
||||||
|
能夠深入理解他人的真實想法和感受,是團隊中不可或缺的「心靈捕手」。
|
||||||
|
|
||||||
|
你真誠待人,善於傾聽,這使得你能夠與不同人群建立良好關係,\
|
||||||
|
並總能敏銳地捕捉到消費者的需求和痛點,最終創造共贏。\
|
||||||
|
你相信,真正的成功在於理解和尊重每一個人。
|
||||||
|
`,
|
||||||
|
Ambition: dedent`
|
||||||
|
你是一位有遠見的戰略領袖,懷抱著 “Freedom to go beyond” 的精神,\
|
||||||
|
擁有遠大的理想和抱負,並會為之付出不懈努力。
|
||||||
|
|
||||||
|
你從不滿足於現狀,勇於挑戰自我,不斷突破自身極限,\
|
||||||
|
積極探索新的可能性,像掌舵人一般帶領團隊乘風破浪,\
|
||||||
|
引領團隊朝著目標前進。你擁有強烈的主人翁精神,並相信這能夠創造無限可能。
|
||||||
|
`,
|
||||||
|
Judgement: dedent`
|
||||||
|
你是一位理性與感性兼備的決策者,擁有出色的判斷力和決策能力,\
|
||||||
|
能夠在複雜多變的情況下快速分析問題、抓住關鍵資訊,並做出明智的決策。
|
||||||
|
|
||||||
|
你是一位問題狙擊手,善於化繁為簡,目光長遠,能夠平衡直覺和理性,\
|
||||||
|
權衡所有利弊,並帶領團隊走向成功。
|
||||||
|
`
|
||||||
|
},
|
||||||
|
// 參照 zh 物件補全,並將圖片路徑改為 zhtw
|
||||||
|
top: new URL("@/assets/imgs/report/top.png", import.meta.url).href,
|
||||||
|
bottom: new URL("@/assets/imgs/report/bottom_zhtw.png", import.meta.url).href,
|
||||||
|
bottomLeft: new URL("@/assets/imgs/report/bottom-left_zhtw.png", import.meta.url).href,
|
||||||
|
bottomRight: new URL("@/assets/imgs/report/bottom-right_zhtw.png", import.meta.url).href,
|
||||||
|
shareMask: new URL("@/assets/imgs/report/share-mask_zhtw.png", import.meta.url).href,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ko: typeof zh_CN = {
|
||||||
|
// 參照 zh 物件補全,並將圖片路徑改為 ko
|
||||||
|
switchLang: new URL("@/assets/imgs/switch-lang_ko.png", import.meta.url).href,
|
||||||
|
you: "당신",
|
||||||
|
home: {
|
||||||
|
title: new URL("@/assets/imgs/home/title_ko.png", import.meta.url).href,
|
||||||
|
button: new URL("@/assets/imgs/home/button_ko.png", import.meta.url).href,
|
||||||
|
intro: new URL("@/assets/imgs/home/intro_ko.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
// 以下為您提供的譯文,已修正格式
|
||||||
|
game: {
|
||||||
|
agree: "매우 그렇다",
|
||||||
|
disagree: "매우 그렇지 않다",
|
||||||
|
},
|
||||||
|
questionData: {
|
||||||
|
DimensionName: {
|
||||||
|
"Learning_Agility": "학습민첩성",
|
||||||
|
"Resilience": "회복탄력성",
|
||||||
|
"Empathy": "공감",
|
||||||
|
"Ambition": "야심",
|
||||||
|
"Judgement": "판단력"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"A1": "나는 끊임없이 내 한계를 탐색하며, 컴포트 존을 벗어나 새로운 시도와 경험에 과감하게 도전한다.",
|
||||||
|
"A2": "나는 적극적으로 피드백을 구하고 경청하며, 끊임없이 나 자신을 성장시킨다.",
|
||||||
|
"A3": "나는 위기를 전환점으로 여기고, 실패를 학습의 좋은 기회로 삼는다.",
|
||||||
|
"A4": "나는 호기심이 넘치며, 스스로 다양한 학습 자원을 찾아 적극적으로 활용한다.",
|
||||||
|
"A5": "나는 경험을 통해 배우는 것을 두려워하지 않으며, 성공이 보장되지 않더라도 새로운 방법을 시도하는 데 최선을 다한다.",
|
||||||
|
|
||||||
|
"B1": "나는 좌절하거나 실패의 책임을 지는 것에 주저하지 않는다.",
|
||||||
|
"B2": "나는 스트레스를 잘 관리하고, 좌절감에서 빠르게 벗어나 긍정적이고 낙관적인 태도를 유지한다.",
|
||||||
|
"B3": "나는 어려움이 닥쳐도 쉽게 포기하지 않으며, 도전을 통해 얻는 동기부여와 영감을 적극적으로 찾고 즐긴다.",
|
||||||
|
"B4": "나는 상황이 불투명할 때도 냉정한 태도를 유지하며, 꾸준히 노력해서 성공을 향해 나아간다.",
|
||||||
|
"B5": "나는 피할 수 없는 실패를 겸허히받아들이고, 상황을 한 발 물러서서 바라보며 다음 기회를 위해 최선을 다해 준비한다.",
|
||||||
|
|
||||||
|
"C1": "나는 다른 사람들과의 관계를 잘 형성하며, 팀 간의 콜라보레이션을 북돋는다.",
|
||||||
|
"C2": "나는 언제나 진정성 있게 사람들을 대하고, 존중과 감사를 직접 표현한다.",
|
||||||
|
"C3": "나는 소비자 행동 뒤에 있는 동기와 심리를 이해하고, 이를 마케팅 전략에 적용할 수 있다.",
|
||||||
|
"C4": "나는 사람들에게 친절하게 대하며 언제나 배려심을 갖고, 타인의 생각과 감정을 이해하기 위해 적극적으로 경청하려고 한다.",
|
||||||
|
"C5": "나는 부정적인 감정을 잘 억제하고 효과적으로 관리하며, 다른 사람들에게 부정적인 영향이 가지 않도록 노력한다.",
|
||||||
|
|
||||||
|
"D1": "나는 장기적으로 상황을 바라볼 줄 아는 안목이 있고 새로운 트렌드를 잘 포착하며, 창의적인 통찰과 비전을 갖고 있다.",
|
||||||
|
"D2": "나는 야심이 넘치고 높은 포부를 지니며, 스스로에게 매우 도전적인 목표를 설정한다.",
|
||||||
|
"D3": "나는 강한 주도성을 갖추고 있으며, 오너십을 가지고 협업을 이끌어 아이디어와 프로젝트를 적극적으로 추진한다.",
|
||||||
|
"D4": "탁월한 야심으로 변화를 이끌며, 이해관계자들의 목표에 부합하는 혁신적인 목표를 설정하고, 언제나 정직한 태도를 유지한다.",
|
||||||
|
"D5": "나는 탁월함을 추구하며 현상유지를 하거나 현재에 안주하지 않고, 언제나 개선의 기회와 돌파구를 모색한다.",
|
||||||
|
|
||||||
|
"E1": "나는 항상 효율성에 신경 쓰며, 과정을 간소화하고 핵심에 집중하는 데 뛰어나다.",
|
||||||
|
"E2": "나는 문제의 근원을 잘 파악하고, 복잡한 난관을 차근차근 풀어내며 해결책을 모색한다.",
|
||||||
|
"E3": "나는 언제나 우선순위를 명확히 정하고, 핵심 과제에 전력을 집중하여 해결책을 마련한다.",
|
||||||
|
"E4": "나는 건설적인 방식으로 타인의 아이디어에 이의를 제기하고, 더욱 발전할 수 있도록 개선책을 제시한다.",
|
||||||
|
"E5": "나는 장·단기 이익 및 중요한 세부 사항을 균형 있게 고려하여 종합적으로 사고하고, 분석과 직관을 효과적으로 결합해 핵심적인 의사결정을 신속하게 내린다."
|
||||||
|
},
|
||||||
|
trait: {
|
||||||
|
Pioneer: "개척자",
|
||||||
|
"Knowledge_Seeker": "지식 줍줍러",
|
||||||
|
"Change_Pioneer": "변화의 주인공",
|
||||||
|
"Curious_Mind": "궁금한 거 못참음",
|
||||||
|
"Trial_Warrior": "겁없는 도전러",
|
||||||
|
|
||||||
|
"Giants_Shoulders": "거인의 어깨 위",
|
||||||
|
"Stress_Master": "멘탈 갑",
|
||||||
|
"Roly-Poly": "오뚝이",
|
||||||
|
"Steady_Anchor": "인생 3회차",
|
||||||
|
"Planner": "파워 J",
|
||||||
|
|
||||||
|
"Communication_Expert": "입으로 먹고 삼",
|
||||||
|
"Sincere_Partner": "믿고 맡기는 찐친 바이브",
|
||||||
|
"Mind_Hunter": "마인드 헌터",
|
||||||
|
"Active_Listener": "리액션 장인 경청러",
|
||||||
|
"Emotion_Management_Master": "감정기복 ZERO",
|
||||||
|
|
||||||
|
"Future_Prophet": "미래 각도기",
|
||||||
|
"Helmsman": "8톤트럭 운전수",
|
||||||
|
"Owner": "책임감 MAX",
|
||||||
|
"Strategic_Leader": "전략가 타입",
|
||||||
|
"Perfectionist": "완벽주의 ON",
|
||||||
|
|
||||||
|
"Simplifier": "복잡한 거 시러시러",
|
||||||
|
"Problem_Sniper": "문제풀이 덕후",
|
||||||
|
"Eagle_Eye": "디테일 덕후",
|
||||||
|
"Thought_Challenger": "물음표 킬러",
|
||||||
|
"Rational_and_Intuitive": "좌뇌 우뇌 밸런스 굿"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
personalInfo: {
|
||||||
|
title: '성실히 답변해 주셔서 감사합니다\n이제 마지막 단계입니다',
|
||||||
|
placeholder: {
|
||||||
|
username: "닉네임을 입력하세요",
|
||||||
|
age: "나이를 입력하세요",
|
||||||
|
gender: "성별을 선택하세요",
|
||||||
|
},
|
||||||
|
gender: {
|
||||||
|
male: "남성",
|
||||||
|
female: "여성",
|
||||||
|
},
|
||||||
|
error: { // 參照 placeholder 補全
|
||||||
|
username: "닉네임을 입력하세요",
|
||||||
|
age: "나이를 입력하세요",
|
||||||
|
gender: "성별을 선택하세요",
|
||||||
|
},
|
||||||
|
submit: new URL("@/assets/imgs/personalInfo/submit_ko.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
title: "당신의 다섯가지 잠재력 (5 Potentials) 점수",
|
||||||
|
subtitle: "이 리포트는 당신의 잠재력 분포 비율을 보여주며\n핵심 강점과 보완점을 파악하는 데 도움을 줍니다",
|
||||||
|
tagLabel: "다음과 같은 잠재력 태그를 획득했습니다:",
|
||||||
|
footnote: "태그 조합 희소도*",
|
||||||
|
footnote2: "*잠재력 태그 조합이 나타날 확률은 다음을 초과하지 않습니다",
|
||||||
|
rare: {
|
||||||
|
"SSR": "SSR",
|
||||||
|
"UR": "UR",
|
||||||
|
},
|
||||||
|
onlyForYou: "*참고용",
|
||||||
|
slideDown: "아래로 스크롤하여 로레알 5대 잠재력에 대한 정의를 확인하고\n최종 리포트를 받아보세요",
|
||||||
|
slideUp: new URL("@/assets/imgs/result/slide-up-notice_ko.png", import.meta.url).href,
|
||||||
|
ratingExplanation: new URL("@/assets/imgs/result/rating-explanation_ko.png", import.meta.url).href,
|
||||||
|
actions: {
|
||||||
|
tryAgain: "다시 테스트하기",
|
||||||
|
generateReport: "개인 리포트 생성",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
title: "님의 핵심 잠재력은",
|
||||||
|
dimDescription: {
|
||||||
|
Learning_Agility: dedent`
|
||||||
|
당신은 타고난 개척자로서 세상에 대한 무한한 호기심을 지니고 있으며, 호기심 많은 아이처럼 미지의 영역을 탐구하려는 열망으로 가득합니다. 또한, 새로운 방법을 끊임없이 시도해 보는 대담함을 갖추고 있습니다.
|
||||||
|
당신은 지식을 추구하는 동시에 변혁의 선봉장입니다. 민첩하게 배우고 변화에 적응하며, 성공과 실패를 통해 경험을 축적해 끊임없이 자신을 발전시킵니다. 시도와 실패를 두려워하지 않으며, 학습에서 얻은 성과를 기꺼이 공유하여 긍정적인 학습 분위기를 조성합니다.
|
||||||
|
`,
|
||||||
|
Resilience: dedent`
|
||||||
|
당신은 강인한 마음과 스트레스를 이겨내는 능력을 갖춘, 오뚝이 같은 존재입니다. 좌절을 겪어도 언제나 긍정적이고 낙관적인 태도를 유지합니다.
|
||||||
|
어려움에 쉽게 무너지지 않으며, 빠르게 마음가짐을 갖추고 경험을 흡수해 팀의 든든한 버팀목으로서 최적의 해결책을 찾아냅니다. 늘 낙천적이고 적극적인 태도를 유지하며, 기꺼이 책임을 지고 팀원들을 독려하여 함께 난관을 극복해 나가는 믿음직한 동료입니다.
|
||||||
|
`,
|
||||||
|
Empathy: dedent`
|
||||||
|
당신은 타고난 소통 능력을 지닌 전문가로, 뛰어난 공감 능력과 예리한 통찰력을 갖추어 타인의 생각과 감정을 깊이 이해할 수 있습니다. 팀 내에서 없어서는 안 될 '마인드 헌터'로서, 상대방의 진정한 의도를 파악하고 효과적으로 문제를 해결하는 데 큰 기여를 합니다.
|
||||||
|
당신은 진심 어린 태도로 사람들을 대하며, 경청을 잘하기 때문에 다양한 사람들과 원만한 관계를 형성할 수 있습니다. 또한, 소비자의 니즈와 문제점을 빠르게 포착하여 서로에게 이익이 되는 결과를 이끌어냅니다. 당신은 모든 사람을 이해하고 존중하는 데에서 진정한 성공이 비롯된다고 믿습니다.
|
||||||
|
`,
|
||||||
|
Ambition: dedent`
|
||||||
|
당신은 탁월한 통찰력과 전략적 리더십을 갖춘, 미래지향적인 리더로서 '감히 도전하고 한계를 뛰어넘는' 정신을 지니고 있습니다.
|
||||||
|
원대한 이상과 포부를 품고 그 목표를 위해 끊임없이 노력하며, 현 상황에 결코 안주하지 않습니다.
|
||||||
|
자신을 끊임없이 시험하고 한계를 뛰어넘으며, 새로운 가능성을 적극 탐색합니다. 마치 배의 키를 잡은 선장처럼 거센 물결 속에서도 팀을 이끌어 나가며, 궁극적으로 목표를 향해 나아가도록 돕습니다. 또한, 강한 오너십을 가지고 있어 이를 통해 무한한 가능성을 만들어낼 수 있다고 믿습니다.
|
||||||
|
`,
|
||||||
|
Judgement: dedent`
|
||||||
|
당신은 이성과 감성을 겸비한 결정권자로서, 뛰어난 판단력과 결정 능력을 지니고 있습니다. 복잡하고 빠르게 변하는 상황에서도 문제를 신속하게 분석하고 핵심 정보를 포착해 현명한 결론을 도출해 냅니다.
|
||||||
|
당신은 ‘문제 저격수’로서 복잡한 사안을 간결하게 정리하고, 멀리 내다보는 안목을 갖추고 있습니다. 직관과 이성을 균형 있게 활용하여 이득과 손실을 고루 따지며, 팀을 성공으로 이끄는 추진력을 발휘합니다.
|
||||||
|
`
|
||||||
|
},
|
||||||
|
top: new URL("@/assets/imgs/report/top.png", import.meta.url).href,
|
||||||
|
bottom: new URL("@/assets/imgs/report/bottom_ko.png", import.meta.url).href,
|
||||||
|
bottomLeft: new URL("@/assets/imgs/report/bottom-left_ko.png", import.meta.url).href,
|
||||||
|
bottomRight: new URL("@/assets/imgs/report/bottom-right_ko.png", import.meta.url).href,
|
||||||
|
shareMask: new URL("@/assets/imgs/report/share-mask_ko.png", import.meta.url).href,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ja: typeof zh_CN = {
|
||||||
|
// 參照 zh 物件補全,並將圖片路徑改為 ja
|
||||||
|
switchLang: new URL("@/assets/imgs/switch-lang_ja.png", import.meta.url).href,
|
||||||
|
you: "あなた",
|
||||||
|
home: {
|
||||||
|
title: new URL("@/assets/imgs/home/title_ja.png", import.meta.url).href,
|
||||||
|
button: new URL("@/assets/imgs/home/button_ja.png", import.meta.url).href,
|
||||||
|
intro: new URL("@/assets/imgs/home/intro_ja.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
// 以下為您提供的譯文,已修正格式
|
||||||
|
game: {
|
||||||
|
agree: "同意する",
|
||||||
|
disagree: "同意しない",
|
||||||
|
},
|
||||||
|
questionData: {
|
||||||
|
DimensionName: {
|
||||||
|
"Learning_Agility": "学ぶ意欲",
|
||||||
|
"Resilience": "忍耐強さ",
|
||||||
|
"Empathy": "共感力",
|
||||||
|
"Ambition": "向上心",
|
||||||
|
"Judgement": "判断力"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"A1": "自分の限界に挑み、心地よい環境から踏み出して新しいことや経験に挑戦するよう努めています。",
|
||||||
|
"A2": "積極的にフィードバックを求め、継続的成長を目指しています。",
|
||||||
|
"A3": "危機をチャンスと捉え、失敗を学習の機会と考えています。",
|
||||||
|
"A4": "好奇心に満ちあふれ、学ぶ機会を積極的に探しています。",
|
||||||
|
"A5": "成功の保証がなくても実践を通じて学び、新しい方法を試す勇気を持っています。",
|
||||||
|
"B1": "失敗や挫折に対して向き合い、責任を負います。",
|
||||||
|
"B2": "ストレス耐性が強く、挫折から素早く立ち直り、前向きな姿勢を保ちます。",
|
||||||
|
"B3": "困難に直面しても簡単にはあきらめず、挑戦の中にモチベーションとインスピレーションを見出します。",
|
||||||
|
"B4": "不確かな状況でも冷静さを失わず、成功へと導きます。",
|
||||||
|
"B5": "避けられない失敗を受け入れ、広い視野を保ち、次のアクションを計画する術を知っています。",
|
||||||
|
"C1": "他者とのつながりを築くのが得意で、チーム間のコラボレーションを醸成します。",
|
||||||
|
"C2": "常に誠実に相手と向き合い、尊敬と感謝の気持ちを率直に伝えることができます。",
|
||||||
|
"C3": "消費者行動の背景にある動機や心理を理解し、それをマーケティング戦略等に活かすことができます。",
|
||||||
|
"C4": "友好的で思いやりをもち、相手の考えや感情を理解するために積極的に傾聴することができます。",
|
||||||
|
"C5": "自分のネガティブな感情をコントロールし、周りに悪影響を及ぼさないよう適切に感情コントロールできます。",
|
||||||
|
"D1": "長期的なビジョンを持ち、新たなトレンドを見極め、創造的な洞察やビジョンを共有することに長けています。",
|
||||||
|
"D2": "大きな向上心を持ち、自分自身に簡単には達成できないような目標を設定します。",
|
||||||
|
"D3": "強い主体性を発揮し、当事者意識をもって協働をリードし、アイデアやプロジェクトを前進させます。",
|
||||||
|
"D4": "大志をもって変革をリードし、ステークホルダーの視座に合わせた目標を設定し、誠実さを保って遂行します。",
|
||||||
|
"D5": "卓越性を追求し、現状に甘んじることなく、常に改善と飛躍の機会を模索します。",
|
||||||
|
"E1": "常に効率性に着目したプロセスの簡素化や、重要なポイントを明確化することに長けています。",
|
||||||
|
"E2": "複雑な問題であっても、根本原因を突き止め、解決策を追求するのが得意です。",
|
||||||
|
"E3": "優先順位を見極め、重要な課題に集中することができます。",
|
||||||
|
"E4": "建設的な方法で他者のアイデアに異論を申し立て、改善を促す勇気を持っています。",
|
||||||
|
"E5": "包括的に物事を捉え、長期的・短期的な利益と細部の状況の見通しを両立させ、論理と直感を効果的に組み合わせて迅速かつ的確な意思決定を行います。"
|
||||||
|
},
|
||||||
|
trait: {
|
||||||
|
"Pioneer": "先駆者",
|
||||||
|
"Knowledge_Seeker": "知識探求者",
|
||||||
|
"Change_Pioneer": "変革の先駆者",
|
||||||
|
"Curious_Mind": "好奇心旺盛",
|
||||||
|
"Trial_Warrior": "挑戦の戦士",
|
||||||
|
"Giants_Shoulders": "巨人の肩",
|
||||||
|
"Stress_Master": "ストレスマスター",
|
||||||
|
"Roly-Poly": "起き上がりこぼし",
|
||||||
|
"Steady_Anchor": "頼れる錨",
|
||||||
|
"Planner": "プランナー",
|
||||||
|
"Communication_Expert": "コミュニケーションの達人",
|
||||||
|
"Sincere_Partner": "誠実なパートナー",
|
||||||
|
"Mind_Hunter": "マインドハンター",
|
||||||
|
"Active_Listener": "傾聴者",
|
||||||
|
"Emotion_Management_Master": "感情マネジメントの達人",
|
||||||
|
"Future_Prophet": "未来の預言者",
|
||||||
|
"Helmsman": "舵取り役",
|
||||||
|
"Owner": "オーナー",
|
||||||
|
"Strategic_Leader": "戦略的リーダー",
|
||||||
|
"Perfectionist": "完璧主義者",
|
||||||
|
"Simplifier": "簡素化の達人",
|
||||||
|
"Problem_Sniper": "問題解決者",
|
||||||
|
"Eagle_Eye": "イーグルアイ",
|
||||||
|
"Thought_Challenger": "思考の挑戦者",
|
||||||
|
"Rational_and_Intuitive": "論理的かつ直感的"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
personalInfo: {
|
||||||
|
title: "回答にご協力いただきありがとうございます。\nこれが最後のステップです。",
|
||||||
|
placeholder: {
|
||||||
|
username: "あなたのニックネームは",
|
||||||
|
age: "あなたの年齢は",
|
||||||
|
gender: "性別を選択してください"
|
||||||
|
},
|
||||||
|
gender: {
|
||||||
|
male: "男性",
|
||||||
|
female: "女性"
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
username: "ニックネームを入力してください",
|
||||||
|
age: "年齢を入力してください",
|
||||||
|
gender: "性別を選択してください"
|
||||||
|
},
|
||||||
|
submit: new URL("@/assets/imgs/personalInfo/submit_ja.png", import.meta.url).href,
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
title: "あなたの5つのポテンシャルスコア",
|
||||||
|
subtitle: "このレポートは、あなたの各ポテンシャルの分布比率を示し、\nあなたの強みと改善点を理解するのに役立ちます",
|
||||||
|
tagLabel: "次のポテンシャルタグを取得しました:",
|
||||||
|
footnote: "タグ組み合わせのレア度*",
|
||||||
|
footnote2: "*タグの組み合わせの出現率は以下です",
|
||||||
|
rare: {
|
||||||
|
"SSR": "SSR",
|
||||||
|
"UR": "UR",
|
||||||
|
},
|
||||||
|
onlyForYou: "※参考用",
|
||||||
|
slideDown: "下にスクロールしてロレアルの5つのポテンシャルの定義を確認し、\n最終レポートを入手してください",
|
||||||
|
slideUp: new URL("@/assets/imgs/result/slide-up-notice_ja.png", import.meta.url).href,
|
||||||
|
ratingExplanation: new URL("@/assets/imgs/result/rating-explanation_ja.png", import.meta.url).href,
|
||||||
|
actions: {
|
||||||
|
tryAgain: "もう一度試す",
|
||||||
|
generateReport: "個人レポートを作成",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
title: "のコアポテンシャルは",
|
||||||
|
dimDescription: {
|
||||||
|
Learning_Agility: dedent`
|
||||||
|
あなたは生まれながらのパイオニアであり、世界に対する強い好奇心を持っています。未知を追い求める探求心旺盛な探検家のように、常に新しいアプローチに挑戦する勇気を備えています。
|
||||||
|
また、知識を追求する探究者であり、変化をリードする先駆者でもあります。俊敏に学び、変化に順応し、成功と失敗の両面から教訓を得て自らを絶えず向上させることができます。勇敢な実験者として、学んだことを共有し、学習に対する前向きな雰囲気をつくり出すのが得意です。
|
||||||
|
`,
|
||||||
|
Resilience: dedent`
|
||||||
|
あなたは揺るぎない力を持ち、内面の強さとストレス耐性を兼ね備え、困難に直面しても常にポジティブで前向きな姿勢を保つことができます。
|
||||||
|
挑戦に容易には屈せず、素早くマインドセットを切り替えて経験から学ぶ力があります。まるでチームにとっての錨のように、問題解決の最善策を見出し、常に前向きな姿勢で責任を勇敢に引き受け、周囲を鼓舞して共に障害を乗り越えていきます。その結果、あなたはチームから信頼される頼れるパートナーとなっています。
|
||||||
|
`,
|
||||||
|
Empathy: dedent`
|
||||||
|
あなたは生まれながらに卓越したコミュニケーション能力と深い共感力、鋭い洞察力を兼ね備えており、他者の本音や感情を的確に把握できるため、チームにとって欠かせない「心を読む人」となっています。
|
||||||
|
常に誠実に人と向き合い、優れた傾聴力を活かしてさまざまな人々との強固な関係を築くことができます。そうして消費者のニーズや課題を正確に見極め、最終的に双方にとって利益のある状況を生み出すのです。真の成功とはすべての人を理解し尊重することにあるとあなたは信じています。
|
||||||
|
`,
|
||||||
|
Ambition: dedent`
|
||||||
|
あなたは先見性ある戦略的リーダーであり、“限界を超える”精神を体現しています。壮大な理想や抱負を胸に抱き、それを成し遂げるために惜しみない努力を続けます。
|
||||||
|
現状に満足することなく、自らを果敢に挑戦へと駆り立て、限界を常に打ち破りながら新たな可能性を積極的に模索しています。まるで嵐の海を進む舵取り役のように、チームを目標へと導きます。強いオーナーシップを発揮するあなたは、その力が無限の可能性を生み出すと強く信じています。
|
||||||
|
`,
|
||||||
|
Judgement: dedent`
|
||||||
|
あなたは理性と感性を兼ね備えた意思決定者であり、優れた判断力と決断力を持っています。複雑で変化の多い状況でも問題を素早く分析し、重要な情報を正確に把握することができます。
|
||||||
|
まさに“問題解決者”とも呼べる存在で、複雑さを簡潔に整理し、先を見通して直感と論理のバランスを取りながら、あらゆる利点と欠点をしっかり考慮してチームを成功へ導きます。
|
||||||
|
`
|
||||||
|
},
|
||||||
|
top: new URL("@/assets/imgs/report/top.png", import.meta.url).href,
|
||||||
|
bottom: new URL("@/assets/imgs/report/bottom_ja.png", import.meta.url).href,
|
||||||
|
bottomLeft: new URL("@/assets/imgs/report/bottom-left_ja.png", import.meta.url).href,
|
||||||
|
bottomRight: new URL("@/assets/imgs/report/bottom-right_ja.png", import.meta.url).href,
|
||||||
|
shareMask: new URL("@/assets/imgs/report/share-mask_ja.png", import.meta.url).href,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MessageSchema = typeof zh_CN
|
||||||
|
|
||||||
|
export default {
|
||||||
|
zh_CN,
|
||||||
|
zh_TW,
|
||||||
|
en,
|
||||||
|
ja,
|
||||||
|
ko
|
||||||
|
}
|
||||||
60
src/main.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import './assets/fonts/stylesheet.css'
|
||||||
|
import './style.scss'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
|
||||||
|
import lang from './locates'
|
||||||
|
import type { MessageSchema } from './locates'
|
||||||
|
import imgs from './assets/imgs';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const userLang = navigator.language || navigator.userLanguage;
|
||||||
|
console.log("Detected language:", userLang);
|
||||||
|
|
||||||
|
// 语言检测逻辑
|
||||||
|
let language = 'en'; // 默认语言
|
||||||
|
if (userLang) {
|
||||||
|
const langCode = userLang.toLowerCase();
|
||||||
|
if (langCode.startsWith('zh-cn') || langCode === 'zh') {
|
||||||
|
language = 'zh_CN';
|
||||||
|
} else if (langCode.startsWith('zh-tw') || langCode.startsWith('zh-hk')) {
|
||||||
|
language = 'zh_TW';
|
||||||
|
} else if (langCode.startsWith('ja')) {
|
||||||
|
language = 'ja';
|
||||||
|
} else if (langCode.startsWith('ko')) {
|
||||||
|
language = 'ko';
|
||||||
|
} else if (langCode.startsWith('en')) {
|
||||||
|
language = 'en';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Selected language:", language);
|
||||||
|
|
||||||
|
const i18n = createI18n<[MessageSchema], 'en' | 'zh_CN' | 'zh_TW' | 'ja' | 'ko'>({
|
||||||
|
locale: language,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
// @ts-ignore
|
||||||
|
messages: lang
|
||||||
|
})
|
||||||
|
|
||||||
|
const flattenNestedObject = (obj: any) => {
|
||||||
|
const flatten = (item: any): string[] | string => {
|
||||||
|
if (typeof item == 'string')
|
||||||
|
return item;
|
||||||
|
else
|
||||||
|
return Object.values(item).map(flatten).flat();
|
||||||
|
};
|
||||||
|
return Object.values(obj).map(flatten).flat();
|
||||||
|
};
|
||||||
|
|
||||||
|
flattenNestedObject(imgs).forEach(img => {
|
||||||
|
let link = document.createElement('link');
|
||||||
|
link.rel = 'preload';
|
||||||
|
link.href = img;
|
||||||
|
link.as = 'image';
|
||||||
|
document.head.appendChild(link);
|
||||||
|
});
|
||||||
|
|
||||||
|
createApp(App).use(i18n).mount('#app')
|
||||||
35
src/mixin.scss
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
@mixin flex-center($gap) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin abs-pos($x, $y, $scale: 1) {
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: center;
|
||||||
|
|
||||||
|
@if $y < 0 {
|
||||||
|
bottom: -$y;
|
||||||
|
transform: translateX(-50%) scale($scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
top: $y;
|
||||||
|
transform: translate(-50%, -50%) scale($scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
left: $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin font($font-size, $line-height, $white-space: normal, $text-align: start) {
|
||||||
|
font-size: $font-size;
|
||||||
|
line-height: $line-height;
|
||||||
|
white-space: $white-space;
|
||||||
|
text-align: $text-align;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@mixin full-size {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
150
src/style.scss
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
@use './mixin.scss' as *;
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
color: white;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
button {
|
||||||
|
font-family: 'LOREAL Essentielle', 'AR NewcuheiGB';
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number]::-webkit-inner-spin-button,
|
||||||
|
input[type=number]::-webkit-outer-spin-button {
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 60vh;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: 0 0 16px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.page {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
@include full-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
@include full-size;
|
||||||
|
|
||||||
|
/* img {
|
||||||
|
@include full-size;
|
||||||
|
object-fit: cover;
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.7s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-down {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-30%);
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translateY(10%);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(-30%);
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-up {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-30%) rotate(-180deg);
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translateY(10%) rotate(-180deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(-30%) rotate(-180deg);
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and ((max-width: 412px) or (max-height: 732px)) {
|
||||||
|
:root {
|
||||||
|
font-size: 13.4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes growing-bg-animation {
|
||||||
|
0% {
|
||||||
|
filter: brightness(1) contrast(1) saturate(1) blur(2px);
|
||||||
|
transform: scale(1) translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
filter: brightness(1.05) contrast(1.05) saturate(1.05) blur(6px);
|
||||||
|
transform: scale(1.1) translate3d(3px, 3px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
filter: brightness(1) contrast(1) saturate(1) blur(6px);
|
||||||
|
transform: scale(1) translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
68% {
|
||||||
|
filter: brightness(.95) contrast(.95) saturate(.95) blur(4px);
|
||||||
|
transform: scale(1.05) translate3d(-5px, -5px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
79% {
|
||||||
|
filter: brightness(.95) contrast(.95) saturate(.95) blur(4px);
|
||||||
|
transform: scale(1.02) translate3d(2px, 2px, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
filter: brightness(1) contrast(1) saturate(1) blur(2px);
|
||||||
|
transform: scale(1) translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.growing-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: -4px;
|
||||||
|
top: -4px;
|
||||||
|
width: calc(100% + 8px);
|
||||||
|
height: calc(100% + 8px);
|
||||||
|
object-fit: cover;
|
||||||
|
animation: growing-bg-animation 8s ease infinite;
|
||||||
|
}
|
||||||
560
src/utils.ts
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
/**
|
||||||
|
* @file utils.ts
|
||||||
|
* @description 这个文件包含了一些实用的函数和一个简化 Canvas 操作的类 EasyCanvas。
|
||||||
|
* @version 1.0.0
|
||||||
|
* @date 2024-08-31
|
||||||
|
* @author feie9454
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Darkens a hex color by a given percentage
|
||||||
|
* @param hex - Hex color code (e.g. '#00D78D' or '00D78D')
|
||||||
|
* @param percentage - Amount to darken by (0-100)
|
||||||
|
* @returns Darkened hex color
|
||||||
|
*/
|
||||||
|
export function darken(hex: string, percentage: number = 20): string {
|
||||||
|
// Remove # if present
|
||||||
|
hex = hex.replace('#', '');
|
||||||
|
|
||||||
|
if (hex.length !== 6) {
|
||||||
|
throw new Error('Invalid hex color format');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure percentage is between 0 and 100
|
||||||
|
percentage = Math.min(100, Math.max(0, percentage));
|
||||||
|
|
||||||
|
// Convert hex to RGB
|
||||||
|
const r = parseInt(hex.substring(0, 2), 16);
|
||||||
|
const g = parseInt(hex.substring(2, 4), 16);
|
||||||
|
const b = parseInt(hex.substring(4, 6), 16);
|
||||||
|
|
||||||
|
// Darken each component
|
||||||
|
const darkFactor = (100 - percentage) / 100;
|
||||||
|
const newR = Math.floor(r * darkFactor);
|
||||||
|
const newG = Math.floor(g * darkFactor);
|
||||||
|
const newB = Math.floor(b * darkFactor);
|
||||||
|
|
||||||
|
// Convert back to hex
|
||||||
|
const toHex = (n: number): string => {
|
||||||
|
const hex = Math.max(0, Math.min(255, n)).toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
return `#${toHex(newR)}${toHex(newG)}${toHex(newB)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在每一帧调用指定的回调函数,直到达到指定的帧数。
|
||||||
|
* @param {(index: number, total: number) => void} callback - 每一帧调用的回调函数。
|
||||||
|
* @param {number} frameCount - 总帧数。
|
||||||
|
*/
|
||||||
|
export function forEachFrame(callback: (index: number, total: number) => void, frameCount: number) {
|
||||||
|
if (frameCount <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let index = 0;
|
||||||
|
const total = frameCount;
|
||||||
|
const f = () => {
|
||||||
|
callback(index, total);
|
||||||
|
index++;
|
||||||
|
if (index < total) {
|
||||||
|
requestAnimationFrame(f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requestAnimationFrame(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在指定的毫秒数内,每一帧调用指定的回调函数。
|
||||||
|
* @param {(elapsedTime: number, totalTime: number) => void} callback - 每一帧调用的回调函数。
|
||||||
|
* @param {number} milliseconds - 总时间(毫秒)。
|
||||||
|
*/
|
||||||
|
export function forEachFrameInMilliseconds(callback: (elapsedTime: number, totalTime: number) => void, milliseconds: number) {
|
||||||
|
let startTime = Date.now();
|
||||||
|
let totalTime = milliseconds
|
||||||
|
const f = () => {
|
||||||
|
let elapsedTime = Date.now() - startTime;
|
||||||
|
if (elapsedTime < totalTime) {
|
||||||
|
callback(elapsedTime, totalTime);
|
||||||
|
requestAnimationFrame(f);
|
||||||
|
} else {
|
||||||
|
callback(totalTime, totalTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
requestAnimationFrame(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个线性到三次贝塞尔曲线的转换函数。
|
||||||
|
* @param {[number, number, number, number]} [f=[0.42, 0, 0.58, 1]] - 贝塞尔曲线参数。
|
||||||
|
* @returns {(t: number) => number} 转换函数。
|
||||||
|
*/
|
||||||
|
export function createLinear2CubicBezier(f: [number, number, number, number] = [0.42, 0, 0.58, 1]): (t: number) => number {
|
||||||
|
return (t: number) => {
|
||||||
|
return linear2CubicBezier(t, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将线性值转换为三次贝塞尔曲线值。
|
||||||
|
* @param {number} source - 源值(0 到 1 之间)。
|
||||||
|
* @param {[number, number, number, number]} [f=[0.42, 0, 0.58, 1]] - 贝塞尔曲线参数。
|
||||||
|
* @returns {number} 转换后的值。
|
||||||
|
* @throws {Error} 如果源值不在 0 到 1 之间,抛出错误。
|
||||||
|
*/
|
||||||
|
export function linear2CubicBezier(source: number, f: [number, number, number, number] = [0.42, 0, 0.58, 1]): number {
|
||||||
|
if (source < 0 || source > 1) {
|
||||||
|
throw new Error("Source must be between 0 and 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = f;
|
||||||
|
|
||||||
|
const cubicBezier = (t: number, p0: number, p1: number, p2: number, p3: number) => {
|
||||||
|
const u = 1 - t;
|
||||||
|
return (u * u * u * p0) + (3 * u * u * t * p1) + (3 * u * t * t * p2) + (t * t * t * p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
const solveCubicBezierX = (xTarget: number, epsilon = 0.00001) => {
|
||||||
|
let t = xTarget;
|
||||||
|
let x = cubicBezier(t, 0, x1, x2, 1);
|
||||||
|
let iteration = 0;
|
||||||
|
|
||||||
|
while (Math.abs(x - xTarget) > epsilon && iteration < 100) {
|
||||||
|
const d = 3 * (1 - t) * (1 - t) * (x1 - 0) + 6 * (1 - t) * t * (x2 - x1) + 3 * t * t * (1 - x2);
|
||||||
|
if (d === 0) break;
|
||||||
|
t -= (x - xTarget) / d;
|
||||||
|
x = cubicBezier(t, 0, x1, x2, 1);
|
||||||
|
iteration++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = solveCubicBezierX(source);
|
||||||
|
const y = cubicBezier(t, 0, y1, y2, 1);
|
||||||
|
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指定长度的随机十六进制字符串。
|
||||||
|
* @param {number} length - 字符串长度。
|
||||||
|
* @returns {string} 随机十六进制字符串。
|
||||||
|
*/
|
||||||
|
export function generateRandomHexString(length: number): string {
|
||||||
|
const characters = '0123456789abcdef';
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||||
|
result += characters[randomIndex];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EasyCanvas 类,用于简化 Canvas 操作。
|
||||||
|
*/
|
||||||
|
export class EasyCanvas {
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
statusMap = new Map<string, ImageData>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param {HTMLCanvasElement} canvas - Canvas 元素。
|
||||||
|
*/
|
||||||
|
constructor(canvas: HTMLCanvasElement) {
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.ctx = canvas.getContext("2d")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static checkSaveSupportMIME(mimetype: string,) {
|
||||||
|
return new Promise<boolean>(resolve => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
// 检查生成的 Blob 类型是否与指定的 mimetype 匹配
|
||||||
|
resolve(!!(blob && blob.type === mimetype));
|
||||||
|
}, mimetype);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getAvailableSaveTypes() {
|
||||||
|
const types = ['image/png', 'image/jpeg', 'image/webp', 'image/bmp'];
|
||||||
|
const results = await Promise.all(types.map(this.checkSaveSupportMIME));
|
||||||
|
return types.filter((_, index) => results[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文本按最大宽度进行换行,并返回每行文本的数组。
|
||||||
|
* @param {CanvasRenderingContext2D} ctx - Canvas 渲染上下文。
|
||||||
|
* @param {string} text - 要换行的文本。
|
||||||
|
* @param {number} maxWidth - 每行的最大宽度。
|
||||||
|
* @returns {string[]} 换行后的文本数组。
|
||||||
|
*/
|
||||||
|
static getWrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number, considerMarkInLineEnd = true): string[] {
|
||||||
|
const txtList = [];
|
||||||
|
const markList = [
|
||||||
|
",", ".", "!", "?", ";", ":", ",", "。", "!", "?", ";", ":",
|
||||||
|
"-", "–", "—", "(", ")", "[", "]", "{", "}", "<", ">", "‘", "’",
|
||||||
|
"“", "”", "\"", "'", "`", "~", "@", "#", "$", "%", "^", "&", "*",
|
||||||
|
"_", "+", "=", "|", "\\", "/", "、", "·", "…", "《", "》", "〈",
|
||||||
|
"〉", "「", "」", "『", "』", "【", "】", "〖", "〗", "(", ")",
|
||||||
|
"〔", "〕", "!", "?", "﹏", "﹋", "﹌", "﹍", "﹎", "﹏", "﹐",
|
||||||
|
"﹑", "﹒", "﹔", "﹖", "﹗", "﹟", "﹠", "﹡", "﹢", "﹣", "﹤",
|
||||||
|
"﹥", "﹦", "﹨", "﹩", "﹪", "﹫"
|
||||||
|
];
|
||||||
|
let str = "";
|
||||||
|
for (let i = 0, len = text.length; i < len; i++) {
|
||||||
|
str += text.charAt(i);
|
||||||
|
if (ctx.measureText(str).width > maxWidth) {
|
||||||
|
if (considerMarkInLineEnd && markList.includes(text.charAt(i))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txtList.push(str.substring(0, str.length - 1))
|
||||||
|
str = ""
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txtList.push(str)
|
||||||
|
return txtList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Canvas 上绘制文本。
|
||||||
|
* @param {string} font - 字体样式。
|
||||||
|
* @param {string} fillStyle - 填充样式。
|
||||||
|
* @param {string} text - 文本内容。
|
||||||
|
* @param {number} x - 文本的 x 坐标。
|
||||||
|
* @param {number} y - 文本的 y 坐标。
|
||||||
|
* @param {Object} [options] - 其他选项。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
fillText(font: string, fillStyle: string, text: string, x: number, y: number,
|
||||||
|
options?:
|
||||||
|
| { align?: "left" | "right" }
|
||||||
|
| { align?: "left" | "right", maxWidth: number, lineHeight: number }
|
||||||
|
| { align: "centerW", width: number }
|
||||||
|
| { align: "centerW", width: number, maxWidth: number, lineHeight: number }
|
||||||
|
| { align: "centerP" }
|
||||||
|
| { align: "centerP", maxWidth: number, lineHeight: number }
|
||||||
|
): this {
|
||||||
|
this.ctx.font = font;
|
||||||
|
this.ctx.fillStyle = fillStyle;
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
if ("align" in options && options.align == "right") {
|
||||||
|
if ("maxWidth" in options && "lineHeight" in options) {
|
||||||
|
EasyCanvas.getWrapText(this.ctx, text, options.maxWidth).forEach((str, index) => {
|
||||||
|
this.ctx.fillText(str, x - this.ctx.measureText(str).width, y + options.lineHeight * index)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.ctx.fillText(text, x - this.ctx.measureText(text).width, y)
|
||||||
|
}
|
||||||
|
} else if ("align" in options && options.align == "centerW") {
|
||||||
|
if ("maxWidth" in options && "lineHeight" in options) {
|
||||||
|
EasyCanvas.getWrapText(this.ctx, text, options.maxWidth).forEach((str, index) => {
|
||||||
|
let textWidth = this.ctx.measureText(str).width;
|
||||||
|
this.ctx.fillText(str, x + (options.width - textWidth) / 2, y + options.lineHeight * index)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let textWidth = this.ctx.measureText(text).width;
|
||||||
|
this.ctx.fillText(text, x + (options.width - textWidth) / 2, y)
|
||||||
|
}
|
||||||
|
} else if ("align" in options && options.align == "centerP") {
|
||||||
|
if ("maxWidth" in options && "lineHeight" in options) {
|
||||||
|
EasyCanvas.getWrapText(this.ctx, text, options.maxWidth).forEach((str, index) => {
|
||||||
|
let textWidth = this.ctx.measureText(str).width;
|
||||||
|
this.ctx.fillText(str, x - textWidth / 2, y + options.lineHeight * index)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let textWidth = this.ctx.measureText(text).width;
|
||||||
|
this.ctx.fillText(text, x - textWidth / 2, y)
|
||||||
|
}
|
||||||
|
} else if ("align" in options && options.align == "left" || !("align" in options)) {
|
||||||
|
if ("maxWidth" in options && "lineHeight" in options) {
|
||||||
|
EasyCanvas.getWrapText(this.ctx, text, options.maxWidth).forEach((str, index) => {
|
||||||
|
this.ctx.fillText(str, x, y + options.lineHeight * index)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.ctx.fillText(text, x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.ctx.fillText(text, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Canvas 上绘制图像。
|
||||||
|
* @param {CanvasImageSource} image - 图像源。
|
||||||
|
* @param {number} dx - 图像在目标 canvas 上的 x 坐标。
|
||||||
|
* @param {number} dy - 图像在目标 canvas 上的 y 坐标。
|
||||||
|
* @param {number} dWidth - 在目标 canvas 上绘制图像的宽度。
|
||||||
|
* @param {number} dHeight - 在目标 canvas 上绘制图像的高度。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
drawImage(image: CanvasImageSource, dx: number, dy: number, dWidth: number, dHeight: number): this {
|
||||||
|
this.ctx.drawImage(image, dx, dy, dWidth, dHeight);
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前 Canvas 状态。
|
||||||
|
* @param {string} statusName - 状态名称。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
saveStatus(statusName: string): this {
|
||||||
|
let canvasData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
this.statusMap.set(statusName, canvasData);
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复之前保存的 Canvas 状态。
|
||||||
|
* @param {string} statusName - 状态名称。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
restoreStatus(statusName: string): this {
|
||||||
|
if (this.statusMap.has(statusName)) {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
this.ctx.putImageData(this.statusMap.get(statusName)!, 0, 0);
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除保存的 Canvas 状态。
|
||||||
|
* @param {string} statusName - 状态名称。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
deleteStatus(statusName: string): this {
|
||||||
|
this.statusMap.delete(statusName);
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Canvas 上绘制线段。
|
||||||
|
* @param {...[number, number][]} points - 线段的点坐标。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
lineTo(...points: [number, number][]): this {
|
||||||
|
points.forEach(([x, y]) => this.ctx.lineTo(x, y));
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建路径。
|
||||||
|
* @param {...[number, number][]} points - 路径的点坐标。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
createPath(...points: [number, number][]): this {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
points.forEach(([x, y], index) => {
|
||||||
|
if (index === 0) this.ctx.moveTo(x, y);
|
||||||
|
this.ctx.lineTo(x, y)
|
||||||
|
});
|
||||||
|
this.ctx.closePath();
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制路径的轮廓。
|
||||||
|
* @param {string} strokeStyle - 轮廓样式。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
stroke(strokeStyle: string): this {
|
||||||
|
this.ctx.strokeStyle = strokeStyle;
|
||||||
|
this.ctx.stroke();
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充路径。
|
||||||
|
* @param {string} fillStyle - 填充样式。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
fill(fillStyle: string): this {
|
||||||
|
this.ctx.fillStyle = fillStyle;
|
||||||
|
this.ctx.fill();
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建动画。
|
||||||
|
* @param {(progress: number, p: this) => void} onFrame - 每一帧调用的回调函数。
|
||||||
|
* @param {Object} options - 动画选项。
|
||||||
|
* @param {number} options.duration - 动画持续时间。
|
||||||
|
* @param {(t: number) => number} options.easing - 缓动函数。
|
||||||
|
* @param {boolean} [options.autoRecover] - 是否自动恢复状态。
|
||||||
|
* @param {number} [options.delay] - 动画延迟时间。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
createAnimation(
|
||||||
|
onFrame: (progress: number, p: this) => void,
|
||||||
|
options: { duration: number, easing: (t: number) => number, autoRecover?: boolean, delay?: number }
|
||||||
|
): this {
|
||||||
|
const { duration, easing } = options;
|
||||||
|
let animationId = generateRandomHexString(16);
|
||||||
|
if (options.autoRecover) this.saveStatus(animationId);
|
||||||
|
setTimeout(() => {
|
||||||
|
forEachFrameInMilliseconds((index, total) => {
|
||||||
|
const progress = index / total;
|
||||||
|
if (options.autoRecover) this.restoreStatus(animationId);
|
||||||
|
onFrame(easing(progress), this);
|
||||||
|
}, duration);
|
||||||
|
}, options.delay);
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建图形。
|
||||||
|
* @param {Object} options - 图形选项。
|
||||||
|
* @param {[number, number]} options.centerPoint - 中心点坐标。
|
||||||
|
* @param {number} options.r - 半径。
|
||||||
|
* @param {string} options.fillStyle - 填充样式。
|
||||||
|
* @param {string} options.strokeStyle - 轮廓样式。
|
||||||
|
* @param {number} [options.startAngle] - 起始角度。
|
||||||
|
* @param {(index: number, domainCount: number) => number} [options.angleCal] - 角度计算函数。
|
||||||
|
* @param {...number[]} value - 值数组。
|
||||||
|
* @returns {this} 当前 EasyCanvas 实例。
|
||||||
|
*/
|
||||||
|
createGraph(options: { centerPoint: [number, number], r: number, fillStyle: string, strokeStyle: string, startAngle?: number, angleCal?: (index: number, domainCount: number) => number }, ...value: number[]): this {
|
||||||
|
const { centerPoint, r, strokeStyle, fillStyle } = options;
|
||||||
|
let points: [number, number][] = [];
|
||||||
|
const domainCount = value.length;
|
||||||
|
for (let i = 0; i < domainCount; i++) {
|
||||||
|
if (!options.angleCal) {
|
||||||
|
options.angleCal = (index: number, domainCount: number) => index / domainCount * Math.PI * 2;
|
||||||
|
}
|
||||||
|
if (!options.startAngle) {
|
||||||
|
options.startAngle = 0;
|
||||||
|
}
|
||||||
|
const angle = options.angleCal(i, domainCount) + options.startAngle;
|
||||||
|
const x = centerPoint[0] + r * Math.cos(angle) * value[i];
|
||||||
|
const y = centerPoint[1] + r * Math.sin(angle) * value[i];
|
||||||
|
points.push([x, y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.createPath(...points)
|
||||||
|
.fill(fillStyle)
|
||||||
|
.stroke(strokeStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class EasyAudio {
|
||||||
|
private static audioCtx: AudioContext | null = null;
|
||||||
|
private buffers: Map<string, AudioBuffer> = new Map();
|
||||||
|
private sources: Map<string, AudioBufferSourceNode> = new Map();
|
||||||
|
private audioOptions: Map<
|
||||||
|
string,
|
||||||
|
{ audioUrl: string; loop?: boolean; volume?: number }
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
|
private get audioCtx(): AudioContext {
|
||||||
|
if (!EasyAudio.audioCtx) {
|
||||||
|
EasyAudio.audioCtx = new AudioContext();
|
||||||
|
}
|
||||||
|
return EasyAudio.audioCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
audios?:
|
||||||
|
| { name: string; audioUrl: string | URL; loop?: boolean; volume?: number }
|
||||||
|
| { name: string; audioUrl: string | URL; loop?: boolean; volume?: number }[]
|
||||||
|
) {
|
||||||
|
if (audios) {
|
||||||
|
this.add(audios);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createAudioBuffer(audioUrl: string): Promise<AudioBuffer> {
|
||||||
|
const response = await fetch(audioUrl);
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
return await this.audioCtx.decodeAudioData(arrayBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadAudio(name: string) {
|
||||||
|
const options = this.audioOptions.get(name);
|
||||||
|
if (!options) {
|
||||||
|
throw new Error(`音频 ${name} 未找到`);
|
||||||
|
}
|
||||||
|
const buffer = await this.createAudioBuffer(options.audioUrl);
|
||||||
|
this.buffers.set(name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(): Promise<void> {
|
||||||
|
const promises = Array.from(this.audioOptions.keys()).map(name =>
|
||||||
|
this.loadAudio(name)
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(
|
||||||
|
audios:
|
||||||
|
| { name: string; audioUrl: string | URL; loop?: boolean; volume?: number }
|
||||||
|
| { name: string; audioUrl: string | URL; loop?: boolean; volume?: number }[]
|
||||||
|
): void {
|
||||||
|
if (Array.isArray(audios)) {
|
||||||
|
audios.forEach(audio => {
|
||||||
|
this.audioOptions.set(audio.name, {
|
||||||
|
audioUrl: typeof (audio.audioUrl) === "string" ? audio.audioUrl : audio.audioUrl.href,
|
||||||
|
loop: audio.loop,
|
||||||
|
volume: audio.volume,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.audioOptions.set(audios.name, {
|
||||||
|
audioUrl: typeof (audios.audioUrl) === "string" ? audios.audioUrl : audios.audioUrl.href,
|
||||||
|
loop: audios.loop,
|
||||||
|
volume: audios.volume,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async play(name: string) {
|
||||||
|
if (!this.buffers.has(name)) {
|
||||||
|
await this.loadAudio(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = this.buffers.get(name);
|
||||||
|
const options = this.audioOptions.get(name);
|
||||||
|
if (!buffer || !options) {
|
||||||
|
throw new Error(`音频 ${name} 未找到`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = this.audioCtx.createBufferSource();
|
||||||
|
source.buffer = buffer;
|
||||||
|
source.loop = options.loop || false;
|
||||||
|
|
||||||
|
let gainNode: GainNode | null = null;
|
||||||
|
if (options.volume !== undefined) {
|
||||||
|
gainNode = this.audioCtx.createGain();
|
||||||
|
gainNode.gain.value = options.volume;
|
||||||
|
source.connect(gainNode);
|
||||||
|
gainNode.connect(this.audioCtx.destination);
|
||||||
|
} else {
|
||||||
|
source.connect(this.audioCtx.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
source.start();
|
||||||
|
this.sources.set(name, source);
|
||||||
|
console.log(`音频 ${name} 播放`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(name: string) {
|
||||||
|
const source = this.sources.get(name);
|
||||||
|
if (source) {
|
||||||
|
source.stop();
|
||||||
|
this.sources.delete(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAll() {
|
||||||
|
this.sources.forEach(source => source.stop());
|
||||||
|
this.sources.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||