黑暗武士初步实现
This commit is contained in:
@@ -0,0 +1,592 @@
|
||||
<template>
|
||||
<div
|
||||
ref="settingRef"
|
||||
class="dark-knight-skill-setting-shell select-none"
|
||||
:class="{ collapsed }"
|
||||
@click.stop
|
||||
>
|
||||
<button
|
||||
v-if="collapsed"
|
||||
type="button"
|
||||
class="shortcut-setting-trigger ml-1px"
|
||||
title="展开快捷键设置"
|
||||
aria-label="展开快捷键设置"
|
||||
@click="collapsed = false"
|
||||
>
|
||||
<img src="../img/shortcut.png" alt="快捷键设置" />
|
||||
</button>
|
||||
|
||||
<div v-else class="dark-knight-skill-setting">
|
||||
<div class="setting-title">
|
||||
<span>添加快捷键</span>
|
||||
<button
|
||||
type="button"
|
||||
class="minimize-button"
|
||||
title="缩小快捷键设置"
|
||||
aria-label="缩小快捷键设置"
|
||||
@click="collapseSetting"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<div class="shortcut-grid">
|
||||
<button
|
||||
v-for="(key, index) in shortcutKeys"
|
||||
:key="key"
|
||||
type="button"
|
||||
class="skill-slot"
|
||||
:class="{ selected: isEditing('shortcut', index) }"
|
||||
:title="getSkill(arrangement.shortcuts[index])?.name || `快捷键 ${key}`"
|
||||
@click="openPicker('shortcut', index)"
|
||||
>
|
||||
<img
|
||||
v-if="getSkill(arrangement.shortcuts[index])"
|
||||
:src="getImageURL(getSkill(arrangement.shortcuts[index])!.icon)"
|
||||
:alt="getSkill(arrangement.shortcuts[index])!.name"
|
||||
/>
|
||||
<!-- <span class="slot-key">{{ key }}</span> -->
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="combo-title">连续技</div>
|
||||
<!-- <div class="combo-markers" aria-hidden="true">
|
||||
<span v-for="key in comboKeys" :key="key">✹</span>
|
||||
</div> -->
|
||||
|
||||
<div class="combo-grid">
|
||||
<div v-for="(key, column) in comboKeys" :key="key" class="combo-column">
|
||||
<template v-for="stage in stageCount" :key="stage">
|
||||
<button
|
||||
type="button"
|
||||
class="skill-slot combo-slot"
|
||||
:class="{
|
||||
selected: isEditing('combo', column, stage - 1),
|
||||
disabled: isStageDisabled(column, stage - 1),
|
||||
}"
|
||||
:disabled="isStageDisabled(column, stage - 1)"
|
||||
:title="
|
||||
isStageDisabled(column, stage - 1)
|
||||
? `请先设置 ${stage - 1} 阶段`
|
||||
: getSkill(arrangement.combos[column][stage - 1])?.name || `${stage}阶段 ${key}`
|
||||
"
|
||||
@click="openPicker('combo', column, stage - 1)"
|
||||
>
|
||||
<img
|
||||
v-if="getSkill(arrangement.combos[column][stage - 1])"
|
||||
:src="getImageURL(getSkill(arrangement.combos[column][stage - 1])!.icon)"
|
||||
:alt="getSkill(arrangement.combos[column][stage - 1])!.name"
|
||||
/>
|
||||
<span v-else class="stage-label">{{ stage }}阶段</span>
|
||||
<!-- <span class="slot-key">{{ key }}</span> -->
|
||||
</button>
|
||||
<span
|
||||
v-if="stage < stageCount"
|
||||
class="stage-arrow"
|
||||
:class="{ connected: isStageConnected(column, stage - 1) }"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
</template>
|
||||
|
||||
<CalcButton
|
||||
small
|
||||
class="reset-button"
|
||||
:title="`重置 ${key} 连续技`"
|
||||
@click="resetCombo(column)"
|
||||
>
|
||||
↻
|
||||
</CalcButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="editingSlot" class="skill-picker">
|
||||
<div class="picker-header">
|
||||
<span>选择主动技能</span>
|
||||
<div class="picker-actions">
|
||||
<button type="button" :disabled="!currentValue" @click="selectSkill('')">清空</button>
|
||||
<button type="button" @click="editingSlot = null">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="picker-grid">
|
||||
<button
|
||||
v-for="skill in selectableSkills"
|
||||
:key="skill.id"
|
||||
type="button"
|
||||
class="picker-skill"
|
||||
:class="{ active: currentValue === skill.id.toString() }"
|
||||
:disabled="isSkillUsed(skill.id.toString())"
|
||||
:title="skill.name"
|
||||
@click="selectSkill(skill.id.toString())"
|
||||
>
|
||||
<img :src="getImageURL(skill.icon)" :alt="skill.name" />
|
||||
<span>{{ skill.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DarkKnightSkillSetting">
|
||||
import type { ISkill } from '@/api/info/type'
|
||||
import type { ISkillShortcuts } from '@/stores/config'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import { onClickOutside, useEventListener } from '@vueuse/core'
|
||||
import CalcButton from '@/components/calc/button/index.vue'
|
||||
|
||||
type EditingSlot =
|
||||
| { type: 'shortcut'; index: number }
|
||||
| { type: 'combo'; column: number; stage: number }
|
||||
|
||||
const shortcutKeys = ['Q', 'W', 'E', 'R', 'T', 'Y']
|
||||
const comboKeys = ['A', 'S', 'D', 'F', 'G', 'H']
|
||||
const stageCount = 5
|
||||
const excludedSkillNames = new Set(['后跳', '受身蹲伏', '跃翔', '黑暗释放'])
|
||||
const excludedAwakeLevels = new Set([85, 100])
|
||||
|
||||
const props = defineProps<{
|
||||
skills: ISkill[]
|
||||
modelValue?: ISkillShortcuts
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: ISkillShortcuts]
|
||||
}>()
|
||||
|
||||
const settingRef = ref<HTMLElement>()
|
||||
const editingSlot = ref<EditingSlot | null>(null)
|
||||
const collapsed = ref(false)
|
||||
|
||||
const arrangement = computed<ISkillShortcuts>(() => ({
|
||||
shortcuts: Array.from(
|
||||
{ length: shortcutKeys.length },
|
||||
(_, index) => props.modelValue?.shortcuts?.[index] ?? '',
|
||||
),
|
||||
combos: Array.from({ length: comboKeys.length }, (_, column) =>
|
||||
Array.from(
|
||||
{ length: stageCount },
|
||||
(_, stage) => props.modelValue?.combos?.[column]?.[stage] ?? '',
|
||||
),
|
||||
),
|
||||
}))
|
||||
|
||||
const skillMap = computed(() => new Map(props.skills.map((skill) => [skill.id.toString(), skill])))
|
||||
|
||||
const selectableSkills = computed(() =>
|
||||
[...props.skills]
|
||||
.filter(
|
||||
(skill) =>
|
||||
skill.type === 'active' &&
|
||||
!excludedSkillNames.has(skill.name) &&
|
||||
!excludedAwakeLevels.has(skill.learnLv),
|
||||
)
|
||||
.sort((left, right) => {
|
||||
const [leftColumn = -1, leftRow = -1] = left.cellPosition ?? []
|
||||
const [rightColumn = -1, rightRow = -1] = right.cellPosition ?? []
|
||||
return leftRow - rightRow || leftColumn - rightColumn || left.learnLv - right.learnLv
|
||||
}),
|
||||
)
|
||||
|
||||
const selectedSkillIds = computed(
|
||||
() =>
|
||||
new Set([...arrangement.value.shortcuts, ...arrangement.value.combos.flat()].filter(Boolean)),
|
||||
)
|
||||
|
||||
const currentValue = computed(() => {
|
||||
const slot = editingSlot.value
|
||||
if (!slot) return ''
|
||||
return slot.type === 'shortcut'
|
||||
? arrangement.value.shortcuts[slot.index]
|
||||
: arrangement.value.combos[slot.column][slot.stage]
|
||||
})
|
||||
|
||||
const getSkill = (skillId: string) => skillMap.value.get(skillId)
|
||||
|
||||
const isEditing = (type: EditingSlot['type'], index: number, stage?: number) => {
|
||||
const slot = editingSlot.value
|
||||
if (!slot) return false
|
||||
if (type === 'shortcut') return slot.type === 'shortcut' && slot.index === index
|
||||
return slot.type === 'combo' && slot.column === index && slot.stage === stage
|
||||
}
|
||||
|
||||
const isStageDisabled = (column: number, stage: number) =>
|
||||
stage > 0 && !arrangement.value.combos[column][stage - 1]
|
||||
|
||||
const isStageConnected = (column: number, stage: number) =>
|
||||
!!arrangement.value.combos[column][stage] && !!arrangement.value.combos[column][stage + 1]
|
||||
|
||||
const openPicker = (type: EditingSlot['type'], index: number, stage?: number) => {
|
||||
if (type === 'combo' && isStageDisabled(index, stage ?? 0)) return
|
||||
editingSlot.value =
|
||||
type === 'shortcut' ? { type, index } : { type, column: index, stage: stage ?? 0 }
|
||||
}
|
||||
|
||||
const isSkillUsed = (skillId: string) =>
|
||||
selectedSkillIds.value.has(skillId) && currentValue.value !== skillId
|
||||
|
||||
const selectSkill = (skillId: string) => {
|
||||
const slot = editingSlot.value
|
||||
if (!slot || isSkillUsed(skillId)) return
|
||||
|
||||
const next: ISkillShortcuts = {
|
||||
shortcuts: [...arrangement.value.shortcuts],
|
||||
combos: arrangement.value.combos.map((column) => [...column]),
|
||||
}
|
||||
if (slot.type === 'shortcut') next.shortcuts[slot.index] = skillId
|
||||
else {
|
||||
next.combos[slot.column][slot.stage] = skillId
|
||||
if (!skillId) next.combos[slot.column].fill('', slot.stage)
|
||||
}
|
||||
|
||||
emit('update:modelValue', next)
|
||||
if (slot.type === 'combo' && skillId) {
|
||||
editingSlot.value =
|
||||
slot.stage < stageCount - 1
|
||||
? { type: 'combo', column: slot.column, stage: slot.stage + 1 }
|
||||
: null
|
||||
}
|
||||
}
|
||||
|
||||
const resetCombo = (column: number) => {
|
||||
const next: ISkillShortcuts = {
|
||||
shortcuts: [...arrangement.value.shortcuts],
|
||||
combos: arrangement.value.combos.map((items, index) =>
|
||||
index === column ? Array.from({ length: stageCount }, () => '') : [...items],
|
||||
),
|
||||
}
|
||||
emit('update:modelValue', next)
|
||||
if (editingSlot.value?.type === 'combo' && editingSlot.value.column === column) {
|
||||
editingSlot.value = { type: 'combo', column, stage: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
const collapseSetting = () => {
|
||||
editingSlot.value = null
|
||||
collapsed.value = true
|
||||
}
|
||||
|
||||
onClickOutside(settingRef, () => (editingSlot.value = null))
|
||||
useEventListener(window, 'blur', () => (editingSlot.value = null))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dark-knight-skill-setting-shell {
|
||||
position: relative;
|
||||
width: auto;
|
||||
flex: none;
|
||||
|
||||
&.collapsed {
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark-knight-skill-setting {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 4px 8px 8px;
|
||||
color: #8f7845;
|
||||
background: linear-gradient(180deg, #11120f 0%, #080a09 100%);
|
||||
border: 1px solid #383224;
|
||||
// box-shadow: 0 4px 14px #000c;
|
||||
}
|
||||
|
||||
.setting-title {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
margin: -3px -7px 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9c854e;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
// font-weight: 700;
|
||||
background: linear-gradient(#2d281b, #13120d);
|
||||
border-bottom: 1px solid #3a3424;
|
||||
text-shadow: 0 1px 2px #000;
|
||||
}
|
||||
|
||||
.minimize-button {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 3px;
|
||||
width: 15px;
|
||||
height: 14px;
|
||||
padding: 0;
|
||||
background: linear-gradient(#393b39, #202220);
|
||||
border: 1px solid #554a32;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
height: 1px;
|
||||
background: #c0aa70;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #8b743d;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-setting-trigger {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(to bottom, #313330 0%, #0f110f 100%);
|
||||
border: 1px solid #514531;
|
||||
border-radius: 2px;
|
||||
box-shadow:
|
||||
0 0 0 1px #322e20,
|
||||
0 3px 8px #000a;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #8b743d;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-grid,
|
||||
.combo-grid,
|
||||
.combo-markers {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 28px);
|
||||
justify-content: center;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
.shortcut-grid {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.skill-slot {
|
||||
position: relative;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #5e5131;
|
||||
background: #030504;
|
||||
border: 1px solid #202320;
|
||||
box-shadow: inset 0 0 0 1px #000;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
border-color: #876f36;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: #353832;
|
||||
border-color: #151815;
|
||||
opacity: 0.8;
|
||||
cursor: not-allowed;
|
||||
.stage-label {
|
||||
color: #353832;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.slot-key {
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
bottom: -1px;
|
||||
color: #fff4bd;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 13px;
|
||||
transform: scale(0.8);
|
||||
text-shadow:
|
||||
-1px -1px 0 #e32610,
|
||||
1px -1px 0 #e32610,
|
||||
-1px 1px 0 #e32610,
|
||||
1px 1px 0 #e32610;
|
||||
}
|
||||
|
||||
.combo-title {
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #aa8e4f;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
// font-weight: 700;
|
||||
}
|
||||
|
||||
.combo-markers {
|
||||
height: 15px;
|
||||
|
||||
span {
|
||||
color: #b7984e;
|
||||
font-size: 15px;
|
||||
line-height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.combo-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.combo-slot {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.stage-label {
|
||||
color: #55482c;
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stage-arrow {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 11px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
top: 0;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #282a28;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
top: 5px;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-top: 6px solid #282a28;
|
||||
}
|
||||
|
||||
&.connected::before {
|
||||
background: #244b80;
|
||||
}
|
||||
|
||||
&.connected::after {
|
||||
border-top-color: #2356a0;
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.skill-picker {
|
||||
position: absolute;
|
||||
right: calc(100% + 8px);
|
||||
bottom: 0;
|
||||
width: 210px;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
z-index: 30;
|
||||
color: #aa9155;
|
||||
background: #090b0a;
|
||||
border: 1px solid #5c4b2c;
|
||||
box-shadow: 0 5px 18px #000e;
|
||||
}
|
||||
|
||||
.picker-header {
|
||||
height: 23px;
|
||||
padding: 0 3px 4px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #302a1e;
|
||||
}
|
||||
|
||||
.picker-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
button {
|
||||
min-width: 22px;
|
||||
height: 18px;
|
||||
font-size: 11px;
|
||||
color: #b9a269;
|
||||
background: #20221f;
|
||||
border: 1px solid #4c432e;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.picker-grid {
|
||||
max-height: 390px;
|
||||
padding-top: 5px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 4px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.picker-skill {
|
||||
min-width: 0;
|
||||
padding: 3px 2px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
color: #aa9155;
|
||||
font-size: 10px;
|
||||
background: #050706;
|
||||
border: 1px solid #292c29;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover:not(:disabled),
|
||||
&.active {
|
||||
color: #e1c67e;
|
||||
border-color: #8b743d;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.25;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="flex relative" ref="skillTreeRef">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex relative h-full" ref="skillTreeRef">
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex bg-#101012 h-40px text-#6C5E4A font-bold">
|
||||
<div
|
||||
class="flex-1 text-15px flex items-center justify-center"
|
||||
@@ -50,9 +50,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showDarkKnightSkillSetting" class="dark-knight-side-column">
|
||||
<div class="skill-detail-region">
|
||||
<button
|
||||
v-if="curCurrentSkill.skillId && curCurrentSkill.level > 0 && !showSkillDetail"
|
||||
type="button"
|
||||
class="side-panel-trigger"
|
||||
@click="showSkillDetail = true"
|
||||
>
|
||||
<img src="./components/img/detail.png" alt="技能详情" />
|
||||
<span>技能详情</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-if="curCurrentSkill.skillId && curCurrentSkill.level > 0 && showSkillDetail"
|
||||
ref="SkillDetailRef"
|
||||
class="skill-detail-panel"
|
||||
>
|
||||
<SkillDetail :skillId="curCurrentSkill.skillId" :level="curCurrentSkill.level" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DarkKnightSkillSetting
|
||||
:skills="infoStore.skills"
|
||||
v-model="configStore.config.skillShortcuts"
|
||||
class="ml-1px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute right--65px"
|
||||
v-if="curCurrentSkill.skillId && curCurrentSkill.level > 0 && !showSkillDetail"
|
||||
class="absolute"
|
||||
:style="detailButtonStyle"
|
||||
v-if="
|
||||
!showDarkKnightSkillSetting &&
|
||||
curCurrentSkill.skillId &&
|
||||
curCurrentSkill.level > 0 &&
|
||||
!showSkillDetail
|
||||
"
|
||||
>
|
||||
<div class="text-#AE8D5A p-1px box-border flex pl-2 w-auto">
|
||||
<div
|
||||
@@ -74,7 +108,12 @@
|
||||
class="bg-#101012 w-300px h-full absolute"
|
||||
:style="style"
|
||||
ref="SkillDetailRef"
|
||||
v-if="curCurrentSkill.skillId && curCurrentSkill.level > 0 && showSkillDetail"
|
||||
v-if="
|
||||
!showDarkKnightSkillSetting &&
|
||||
curCurrentSkill.skillId &&
|
||||
curCurrentSkill.level > 0 &&
|
||||
showSkillDetail
|
||||
"
|
||||
>
|
||||
<SkillDetail :skillId="curCurrentSkill.skillId" :level="curCurrentSkill.level"></SkillDetail>
|
||||
</div>
|
||||
@@ -87,14 +126,19 @@ import SkillVP from './components/SkillVP'
|
||||
import SkillUP from './components/SkillUP'
|
||||
import { useConfigStore } from '@/stores'
|
||||
import SkillDetail from './components/SkillDetail'
|
||||
import DarkKnightSkillSetting from './components/DarkKnightSkillSetting/index.vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
alter: string
|
||||
}>()
|
||||
const tab = ref(0)
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const skillTreeWidth = 550
|
||||
|
||||
const showDarkKnightSkillSetting = computed(() => infoStore.infos?.alter === 'dark_knight')
|
||||
|
||||
const curSkillId = ref()
|
||||
|
||||
const curCurrentSkill = computed(() => {
|
||||
@@ -110,11 +154,15 @@ const showSkillDetail = ref(false)
|
||||
const skillTreeRef = ref<HTMLHtmlElement>()
|
||||
const SkillDetailRef = ref<HTMLHtmlElement>()
|
||||
|
||||
const detailButtonStyle = computed(() => ({
|
||||
left: `${skillTreeRef.value?.clientWidth ?? skillTreeWidth}px`,
|
||||
}))
|
||||
|
||||
const style = computed(() => {
|
||||
return {
|
||||
zIndex: 99,
|
||||
height: `${skillTreeRef.value?.clientHeight ?? 0}px`,
|
||||
left: `${(skillTreeRef.value?.clientWidth ?? 0) + 5}px`,
|
||||
left: `${(skillTreeRef.value?.clientWidth ?? skillTreeWidth) + 5}px`,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -122,9 +170,15 @@ const setCurrentSkill = (skillId: number) => {
|
||||
curSkillId.value = infoStore.skills.find((skill) => skill.id === skillId)?.uuid || ''
|
||||
}
|
||||
|
||||
onClickOutside(SkillDetailRef, (event) => {showSkillDetail.value = false}, {
|
||||
ignore: [skillTreeRef],
|
||||
})
|
||||
onClickOutside(
|
||||
SkillDetailRef,
|
||||
() => {
|
||||
showSkillDetail.value = false
|
||||
},
|
||||
{
|
||||
ignore: [skillTreeRef],
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -154,6 +208,59 @@ onClickOutside(SkillDetailRef, (event) => {showSkillDetail.value = false}, {
|
||||
}
|
||||
}
|
||||
|
||||
.dark-knight-side-column {
|
||||
width: 310px;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skill-detail-region {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skill-detail-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
background: #101012;
|
||||
}
|
||||
|
||||
.side-panel-trigger {
|
||||
min-width: 48px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
color: #ae8d5a;
|
||||
background: linear-gradient(to bottom, #313330 0%, #0f110f 100%);
|
||||
border: 1px solid #514531;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px #322e20;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #8b743d;
|
||||
}
|
||||
}
|
||||
|
||||
.item-head {
|
||||
background: linear-gradient(#2b2817, #171407);
|
||||
border-top: 1px solid #423d2c;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { useInfoStore } from './info'
|
||||
import api from '@/api'
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import type { IEquipment, ISkill } from '@/api/info/type'
|
||||
import type { IResultSkillCount } from '@/api/calc/type'
|
||||
// | '上衣'
|
||||
// | '头肩'
|
||||
@@ -42,6 +42,11 @@ export interface IConfigEmblem {
|
||||
items: string[]
|
||||
}
|
||||
|
||||
export interface ISkillShortcuts {
|
||||
shortcuts: string[]
|
||||
combos: string[][]
|
||||
}
|
||||
|
||||
export interface IConfig {
|
||||
skills: Record<string, { lv: number; vp: number; up: number }>
|
||||
equips: Record<string, IConfigEquip>
|
||||
@@ -73,6 +78,7 @@ export interface IConfig {
|
||||
options: Record<string, number>
|
||||
avatarLink: string
|
||||
emblems: Record<string, IConfigEmblem>
|
||||
skillShortcuts: ISkillShortcuts
|
||||
}
|
||||
|
||||
const defaultEqusConfig = {
|
||||
@@ -113,6 +119,39 @@ const createDefaultEmblemConfig = (): IConfigEmblem => ({
|
||||
|
||||
const defaultEmblemConfig = createDefaultEmblemConfig()
|
||||
|
||||
const createDefaultSkillShortcuts = (): ISkillShortcuts => ({
|
||||
shortcuts: Array.from({ length: 6 }, () => ''),
|
||||
combos: Array.from({ length: 6 }, () => Array.from({ length: 5 }, () => '')),
|
||||
})
|
||||
|
||||
const normalizeSkillShortcuts = (
|
||||
arrangement?: Partial<ISkillShortcuts>,
|
||||
skills: ISkill[] = [],
|
||||
): ISkillShortcuts => {
|
||||
const validSkillIds = new Set(skills.map((skill) => skill.id.toString()))
|
||||
const skillIdByUuid = new Map(skills.map((skill) => [skill.uuid, skill.id.toString()]))
|
||||
const normalizeSkillId = (value: unknown) => {
|
||||
const key =
|
||||
typeof value === 'number' ? value.toString() : typeof value === 'string' ? value : ''
|
||||
if (!key) return ''
|
||||
return validSkillIds.has(key) ? key : (skillIdByUuid.get(key) ?? '')
|
||||
}
|
||||
|
||||
return {
|
||||
shortcuts: Array.from({ length: 6 }, (_, index) =>
|
||||
normalizeSkillId(arrangement?.shortcuts?.[index]),
|
||||
),
|
||||
combos: Array.from({ length: 6 }, (_, column) => {
|
||||
const combo = Array.from({ length: 5 }, (_, stage) =>
|
||||
normalizeSkillId(arrangement?.combos?.[column]?.[stage]),
|
||||
)
|
||||
const firstEmptyStage = combo.findIndex((skillId) => !skillId)
|
||||
if (firstEmptyStage >= 0) combo.fill('', firstEmptyStage)
|
||||
return combo
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export const useConfigStore = defineStore('configStore', () => {
|
||||
const config = ref<IConfig>({
|
||||
skills: {},
|
||||
@@ -128,6 +167,7 @@ export const useConfigStore = defineStore('configStore', () => {
|
||||
options: {},
|
||||
avatarLink: '',
|
||||
emblems: {},
|
||||
skillShortcuts: createDefaultSkillShortcuts(),
|
||||
})
|
||||
|
||||
const skillCountConfig = ref<IResultSkillCount[]>([])
|
||||
@@ -161,6 +201,7 @@ export const useConfigStore = defineStore('configStore', () => {
|
||||
options: {},
|
||||
avatarLink: '',
|
||||
emblems: {},
|
||||
skillShortcuts: createDefaultSkillShortcuts(),
|
||||
}
|
||||
}
|
||||
if (!config.value?.equips) config.value.equips = {}
|
||||
@@ -172,6 +213,10 @@ export const useConfigStore = defineStore('configStore', () => {
|
||||
if (!config.value?.options) config.value.options = {}
|
||||
if (!config.value?.oaths) config.value.oaths = {}
|
||||
if (!config.value?.emblems) config.value.emblems = {}
|
||||
config.value.skillShortcuts = normalizeSkillShortcuts(
|
||||
config.value.skillShortcuts,
|
||||
infoStore.skills,
|
||||
)
|
||||
|
||||
const part = [...infoStore.parts, '宠物', '副武器'].filter(
|
||||
(a) => !config.value?.equips.hasOwnProperty(a),
|
||||
@@ -291,5 +336,6 @@ export const useConfigStore = defineStore('configStore', () => {
|
||||
createDefaultOathConfig,
|
||||
defaultEmblemConfig,
|
||||
createDefaultEmblemConfig,
|
||||
createDefaultSkillShortcuts,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user