290 lines
9.8 KiB
TypeScript
290 lines
9.8 KiB
TypeScript
import { getImageURL } from '@/utils/images'
|
|
import type { ISkill } from '@/api/info/type'
|
|
import { useVModel } from '@vueuse/core'
|
|
import { UiButton } from '@/components/ui/button'
|
|
import addIcon from './img/add.svg'
|
|
import maxIcon from './img/max.svg'
|
|
import minIcon from './img/min.svg'
|
|
import subIcon from './img/sub.svg'
|
|
import './style.scss'
|
|
|
|
export default defineComponent({
|
|
name: 'SkillTree',
|
|
props: {
|
|
skills: {
|
|
type: Array as PropType<ISkill[]>,
|
|
default: () => [],
|
|
},
|
|
lvInfo: {
|
|
type: Object as PropType<Record<string, { lv: number; vp: number; up: number }>>,
|
|
default: () => ({}),
|
|
},
|
|
bindAwake: {
|
|
type: Number,
|
|
default: () => 50,
|
|
},
|
|
},
|
|
emits: ['chooseSkill'],
|
|
setup(props, { emit }) {
|
|
const skills = computed(() => props.skills)
|
|
const lvInfo = useVModel(props, 'lvInfo')
|
|
const bindAwake = useVModel(props, 'bindAwake')
|
|
// const emit = defineEmits(['chooseSkill'])
|
|
|
|
const getSkill = (lvStart: number, lvEnd: number, position: number) => {
|
|
if (position >= 0)
|
|
return skills.value.find(
|
|
(item) =>
|
|
item.line > lvStart - 5 &&
|
|
item.line <= Math.max(lvEnd - 5, 1) &&
|
|
item.position == position,
|
|
)
|
|
else
|
|
return skills.value.filter(
|
|
(item) => item.line > lvStart - 5 && item.line <= Math.max(lvEnd - 5, 1),
|
|
)
|
|
}
|
|
|
|
const lvList = [15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100]
|
|
|
|
const columns = Array.from({ length: 11 }, (_, index) => index)
|
|
|
|
const activeSkill = ref<number>(-1)
|
|
|
|
const chooseSkill = (skill?: ISkill, operation: string = 'add') => {
|
|
if (!skill) {
|
|
activeSkill.value = -1
|
|
return
|
|
}
|
|
if (activeSkill.value == skill.id) actionSkillLv(skill, operation)
|
|
else activeSkill.value = skill.id
|
|
emit('chooseSkill', skill.id)
|
|
}
|
|
|
|
const awakeSkill_1 = computed(() =>
|
|
skills.value.find((item) => item.learnLv == 50 && item.type == 'active' && item.bind),
|
|
)
|
|
const awakeSkill_2 = computed(() =>
|
|
skills.value.find((item) => item.learnLv == 85 && item.type == 'active' && item.bind),
|
|
)
|
|
|
|
const actionSkillLv = (skill: ISkill, operation: string) => {
|
|
if (operation == 'add') {
|
|
lvInfo.value[skill.id.toString()].lv = Math.min(
|
|
lvInfo.value[skill.id.toString()]?.lv + 1,
|
|
skill.maxLv,
|
|
)
|
|
return
|
|
}
|
|
if (operation == 'addMax') {
|
|
lvInfo.value[skill.id.toString()].lv = skill.maxLearnLv
|
|
return
|
|
}
|
|
if (operation == 'sub') {
|
|
lvInfo.value[skill.id.toString()].lv = Math.max(
|
|
lvInfo.value[skill.id.toString()]?.lv - 1,
|
|
0,
|
|
)
|
|
return
|
|
}
|
|
if (operation == 'subMax') {
|
|
lvInfo.value[skill.id.toString()] = { lv: 0, vp: 0, up: 0 }
|
|
return
|
|
}
|
|
emit('chooseSkill', skill.id)
|
|
}
|
|
|
|
const chooseBindAwake = (lv: number) => {
|
|
bindAwake.value = lv
|
|
}
|
|
|
|
const renderSkill = (skill: ISkill, index: number) => {
|
|
const info = lvInfo.value?.[skill.id.toString()]
|
|
let color = 'white'
|
|
if (info?.lv < skill.maxLearnLv) color = '#32E432'
|
|
if (info?.lv > skill.maxLearnLv) color = '#58E0FC'
|
|
return (
|
|
<>
|
|
<div class="skill-tree__slot">
|
|
<div class="skill-tree__enhancements">
|
|
{info?.vp > 0 && <div class={`vp_${info.vp}`}></div>}
|
|
{info?.up > 0 && <div class={`up_${info.up}`}></div>}
|
|
</div>
|
|
{activeSkill.value == skill.id && renderSkillAction(skill)}
|
|
<div
|
|
class={['skill-tree__skill', skill.learnLv == 100 && skill.bind ? '' : 'skill']}
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
chooseSkill(skill)
|
|
}}
|
|
onContextmenu={(e) => {
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
chooseSkill(skill, 'sub')
|
|
}}
|
|
key={index}
|
|
>
|
|
<img
|
|
class={['skill-tree__icon', skill.type === 'passive' ? 'passive' : '']}
|
|
src={getImageURL(skill.icon)}
|
|
alt={skill.name}
|
|
></img>
|
|
<div class="skill-tree__level" style={{ color }}>
|
|
{info?.lv ?? 0}
|
|
</div>
|
|
</div>
|
|
{skill.learnLv == 100 && skill.bind && (
|
|
<>
|
|
<div class={`skill-tree__awake awake_${bindAwake.value}`}>
|
|
<div class="skill-tree__awake-option" onClick={() => chooseBindAwake(50)}>
|
|
<img
|
|
class="skill-tree__icon skill-tree__awake-icon"
|
|
src={getImageURL(awakeSkill_1.value?.icon ?? '')}
|
|
></img>
|
|
<div class="skill-tree__awake-label">1次</div>
|
|
</div>
|
|
<div class="skill-tree__awake-option" onClick={() => chooseBindAwake(85)}>
|
|
<img
|
|
class="skill-tree__icon skill-tree__awake-icon"
|
|
src={getImageURL(awakeSkill_2.value?.icon ?? '')}
|
|
></img>
|
|
<div class="skill-tree__awake-label">2次</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const renderSkillAction = (skill: ISkill) => (
|
|
<>
|
|
<>
|
|
<div class="skill-active skill-tree__actions">
|
|
<div class="skill-tree__action-list">
|
|
<UiButton
|
|
size="icon"
|
|
class="skill-action-button"
|
|
aria-label="技能等级归零"
|
|
disabled={lvInfo.value[skill.id.toString()]?.lv == 0}
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'subMax')
|
|
}}
|
|
>
|
|
<img data-icon src={minIcon} alt="" />
|
|
</UiButton>
|
|
<UiButton
|
|
size="icon"
|
|
class="skill-action-button"
|
|
aria-label="技能等级减一"
|
|
disabled={lvInfo.value[skill.id.toString()]?.lv == 0}
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'sub')
|
|
}}
|
|
>
|
|
<img data-icon src={subIcon} alt="" />
|
|
</UiButton>
|
|
<UiButton
|
|
size="icon"
|
|
class="skill-action-button"
|
|
aria-label="技能等级加一"
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'add')
|
|
}}
|
|
>
|
|
<img data-icon src={addIcon} alt="" />
|
|
</UiButton>
|
|
<UiButton
|
|
size="icon"
|
|
class="skill-action-button"
|
|
aria-label="技能等级加满"
|
|
disabled={lvInfo.value[skill.id.toString()]?.lv >= skill.maxLearnLv}
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'addMax')
|
|
}}
|
|
>
|
|
<img data-icon src={maxIcon} alt="" />
|
|
</UiButton>
|
|
{/* <img
|
|
src={`${new URL('./img/min.svg', import.meta.url).href}`}
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'subMax')
|
|
}}
|
|
></img>
|
|
<img
|
|
src={`${new URL('./img/sub.svg', import.meta.url).href}`}
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'sub')
|
|
}}
|
|
></img>
|
|
<img
|
|
src={`${new URL('./img/add.svg', import.meta.url).href}`}
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'add')
|
|
}}
|
|
></img>
|
|
<img
|
|
src={`${new URL('./img/max.svg', import.meta.url).href}`}
|
|
onClick={(e: Event) => {
|
|
e.stopPropagation()
|
|
actionSkillLv(skill, 'addMax')
|
|
}}
|
|
></img> */}
|
|
</div>
|
|
</div>
|
|
</>
|
|
</>
|
|
)
|
|
|
|
return () => (
|
|
<>
|
|
<div class="skill-tree" onClick={() => (activeSkill.value = -1)}>
|
|
{((getSkill(0, 15, -1) ?? []) as ISkill[]).length > 0 && (
|
|
<>
|
|
<div class="skill-tree-line">
|
|
<div class="skill-tree__row-label">
|
|
其<br />余
|
|
</div>
|
|
{((getSkill(0, 15, -1) ?? []) as ISkill[])?.map((skill, index) =>
|
|
renderSkill(skill, index),
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{lvList.map((lv, index) =>
|
|
((getSkill(lv, lvList[index + 1] ?? 150, -1) as ISkill[])?.length ?? 0) > 0 ? (
|
|
<div class="skill-tree-line" key={index}>
|
|
{lv == 1 || lv % 10 == 0 ? (
|
|
<div class="skill-tree__row-label">{lv}</div>
|
|
) : (
|
|
<div class="skill-tree__placeholder"></div>
|
|
)}
|
|
{columns.map((column) => {
|
|
const skill = getSkill(lv, lvList[index + 1] ?? 150, column) as ISkill
|
|
return skill ? (
|
|
renderSkill(skill, index)
|
|
) : (
|
|
<div class="skill-tree__placeholder"></div>
|
|
)
|
|
})}
|
|
<div class="skill-tree__row-end" onClick={() => (activeSkill.value = -1)}></div>
|
|
</div>
|
|
) : (
|
|
<></>
|
|
),
|
|
)}
|
|
</div>
|
|
</>
|
|
)
|
|
},
|
|
})
|