init
@@ -0,0 +1,72 @@
|
||||
<script lang="tsx">
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import api from '@/api'
|
||||
import { useDialog } from '@/components/hooks/dialog'
|
||||
import { isShow, onShow } from '@/components/hooks/show'
|
||||
import { useConfigStore } from '@/stores'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const configStore = useConfigStore()
|
||||
const uid = ref('')
|
||||
|
||||
const { state, execute, isLoading } = useAsyncState(
|
||||
async () => {
|
||||
const res = await api.getConfigFromHelper(uid.value)
|
||||
configStore.config.equips = res.equips
|
||||
configStore.config.avatar = res.avatars
|
||||
configStore.config.avatarLink = res.avatarLink
|
||||
configStore.config.emblems = res.emblems
|
||||
configStore.config.sundry.fog = res.fogOathLv
|
||||
configStore.config.oaths = res.oaths
|
||||
const { alert } = useDialog()
|
||||
visible.value = false
|
||||
await alert({
|
||||
content: '导入成功',
|
||||
})
|
||||
return res
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
resetOnExecute: false,
|
||||
immediate: false,
|
||||
onError: Promise.reject,
|
||||
},
|
||||
)
|
||||
|
||||
onShow(
|
||||
() => {
|
||||
uid.value = ''
|
||||
state.value = undefined
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const visible = isShow()
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<>
|
||||
<div class="h-30 w-80 py-2 px-4 text-#e9c556">
|
||||
<div class="flex justify-around items-center">
|
||||
助手社区ID
|
||||
<calc-input class="!w-60" v-model={uid.value} />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-#e9c556/80">* 点击导入后会从助手通过文字识别获取打造</p>
|
||||
<p class="text-#e9c556/80">* 部分打造无法获取,此处仅做快捷导入</p>
|
||||
</div>
|
||||
<div class="flex justify-around items-center">
|
||||
<calc-button onClick={execute} disabled={isLoading.value}>
|
||||
导入
|
||||
</calc-button>
|
||||
<calc-button onClick={() => (visible.value = false)}>取消</calc-button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,80 @@
|
||||
<script lang="tsx">
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import api from '@/api'
|
||||
import { useDialog } from '@/components/hooks/dialog'
|
||||
import { isShow, onShow } from '@/components/hooks/show'
|
||||
import { useConfigStore } from '@/stores'
|
||||
import { type IHelperRoleInfo } from '@/api/info/type'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
alter: {
|
||||
type: Object as () => IHelperRoleInfo,
|
||||
},
|
||||
uid: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const configStore = useConfigStore()
|
||||
const { state, execute, isLoading } = useAsyncState(
|
||||
async () => {
|
||||
const res = await api.getConfigFromHelperAlter(props.uid || '', props.alter?.roleId || '')
|
||||
configStore.config.equips = res.equips
|
||||
configStore.config.avatar = res.avatars
|
||||
configStore.config.avatarLink = res.avatarLink
|
||||
configStore.config.emblems = res.emblems
|
||||
configStore.config.sundry.fog = res.fogOathLv
|
||||
configStore.config.oaths = res.oaths
|
||||
const { alert } = useDialog()
|
||||
visible.value = false
|
||||
await alert({
|
||||
content: '导入成功',
|
||||
})
|
||||
return res
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
resetOnExecute: false,
|
||||
immediate: false,
|
||||
onError: Promise.reject,
|
||||
},
|
||||
)
|
||||
|
||||
onShow(
|
||||
() => {
|
||||
state.value = undefined
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const visible = isShow()
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<>
|
||||
<div class="h-45 w-80 py-2 px-4 text-#e9c556">
|
||||
<div class="flex justify-around items-center flex-col gap-1">
|
||||
<div>检测到以下信息,是否导入助手数据?</div>
|
||||
<div class="text-#DED29A">{props.alter?.roleName}</div>
|
||||
<div class="text-#908150">{props.alter?.roleJob}</div>
|
||||
<div class="text-#3ea74e">名望:{props.alter?.fame}</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-#e9c556/80">* 点击导入后会从助手通过文字识别获取打造</p>
|
||||
<p class="text-#e9c556/80">* 部分打造无法获取,此处仅做快捷导入</p>
|
||||
</div>
|
||||
<div class="flex justify-around items-center">
|
||||
<calc-button onClick={execute} disabled={isLoading.value}>
|
||||
导入
|
||||
</calc-button>
|
||||
<calc-button onClick={() => (visible.value = false)}>取消</calc-button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script lang="tsx">
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import api from '@/api'
|
||||
import { useDialog } from '@/components/hooks/dialog'
|
||||
import { isShow, onShow } from '@/components/hooks/show'
|
||||
import { useConfigStore } from '@/stores'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const configStore = useConfigStore()
|
||||
const skillCode = ref('')
|
||||
|
||||
const { state, execute, isLoading } = useAsyncState(
|
||||
async () => {
|
||||
const res = await api.getSkillFromCode(skillCode.value)
|
||||
// console.log(res)
|
||||
configStore.config.skills = res.skills
|
||||
const { alert } = useDialog()
|
||||
visible.value = false
|
||||
await alert({
|
||||
content: '导入成功',
|
||||
})
|
||||
return res
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
resetOnExecute: false,
|
||||
immediate: false,
|
||||
onError: Promise.reject,
|
||||
},
|
||||
)
|
||||
|
||||
onShow(
|
||||
() => {
|
||||
skillCode.value = ''
|
||||
state.value = undefined
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const visible = isShow()
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<>
|
||||
<div class="h-35 w-80 py-2 px-4 text-#e9c556">
|
||||
<div class="flex justify-around items-center">
|
||||
技能加点代码
|
||||
<calc-input class="!w-60" v-model={skillCode.value} />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-#e9c556/80">
|
||||
*
|
||||
点击导入后会根据规则解析出技能加点、技能进化、技能强化
|
||||
</p>
|
||||
<p class="text-#e9c556/80">* 部分技能可能存在无法识别的情况,需要手动调整</p>
|
||||
</div>
|
||||
<div class="flex justify-around items-center">
|
||||
<calc-button onClick={execute} disabled={!skillCode.value || isLoading.value}>
|
||||
导入
|
||||
</calc-button>
|
||||
<calc-button onClick={() => (visible.value = false)}>取消</calc-button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="mx-10px">
|
||||
<calc-collapse title="武器/称号/宠物/秘宝">
|
||||
<calc-tabs v-model="anotherTab" class="!mb-2">
|
||||
<calc-tab :value="0">武器</calc-tab>
|
||||
<calc-tab :value="1">称号/宠物/秘宝</calc-tab>
|
||||
<calc-tab :value="2" v-if="infoStore.infos?.alter.includes('vegabond')">副武器</calc-tab>
|
||||
</calc-tabs>
|
||||
<Weapon v-show="anotherTab === 0"></Weapon>
|
||||
<Else v-show="anotherTab === 1"></Else>
|
||||
<SubWeapon v-show="anotherTab === 2"></SubWeapon>
|
||||
</calc-collapse>
|
||||
<calc-collapse title="套装/通宝">
|
||||
<calc-tabs v-model="equTab" class="!mb-2">
|
||||
<calc-tab :value="0">套装</calc-tab>
|
||||
<calc-tab :value="1">通宝</calc-tab>
|
||||
</calc-tabs>
|
||||
<Suit v-show="equTab === 0"></Suit>
|
||||
<Universal v-show="equTab === 1"></Universal>
|
||||
</calc-collapse>
|
||||
<calc-collapse title="誓约">
|
||||
<calc-tabs v-model="stoneTab" class="!mb-2">
|
||||
<calc-tab :value="0">套装</calc-tab>
|
||||
<calc-tab :value="1">通宝</calc-tab>
|
||||
<calc-tab :value="2">其他</calc-tab>
|
||||
</calc-tabs>
|
||||
<Stone v-show="stoneTab == 0"></Stone>
|
||||
<StoneUniversal v-show="stoneTab == 1"></StoneUniversal>
|
||||
<StoneOld v-show="stoneTab == 2"></StoneOld>
|
||||
</calc-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="Index">
|
||||
import Suit from './components/Suit.vue'
|
||||
import Weapon from './components/Weapon.vue'
|
||||
import Stone from './components/Stone.vue'
|
||||
import Universal from './components/Universal.vue'
|
||||
import StoneUniversal from './components/StoneUniversal.vue'
|
||||
import StoneOld from './components/StoneOld.vue'
|
||||
import Else from './components/Else.vue'
|
||||
import { useInfoStore } from '@/stores'
|
||||
import SubWeapon from './components/SubWeapon.vue'
|
||||
|
||||
const infoStore = useInfoStore()
|
||||
const equTab = ref(0)
|
||||
const stoneTab = ref(0)
|
||||
const anotherTab = ref(0)
|
||||
</script>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- 每排总计6+4+4,共计14个排列 -->
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
|
||||
<template v-for="(equ, _) in equs" :key="_">
|
||||
<EquipmentIcon
|
||||
:equipment="equ"
|
||||
:inactive="configStore.config.equips[equ?.itemDetailType ?? '']?.id != equ?.id"
|
||||
@click="configStore.chooseEqu(equ)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
rowCount?: number
|
||||
}>()
|
||||
|
||||
const rowCount = Math.max(props.rowCount ?? 0, 14)
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const equs = computed(() => {
|
||||
const items = infoStore.equips.filter((equip) => ['称号', '宠物'].includes(equip.itemDetailType))
|
||||
const res: (IEquipment | undefined)[] = []
|
||||
Array.from(new Set(items.map((item) => item.itemDetailType))).forEach((type) => {
|
||||
const equList = items.filter((item) => item.itemDetailType === type)
|
||||
equList.sort((a, b) => {
|
||||
if (a.itemType !== b.itemType) {
|
||||
return b.itemType.localeCompare(a.itemType)
|
||||
}
|
||||
const rarityOrder = ['传说', '神器', '稀有']
|
||||
return rarityOrder.indexOf(a.rarity) - rarityOrder.indexOf(b.rarity)
|
||||
})
|
||||
res.push(...equList)
|
||||
const remind = rowCount - (res.length % rowCount)
|
||||
res.push(...Array(remind + rowCount).fill(undefined))
|
||||
})
|
||||
const treasure = infoStore.equips.filter((equip) => equip.categorize.includes('秘宝'))
|
||||
res.push(...treasure, ...Array(rowCount - treasure.length).fill(undefined))
|
||||
return res
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,97 @@
|
||||
<!-- 每排总计6+4+4,共计14个排列 -->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col gap-10px pb-10px">
|
||||
<!-- 套装选择tab -->
|
||||
<div class="flex flex-wrap w-100%">
|
||||
<template v-for="(suit, _) in suits" :key="_">
|
||||
<div class="flex items-center justify-center">
|
||||
<img
|
||||
class="w-35px h-35px object-contain"
|
||||
:src="getImageURL('equipment' + suit.imageUrl)"
|
||||
:alt="suit.name"
|
||||
:style="{ filter: curSuit === suit.value ? '' : 'grayscale(100%)' }"
|
||||
@click="curSuit = suit.value"
|
||||
@dblclick="handleDoubleClick"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<EquipmentRaritySelect v-model="curRarity" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
|
||||
<template v-for="(equip, _) in suitEquipments" :key="_">
|
||||
<template v-if="equip?.rarity == curRarity || !curRarity">
|
||||
<EquipmentIcon
|
||||
:equipment="equip"
|
||||
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id"
|
||||
@click="configStore.chooseEqu(equip, true)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<EquipmentIcon />
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
import EquipmentRaritySelect from '@/components/dnf/Equipment/RaritySelect/index.vue'
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
const curSuit = ref(16201)
|
||||
|
||||
const curRarity = ref()
|
||||
|
||||
const props = defineProps<{
|
||||
rowCount?: number
|
||||
}>()
|
||||
|
||||
const rowCount = Math.max(props.rowCount ?? 0, 14)
|
||||
|
||||
const suits = computed(() => {
|
||||
const uniqueSuits = new Map<number, { name: string; value: number; imageUrl: string }>()
|
||||
infoStore.suits.forEach((a) => {
|
||||
if (!uniqueSuits.has(a.suitId)) {
|
||||
uniqueSuits.set(a.suitId, { name: a.name, value: a.suitId, imageUrl: a.imageUrl })
|
||||
}
|
||||
})
|
||||
return Array.from(uniqueSuits.values())
|
||||
})
|
||||
|
||||
const suitEquipments = computed(() => {
|
||||
const equs = infoStore.stones.filter(
|
||||
(a) => a.suit.includes(curSuit.value.toString()) && a.suit.length === 1,
|
||||
)
|
||||
|
||||
const res: (IEquipment | undefined)[] = []
|
||||
const raritiyList = ['神器', '传说', '史诗']
|
||||
|
||||
raritiyList.forEach((rarity) => {
|
||||
const equ: (IEquipment | undefined)[] = equs.filter((a) => a.rarity === rarity)
|
||||
equ.splice(5, 0, undefined)
|
||||
equ.splice(9, 0, undefined)
|
||||
res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
|
||||
})
|
||||
return res
|
||||
})
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
if (!curRarity.value) return
|
||||
if (curRarity.value == '太初') return
|
||||
suitEquipments.value
|
||||
.filter((a) => !!a && a.rarity == curRarity.value)
|
||||
.splice(0, 11)
|
||||
.forEach((a) => {
|
||||
configStore.chooseEqu(a, true)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- 每排总计6+4+4,共计14个排列 -->
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
|
||||
<template v-for="(equip, _) in equs" :key="_">
|
||||
<EquipmentIcon
|
||||
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.fusion != equip?.id"
|
||||
:equipment="equip"
|
||||
@click="configStore.chooseEqu(equip, true)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup StoneUniversal>
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
rowCount?: number
|
||||
}>()
|
||||
|
||||
const rowCount = Math.max(props.rowCount ?? 0, 14)
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const equs = computed(() => {
|
||||
const items = infoStore.stones.filter((equip) => equip.categorize.includes('通宝'))
|
||||
const res: (IEquipment | undefined)[] = []
|
||||
const raritiyList = ['神器', '史诗']
|
||||
const categorize = Array.from(new Set(items.map((item) => item.categorize)))
|
||||
raritiyList.forEach((rarity) => {
|
||||
categorize.forEach((cat) => {
|
||||
const equ: (IEquipment | undefined)[] = items.filter(
|
||||
(a) => a.rarity === rarity && a.categorize === cat,
|
||||
)
|
||||
equ.splice(5, 0, undefined)
|
||||
res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
|
||||
})
|
||||
})
|
||||
return res
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!-- 每排总计6+4+4,共计14个排列 -->
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
|
||||
<template v-for="(weapon, _) in weapons" :key="_">
|
||||
<EquipmentIcon
|
||||
:equipment="weapon"
|
||||
:inactive="configStore.config.equips['副武器']?.id != weapon?.id"
|
||||
@click="configStore.chooseEqu(weapon, false, true)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
rowCount?: number
|
||||
}>()
|
||||
|
||||
const rowCount = Math.max(props.rowCount ?? 0, 14)
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const weapons = computed(() => {
|
||||
const items = infoStore.equips.filter(
|
||||
(equip) => equip.itemType === '武器' && equip.itemDetailType == '光剑',
|
||||
)
|
||||
// console.log(items)
|
||||
const res: (IEquipment | undefined)[] = []
|
||||
Array.from(new Set(items.map((item) => item.itemDetailType))).forEach((type) => {
|
||||
const weapon = items.filter((item) => item.itemDetailType === type)
|
||||
res.push(
|
||||
...weapon.filter((item) => item.categorize === '制式武器'),
|
||||
undefined,
|
||||
...weapon.filter((item) => item.categorize === '传世武器'),
|
||||
)
|
||||
const remind = rowCount - (res.length % rowCount)
|
||||
res.push(...Array(remind).fill(undefined))
|
||||
})
|
||||
return res
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,111 @@
|
||||
<!-- 每排总计6+4+4,共计14个排列 -->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col gap-10px pb-10px">
|
||||
<!-- 套装选择tab -->
|
||||
<div class="flex flex-wrap w-100%">
|
||||
<template v-for="(suit, _) in suits" :key="_">
|
||||
<div class="flex items-center justify-center">
|
||||
<img
|
||||
class="w-35px h-35px object-contain"
|
||||
:src="getImageURL('equipment' + suit.imageUrl)"
|
||||
:alt="suit.name"
|
||||
:style="{ filter: curSuit === suit.value ? '' : 'grayscale(100%)' }"
|
||||
@click="curSuit = suit.value"
|
||||
@dblclick="handleDoubleClick"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<EquipmentRaritySelect v-model="curRarity" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
|
||||
<template v-for="(equip, _) in suitEquipments" :key="_">
|
||||
<template v-if="equip?.rarity == curRarity || !curRarity">
|
||||
<EquipmentIcon
|
||||
:equipment="equip"
|
||||
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.id != equip?.id"
|
||||
@click="configStore.chooseEqu(equip)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<EquipmentIcon />
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
import EquipmentRaritySelect from '@/components/dnf/Equipment/RaritySelect/index.vue'
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
const curSuit = ref(16201)
|
||||
|
||||
const curRarity = ref()
|
||||
|
||||
const props = defineProps<{
|
||||
rowCount?: number
|
||||
}>()
|
||||
|
||||
const rowCount = Math.max(props.rowCount ?? 0, 14)
|
||||
|
||||
const suits = computed(() => {
|
||||
const uniqueSuits = new Map<number, { name: string; value: number; imageUrl: string }>()
|
||||
infoStore.suits.forEach((a) => {
|
||||
if (!uniqueSuits.has(a.suitId)) {
|
||||
uniqueSuits.set(a.suitId, { name: a.name, value: a.suitId, imageUrl: a.imageUrl })
|
||||
}
|
||||
})
|
||||
return Array.from(uniqueSuits.values())
|
||||
})
|
||||
|
||||
const suitEquipments = computed(() => {
|
||||
const equs = infoStore.equips.filter(
|
||||
(a) => a.suit.includes(curSuit.value.toString()) && a.suit.length === 1,
|
||||
)
|
||||
|
||||
const res: (IEquipment | undefined)[] = []
|
||||
const raritiyList = ['神器', '传说', '史诗']
|
||||
raritiyList.forEach((rarity) => {
|
||||
const equ: (IEquipment | undefined)[] = equs.filter(
|
||||
(a) => a.rarity === rarity && !a.name.includes('黑牙'),
|
||||
)
|
||||
equ.splice(5, 0, undefined)
|
||||
equ.splice(9, 0, undefined)
|
||||
res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
|
||||
})
|
||||
const remind = equs
|
||||
.filter((a) => a.rarity == '太初' || a.name.includes('黑牙'))
|
||||
.sort((a, b) => (a.rarity == '太初' ? 1 : 0) - (b.rarity == '太初' ? 1 : 0))
|
||||
res.push(
|
||||
undefined,
|
||||
undefined,
|
||||
...remind.filter((a) => a.itemDetailType == '项链'),
|
||||
undefined,
|
||||
...remind.filter((a) => a.itemDetailType == '手镯'),
|
||||
undefined,
|
||||
...remind.filter((a) => a.itemDetailType == '戒指'),
|
||||
...Array(Math.max(0, rowCount - remind.length - 4)).fill(undefined),
|
||||
)
|
||||
return res
|
||||
})
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
if (!curRarity.value) return
|
||||
if (curRarity.value == '太初') return
|
||||
suitEquipments.value
|
||||
.filter((a) => !!a && a.rarity == curRarity.value)
|
||||
.splice(0, 11)
|
||||
.forEach((a) => {
|
||||
configStore.chooseEqu(a)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- 每排总计6+4+4,共计14个排列 -->
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
|
||||
<template v-for="(equip, _) in equs" :key="_">
|
||||
<EquipmentIcon
|
||||
:inactive="configStore.config.equips[equip?.itemDetailType ?? '']?.id != equip?.id"
|
||||
:equipment="equip"
|
||||
@click="configStore.chooseEqu(equip)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
rowCount?: number
|
||||
}>()
|
||||
|
||||
const rowCount = Math.max(props.rowCount ?? 0, 14)
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const equs = computed(() => {
|
||||
const items = infoStore.equips.filter(
|
||||
(equip) => equip.categorize === '通宝装备' || equip.categorize === '稀有装备 套装',
|
||||
)
|
||||
const res: (IEquipment | undefined)[] = []
|
||||
const raritiyList = ['稀有', '神器', '传说', '史诗']
|
||||
raritiyList.forEach((rarity) => {
|
||||
const equ: (IEquipment | undefined)[] = items.filter((a) => a.rarity === rarity)
|
||||
equ.splice(5, 0, undefined)
|
||||
equ.splice(9, 0, undefined)
|
||||
res.push(...equ, ...Array(Math.max(0, rowCount - equ.length)).fill(undefined))
|
||||
})
|
||||
return res
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- 每排总计6+4+4,共计14个排列 -->
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap gap-4px content-start" :style="{ width: `${rowCount * 32}px` }">
|
||||
<template v-for="(weapon, _) in weapons" :key="_">
|
||||
<EquipmentIcon
|
||||
:equipment="weapon"
|
||||
:inactive="configStore.config.equips['武器']?.id != weapon?.id"
|
||||
@click="configStore.chooseEqu(weapon)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
rowCount?: number
|
||||
}>()
|
||||
|
||||
const rowCount = Math.max(props.rowCount ?? 0, 14)
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const weapons = computed(() => {
|
||||
const items = infoStore.equips.filter(
|
||||
(equip) => equip.itemType === '武器' && infoStore.infos?.weapons.includes(equip.itemDetailType),
|
||||
)
|
||||
const res: (IEquipment | undefined)[] = []
|
||||
Array.from(new Set(items.map((item) => item.itemDetailType))).forEach((type) => {
|
||||
const weapon = items.filter((item) => item.itemDetailType === type)
|
||||
res.push(
|
||||
...weapon.filter((item) => item.categorize === '制式武器'),
|
||||
undefined,
|
||||
...weapon.filter((item) => item.categorize === '传世武器'),
|
||||
)
|
||||
const remind = rowCount - (res.length % rowCount)
|
||||
res.push(...Array(remind).fill(undefined))
|
||||
})
|
||||
return res
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,496 @@
|
||||
<template>
|
||||
<div class="w-340px min-w-340px shrink-0 bg-black flex flex-col">
|
||||
<calc-dialog lazy header="装备条件" v-model:visible="triggerVisible">
|
||||
<options />
|
||||
</calc-dialog>
|
||||
<!-- 头部 选择是装备还是融合石 -->
|
||||
<div class="flex bg-#101012 h-40px text-#6C5E4A font-bold">
|
||||
<div
|
||||
class="flex-1 text-15px flex items-center justify-center"
|
||||
@click="chooseCategory('equ')"
|
||||
:class="{ active: type === 'equ' }"
|
||||
>
|
||||
装备
|
||||
</div>
|
||||
<div class="w-auto text-18px flex items-center justify-center !font-normal">|</div>
|
||||
<div
|
||||
class="flex-1 text-15px flex items-center justify-center"
|
||||
@click="chooseCategory('oath')"
|
||||
:class="{ active: type === 'oath' }"
|
||||
>
|
||||
誓约
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w-full h-1px bg-gradient-to-r from-[rgba(52,43,27,0.05)] via-[rgba(52,43,27,1)] to-[rgba(52,43,27,0.05)]"
|
||||
></div>
|
||||
<div class="p-1 space-y-1 mt-10px">
|
||||
<div class="flex w-full">
|
||||
<div class="flex flex-1 items-center">
|
||||
部位:<part-select class="!max-w-25 !w-25" v-model:partList="choose_part"></part-select>
|
||||
</div>
|
||||
<div class="flex flex-1 items-center">
|
||||
品质:<EquipmentRaritySelect v-model="curRarity" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
套装:
|
||||
<div class="flex items-center justify-center gap-2px">
|
||||
<template v-for="(suit, _) in suitList" :key="_">
|
||||
<img
|
||||
class="w-auto h-15px"
|
||||
:src="getImageURL('equipment' + suit.imageUrl)"
|
||||
:alt="suit.name"
|
||||
:style="{
|
||||
filter: curSuit == suit.value ? '' : 'grayscale(100%)',
|
||||
}"
|
||||
@click="curSuit = suit.value"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<calc-button
|
||||
icon="set"
|
||||
title="装备触发条件"
|
||||
@click="triggerVisible = !triggerVisible"
|
||||
></calc-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-y-auto gap-1 p-1 flex flex-col items-center flex-1 h-0">
|
||||
<template v-for="item in equs" :key="item.name">
|
||||
<template v-if="item.equs.length > 0">
|
||||
<div class="item-head w-full">{{ item.name }}</div>
|
||||
<div class="grid grid-cols-10 gap-4px w-316px">
|
||||
<template
|
||||
v-for="(equ, index) in item.equs"
|
||||
:key="equ ? `${equ.id}-${equ.oathPos ?? 'equ'}-${index}` : `empty-${index}`"
|
||||
>
|
||||
<EquipmentIcon :inactive="!isActive(equ)" :equipment="equ" @click="chooseEqu(equ)" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-5px flex flex-col">
|
||||
<!-- 基础信息 -->
|
||||
<div
|
||||
class="flex flex-col overflow-hidden bg-black"
|
||||
:class="!hideBase ? '' : 'mb-4px'"
|
||||
v-if="curEquInfo"
|
||||
>
|
||||
<div class="item-head" @click="hideBase = !hideBase">
|
||||
基础信息{{ hideBase ? ' (点击显示)' : '(点击隐藏)' }}
|
||||
</div>
|
||||
<div v-show="!hideBase" class="overflow-y-auto overflow-x-hidden bg-black w-240px">
|
||||
<Info v-if="curEquInfo" :equipment="curEquInfo" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 套装信息 -->
|
||||
<div
|
||||
class="flex flex-col overflow-hidden bg-black"
|
||||
:class="!hideSuit ? 'flex-1' : 'mb-4px'"
|
||||
v-if="curSuitInfo"
|
||||
>
|
||||
<div class="item-head" @click="hideSuit = !hideSuit">
|
||||
套装信息{{ hideSuit ? ' (点击显示)' : '(点击隐藏)' }}
|
||||
</div>
|
||||
<template v-if="!hideSuit">
|
||||
<div class="flex bg-#191918 items-center justify-center gap-2px p-5px">
|
||||
<calc-button
|
||||
:disabled="raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) <= 0"
|
||||
icon="min-little"
|
||||
@click="changeSuitLv('preRarity')"
|
||||
></calc-button>
|
||||
<calc-button
|
||||
icon="reduce-little"
|
||||
:disabled="
|
||||
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) <= 0 &&
|
||||
curSuitLv.lv <= 1
|
||||
"
|
||||
@click="changeSuitLv('preLv')"
|
||||
></calc-button>
|
||||
<div class="flex px-20px gap-x-10px" :class="rarityClass(curSuitLv.rarity)">
|
||||
<span>{{ curSuitLv.rarity }}</span>
|
||||
<span>{{ formatLv(curSuitLv.lv, curSuitLv.rarity) }}</span>
|
||||
</div>
|
||||
<calc-button
|
||||
:disabled="
|
||||
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) >=
|
||||
raritiyList.length - 1
|
||||
"
|
||||
@click="changeSuitLv('nextLv')"
|
||||
icon="increase-little"
|
||||
></calc-button>
|
||||
<calc-button
|
||||
:disabled="
|
||||
raritiyList.findIndex((rarity) => rarity === curSuitLv.rarity) >=
|
||||
raritiyList.length - 1
|
||||
"
|
||||
@click="changeSuitLv('nextRarity')"
|
||||
icon="max-little"
|
||||
></calc-button>
|
||||
</div>
|
||||
<div
|
||||
class="w-80% m-auto h-1px bg-gradient-to-r from-[rgba(52,43,27,0.05)] via-[rgba(52,43,27,1)] to-[rgba(52,43,27,0.05)]"
|
||||
></div>
|
||||
<div class="flex-1 overflow-y-auto overflow-x-hidden bg-black w-240px">
|
||||
<SuitInfo v-if="curSuitInfo" :suit="curSuitInfo" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 overflow-hidden bg-black" v-if="curOathInfo">
|
||||
<div class="item-head">誓约信息</div>
|
||||
|
||||
<div class="flex bg-#191918 items-center justify-center gap-2px p-5px">
|
||||
<calc-button
|
||||
:disabled="raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) <= 0"
|
||||
icon="min-little"
|
||||
@click="changeOathSkillLv('preRarity')"
|
||||
></calc-button>
|
||||
<calc-button
|
||||
icon="reduce-little"
|
||||
:disabled="
|
||||
raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) <= 0 &&
|
||||
curOathSkillLv.lv <= 1
|
||||
"
|
||||
@click="changeOathSkillLv('preLv')"
|
||||
></calc-button>
|
||||
<div class="flex px-20px gap-x-10px" :class="rarityClass(curOathSkillLv.rarity)">
|
||||
<span>{{ curOathSkillLv.rarity }}</span>
|
||||
<span>{{ formatLv(curOathSkillLv.lv, curOathSkillLv.rarity) }}</span>
|
||||
</div>
|
||||
<calc-button
|
||||
:disabled="
|
||||
raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) >=
|
||||
raritiyList.length - 1
|
||||
"
|
||||
@click="changeOathSkillLv('nextLv')"
|
||||
icon="increase-little"
|
||||
></calc-button>
|
||||
<calc-button
|
||||
:disabled="
|
||||
raritiyList.findIndex((rarity) => rarity === curOathSkillLv.rarity) >=
|
||||
raritiyList.length - 1
|
||||
"
|
||||
@click="changeOathSkillLv('nextRarity')"
|
||||
icon="max-little"
|
||||
></calc-button>
|
||||
</div>
|
||||
|
||||
<!-- 技能信息 -->
|
||||
<div class="flex items-center justify-center py-5px">
|
||||
<template v-for="skill in curOathInfo.skills" :key="skill.name">
|
||||
<div
|
||||
class="flex-1 flex flex-col items-center justify-center"
|
||||
:class="{ unActive: skill.skillId != oathSkillId }"
|
||||
@click="oathSkillId = skill.skillId"
|
||||
>
|
||||
<img class="w-40px h-40px" :src="getImageURL(skill.icon)" :alt="skill.name" />
|
||||
<div class="text-#DED29A">{{ skill.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
class="w-80% m-auto h-1px bg-gradient-to-r from-[rgba(52,43,27,0.05)] via-[rgba(52,43,27,1)] to-[rgba(52,43,27,0.05)]"
|
||||
></div>
|
||||
<div class="flex-1 overflow-y-auto overflow-x-hidden bg-black w-240px">
|
||||
<SuitInfo v-if="curOathSkillInfo" :suit="curOathSkillInfo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import PartSelect from '@/components/dnf/Equipment/PartSelect/index.vue'
|
||||
import EquipmentRaritySelect from '@/components/dnf/Equipment/RaritySelect/index.vue'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import { rarityClass } from '@/utils'
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
import EquipmentIcon from '@/components/dnf/Equipment/Icon/index.vue'
|
||||
import Info from '@/components/dnf/Equipment/Info/index.vue'
|
||||
import SuitInfo from '@/components/dnf/Equipment/SuitInfo/index.vue'
|
||||
import { formatEqu, formatOath } from './formatData'
|
||||
import options from './components/options.vue'
|
||||
|
||||
// const props = defineProps<{
|
||||
// alter: string
|
||||
// }>()
|
||||
|
||||
const infoStore = useInfoStore()
|
||||
const type = ref('equ')
|
||||
const curRarity = ref('')
|
||||
const curSuit = ref(16203)
|
||||
const curEquId = ref()
|
||||
const curSuitLv = ref({
|
||||
rarity: '太初',
|
||||
lv: 1,
|
||||
})
|
||||
const curOathSkillLv = ref({
|
||||
rarity: '史诗',
|
||||
lv: 1,
|
||||
})
|
||||
const triggerVisible = ref(false)
|
||||
|
||||
const hideBase = ref(false)
|
||||
const hideSuit = ref(false)
|
||||
const oathSkillId = ref('1')
|
||||
|
||||
const raritiyList = ['稀有', '神器', '传说', '史诗', '太初']
|
||||
|
||||
const formatLv = (number: number, raritiy?: string) => {
|
||||
if (raritiy == '太初') return ''
|
||||
const greekNumerals = ['Ι', 'ΙΙ', 'ΙΙΙ', 'ΙV', 'V', 'VΙ', 'VΙΙ', 'VΙΙΙ', 'ΙX', 'X']
|
||||
return greekNumerals[number - 1] || number.toString()
|
||||
}
|
||||
|
||||
const changeSuitLv = (
|
||||
type: 'preRarity' | 'nextRarity' | 'preLv' | 'nextLv',
|
||||
resetLv: boolean = true,
|
||||
) => {
|
||||
if (type == 'preRarity') {
|
||||
const index = raritiyList.findIndex((a) => a == curSuitLv.value.rarity)
|
||||
if (index > 0) {
|
||||
curSuitLv.value.rarity = raritiyList[index - 1]
|
||||
curSuitLv.value.lv = resetLv ? 1 : 5
|
||||
return
|
||||
}
|
||||
} else if (type == 'nextRarity') {
|
||||
const index = raritiyList.findIndex((a) => a == curSuitLv.value.rarity)
|
||||
if (index < raritiyList.length - 1) {
|
||||
curSuitLv.value.rarity = raritiyList[index + 1]
|
||||
curSuitLv.value.lv = 1
|
||||
return
|
||||
}
|
||||
} else if (type == 'preLv') {
|
||||
if (curSuitLv.value.lv > 1) {
|
||||
curSuitLv.value.lv--
|
||||
return
|
||||
} else {
|
||||
changeSuitLv('preRarity', false)
|
||||
}
|
||||
} else if (type == 'nextLv') {
|
||||
if (curSuitLv.value.lv < 5) {
|
||||
curSuitLv.value.lv++
|
||||
return
|
||||
} else {
|
||||
changeSuitLv('nextRarity')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const changeOathSkillLv = (
|
||||
type: 'preRarity' | 'nextRarity' | 'preLv' | 'nextLv',
|
||||
resetLv: boolean = true,
|
||||
) => {
|
||||
if (type == 'preRarity') {
|
||||
const index = raritiyList.findIndex((a) => a == curOathSkillLv.value.rarity)
|
||||
if (index > 0) {
|
||||
curOathSkillLv.value.rarity = raritiyList[index - 1]
|
||||
curOathSkillLv.value.lv = resetLv ? 1 : 5
|
||||
return
|
||||
}
|
||||
} else if (type == 'nextRarity') {
|
||||
const index = raritiyList.findIndex((a) => a == curOathSkillLv.value.rarity)
|
||||
if (index < raritiyList.length - 1) {
|
||||
curOathSkillLv.value.rarity = raritiyList[index + 1]
|
||||
curOathSkillLv.value.lv = 1
|
||||
return
|
||||
}
|
||||
} else if (type == 'preLv') {
|
||||
if (curOathSkillLv.value.lv > 1) {
|
||||
curOathSkillLv.value.lv--
|
||||
return
|
||||
} else {
|
||||
changeOathSkillLv('preRarity', false)
|
||||
}
|
||||
} else if (type == 'nextLv') {
|
||||
if (curOathSkillLv.value.lv < 5) {
|
||||
curOathSkillLv.value.lv++
|
||||
return
|
||||
} else {
|
||||
changeOathSkillLv('nextRarity')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const choose_part = ref<string[]>([])
|
||||
|
||||
const curEquInfo = computed(() => {
|
||||
if (type.value == 'equ') {
|
||||
return infoStore.equips.find((a) => a.id == curEquId.value)
|
||||
} else {
|
||||
return infoStore.oaths.find((a) => a.id == curEquId.value)
|
||||
}
|
||||
})
|
||||
|
||||
const curSuitInfo = computed(() => {
|
||||
if (curEquInfo.value?.suit.length != 1) {
|
||||
return undefined
|
||||
}
|
||||
return infoStore.suits.find(
|
||||
(a) =>
|
||||
a.suitId.toString() == curEquInfo.value?.suit[0] &&
|
||||
a.rarity == curSuitLv.value.rarity &&
|
||||
a.level == curSuitLv.value.lv,
|
||||
)
|
||||
})
|
||||
|
||||
const curOathInfo = computed(() => {
|
||||
if (curEquInfo.value?.suit.length != 1 || curEquInfo.value?.oathPos == undefined) {
|
||||
return undefined
|
||||
}
|
||||
return infoStore.oathSkills?.[curEquInfo.value?.suit[0]]
|
||||
})
|
||||
|
||||
const curOathSkillInfo = asyncComputed(async () => {
|
||||
if (!curOathInfo.value || !curEquInfo.value?.suit[0]) {
|
||||
return undefined
|
||||
}
|
||||
const res = await infoStore.getOathInfo(
|
||||
curEquInfo.value?.suit[0],
|
||||
oathSkillId.value,
|
||||
curOathSkillLv.value.rarity,
|
||||
curOathSkillLv.value.lv,
|
||||
)
|
||||
return res
|
||||
})
|
||||
|
||||
// const curOathSkillInfo = computed(() => {
|
||||
// const res = curOathSkillLvs.value
|
||||
// if (!res) {
|
||||
// return undefined
|
||||
// }
|
||||
// // console.log(curEquInfo.value?.suit[0])
|
||||
// return {
|
||||
// ...res,
|
||||
// SkillAttack: parseFloat(res.SkillAttack.toString().split(',')[curOathSkillLv.value.lv - 1]),
|
||||
// Buffer: parseFloat(res.Buffer.toString().split(',')[curOathSkillLv.value.lv - 1]),
|
||||
// point: parseFloat(res.point.toString().split(',')[curOathSkillLv.value.lv - 1]),
|
||||
// }
|
||||
// })
|
||||
|
||||
const chooseCategory = (cat: string) => {
|
||||
type.value = cat
|
||||
if (type.value == 'oath' && curSuit.value == -1) {
|
||||
curSuit.value = 16203
|
||||
}
|
||||
}
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const suitList = computed(() => {
|
||||
const uniqueSuits = new Map<number, { name: string; value: number; imageUrl: string }>()
|
||||
infoStore.suits.forEach((a) => {
|
||||
if (!uniqueSuits.has(a.suitId)) {
|
||||
uniqueSuits.set(a.suitId, {
|
||||
name: a.suitName,
|
||||
value: a.suitId,
|
||||
imageUrl: a.imageUrl.replace('/suit/', '/suit/icon/'),
|
||||
})
|
||||
}
|
||||
})
|
||||
const res = Array.from(uniqueSuits.values())
|
||||
if (type.value == 'equ') res.push({ name: '通宝', value: -1, imageUrl: '/suit/icon/-1.png' })
|
||||
return res
|
||||
})
|
||||
|
||||
const equs = computed(() => {
|
||||
// 筛选出装备
|
||||
const total = (type.value == 'equ' ? infoStore.equips : infoStore.oaths).filter(
|
||||
(a) =>
|
||||
(!curRarity.value || a.rarity === curRarity.value) &&
|
||||
(choose_part.value.length === 0 ||
|
||||
choose_part.value.includes(a.itemDetailType || a.itemType)) &&
|
||||
(a.suit.length == 0 ||
|
||||
(curSuit.value == -1 && a.suit.length > 1 && type.value == 'equ') ||
|
||||
(a.suit.length > 1 && type.value == 'oath') ||
|
||||
(a.suit.includes(curSuit.value.toString()) && a.suit.length === 1)),
|
||||
)
|
||||
|
||||
if (type.value == 'equ') {
|
||||
return formatEqu(
|
||||
total,
|
||||
infoStore.infos?.subweapons,
|
||||
suitList.value.find((a) => a.value == curSuit.value)?.name,
|
||||
)
|
||||
} else {
|
||||
return formatOath(
|
||||
total as unknown as IEquipment[],
|
||||
suitList.value.find((a) => a.value == curSuit.value),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const isActive = computed(() => {
|
||||
return (item?: IEquipment) => {
|
||||
if (!item) return false
|
||||
if (type.value == 'equ') {
|
||||
if (item.itemType.includes('武器')) {
|
||||
return configStore.config.equips[item.itemType].id == item.id
|
||||
} else {
|
||||
return configStore.config.equips[item.itemDetailType].id == item.id
|
||||
}
|
||||
} else {
|
||||
return configStore.config.oaths[item.oathPos].id == item.id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const chooseEqu = (item?: IEquipment) => {
|
||||
if (!item) return
|
||||
curEquId.value = item.id
|
||||
const target =
|
||||
type.value === 'equ'
|
||||
? configStore.config.equips[
|
||||
item.itemType.includes('武器') ? item.itemType : item.itemDetailType
|
||||
]
|
||||
: configStore.config.oaths[item.oathPos]
|
||||
if (isActive.value(item) && type.value == 'oath') {
|
||||
// 誓约和星蕴石 有默认穿戴
|
||||
target['id'] = item.oathPos == '11' ? '2000500' : '2000101'
|
||||
} else {
|
||||
target['id'] = isActive.value(item) ? '' : item.id
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.active {
|
||||
color: #ccc088;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 15px;
|
||||
opacity: 0.35;
|
||||
background: #bf8d20;
|
||||
filter: blur(7px);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: url('@/assets/img/active.png') no-repeat;
|
||||
width: 148px;
|
||||
height: 16px;
|
||||
bottom: 2px;
|
||||
transform: translate(0, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
.item-head {
|
||||
background: linear-gradient(#2b2817, #171407);
|
||||
border-top: 1px solid #423d2c;
|
||||
border-bottom: 1px solid #211d15;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.unActive {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 w-250px p-2">
|
||||
<div v-for="option in info.options" :key="option.id" class="flex items-center gap-2">
|
||||
<div class="w-60px text-end">{{ option.name }}</div>
|
||||
<calc-select
|
||||
class="flex-1 !h-20px"
|
||||
:appendTo="dialogRef"
|
||||
v-model="config.config.options[option.id.toString()]"
|
||||
>
|
||||
<calc-option v-for="(item, index) in option.options" :key="index" :value="index">
|
||||
{{ item }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
const dialogRef = inject('dialogRef', ref(null))
|
||||
const config = useConfigStore()
|
||||
const info = useInfoStore()
|
||||
</script>
|
||||
@@ -0,0 +1,326 @@
|
||||
import type { IEquipment } from '@/api/info/type'
|
||||
|
||||
export const rowCount = 10
|
||||
|
||||
export const formatEqu = (total: IEquipment[], subweapons?: string[], curSuit?: string) => {
|
||||
// 武器
|
||||
const item_weapon = total.filter((a) => a.itemType == '武器') ?? []
|
||||
const weapons: (IEquipment | undefined)[] = []
|
||||
if (item_weapon.length > 0) {
|
||||
Array.from(new Set(item_weapon.map((item) => item.rarity)))
|
||||
.filter((a) => !['稀有', '神器'].includes(a))
|
||||
.forEach((rarity) => {
|
||||
const weapon = item_weapon
|
||||
.filter((item) => item.rarity === rarity)
|
||||
.sort((a, b) => {
|
||||
if (a.categorize == b.categorize) {
|
||||
return a.itemDetailType.localeCompare(b.itemDetailType)
|
||||
}
|
||||
return b.categorize.localeCompare(a.categorize)
|
||||
})
|
||||
weapons.push(
|
||||
...weapon.filter((a) => !['传世武器升级', '狄瑞吉传世武器'].includes(a.categorize)),
|
||||
)
|
||||
const remind = weapons.length % rowCount
|
||||
if (remind > 0) {
|
||||
weapons.push(...Array(rowCount - remind).fill(undefined))
|
||||
}
|
||||
})
|
||||
if (item_weapon.some((a) => a.categorize === '传世武器升级')) {
|
||||
const length = item_weapon.filter((a) => a.categorize === '传世武器升级').length
|
||||
weapons.push(...Array(length).fill(undefined))
|
||||
weapons.push(
|
||||
...item_weapon
|
||||
.filter((a) => a.categorize === '传世武器升级')
|
||||
.sort((a, b) => {
|
||||
return a.itemDetailType.localeCompare(b.itemDetailType)
|
||||
}),
|
||||
)
|
||||
}
|
||||
let remind = weapons.length % rowCount
|
||||
if (remind > 0) {
|
||||
weapons.push(...Array(rowCount - remind).fill(undefined))
|
||||
}
|
||||
if (item_weapon.some((a) => a.categorize === '狄瑞吉传世武器')) {
|
||||
const length = item_weapon.filter((a) => a.categorize === '狄瑞吉传世武器').length
|
||||
weapons.push(...Array(length).fill(undefined))
|
||||
weapons.push(
|
||||
...item_weapon
|
||||
.filter((a) => a.categorize === '狄瑞吉传世武器')
|
||||
.sort((a, b) => {
|
||||
return a.itemDetailType.localeCompare(b.itemDetailType)
|
||||
}),
|
||||
)
|
||||
}
|
||||
remind = weapons.length % rowCount
|
||||
if (remind > 0) {
|
||||
weapons.push(...Array(rowCount - remind).fill(undefined))
|
||||
}
|
||||
}
|
||||
|
||||
// # region 副武器
|
||||
const subWeapons: (IEquipment | undefined)[] = []
|
||||
const item_sub_weapon = (total.filter((a) => subweapons?.includes(a.itemDetailType)) ?? []).sort(
|
||||
(a, b) => {
|
||||
if (a.categorize == b.categorize) {
|
||||
return a.itemDetailType.localeCompare(b.itemDetailType)
|
||||
}
|
||||
return b.categorize.localeCompare(a.categorize)
|
||||
},
|
||||
)
|
||||
if (item_sub_weapon.length > 0) {
|
||||
subWeapons.push(
|
||||
...item_sub_weapon.map((a) => ({ ...a, itemType: '副武器' })),
|
||||
...Array(rowCount - (item_sub_weapon.length % rowCount)).fill(undefined),
|
||||
)
|
||||
}
|
||||
// # endregion
|
||||
|
||||
// 秘宝
|
||||
const item_treasure = total.filter((equip) => equip.categorize.includes('秘宝'))
|
||||
const treasures: (IEquipment | undefined)[] = []
|
||||
if (item_treasure.length > 0) {
|
||||
treasures.push(
|
||||
...item_treasure,
|
||||
...Array(rowCount - (item_treasure.length % rowCount)).fill(undefined),
|
||||
)
|
||||
}
|
||||
// 套装
|
||||
const item_suits = total.filter(
|
||||
(equip) => equip.categorize.includes('套装') || equip.categorize.includes('通宝'),
|
||||
)
|
||||
const suits: (IEquipment | undefined)[] = []
|
||||
const raritiyList = Array.from(new Set(item_suits.map((item) => item.rarity))).filter(
|
||||
(a) => !['稀有', '神器'].includes(a),
|
||||
)
|
||||
raritiyList.forEach((rarity) => {
|
||||
const armor = item_suits.filter(
|
||||
(item) => item.rarity === rarity && item.itemType === '防具' && !item.name.includes('侵蚀'),
|
||||
)
|
||||
if (armor.length > 0) {
|
||||
suits.push(...armor, undefined)
|
||||
}
|
||||
|
||||
const special = item_suits.filter(
|
||||
(item) => item.rarity === rarity && item.itemType === '特殊装备',
|
||||
)
|
||||
if (special.length > 0) {
|
||||
suits.push(...special)
|
||||
if (suits.length % rowCount > 0) {
|
||||
suits.push(...Array(rowCount - (suits.length % rowCount)).fill(undefined))
|
||||
}
|
||||
}
|
||||
|
||||
const jewelry = item_suits.filter((item) => item.rarity === rarity && item.itemType === '首饰')
|
||||
const jewelryNormal = jewelry.filter((a) => !a.name.includes('黑牙')) ?? []
|
||||
const jewelryHY = jewelry.filter((a) => a.name.includes('黑牙')) ?? []
|
||||
|
||||
if (jewelry.length > 0) {
|
||||
suits.push(
|
||||
...Array(3 - jewelryHY.length + 2).fill(undefined),
|
||||
...jewelryHY,
|
||||
undefined,
|
||||
...jewelryNormal,
|
||||
)
|
||||
suits.push(...Array(rowCount - (suits.length % rowCount)).fill(undefined))
|
||||
}
|
||||
|
||||
const armorUpgrade = item_suits.filter(
|
||||
(item) => item.rarity === rarity && item.itemType === '防具' && item.name.includes('侵蚀'),
|
||||
)
|
||||
if (armorUpgrade.length > 0) {
|
||||
suits.push(...armorUpgrade)
|
||||
}
|
||||
|
||||
if (suits.length % rowCount > 0) {
|
||||
suits.push(...Array(rowCount - (suits.length % rowCount)).fill(undefined))
|
||||
}
|
||||
})
|
||||
// 称号
|
||||
const item_title = total.filter((equip) => equip.itemDetailType == '称号')
|
||||
const title: (IEquipment | undefined)[] = []
|
||||
if (item_title.length > 0) {
|
||||
title.push(...item_title, ...Array(rowCount - (item_title.length % rowCount)).fill(undefined))
|
||||
}
|
||||
// 宠物
|
||||
const item_pets = total.filter((equip) => equip.itemDetailType == '宠物')
|
||||
const pet: (IEquipment | undefined)[] = []
|
||||
if (item_pets.length > 0) {
|
||||
pet.push(...item_pets, ...Array(rowCount - (item_pets.length % rowCount)).fill(undefined))
|
||||
}
|
||||
|
||||
// 称号
|
||||
return [
|
||||
{
|
||||
name: '武器',
|
||||
equs: weapons,
|
||||
},
|
||||
{
|
||||
name: '副武器',
|
||||
equs: subWeapons,
|
||||
},
|
||||
{
|
||||
name: '秘宝',
|
||||
equs: treasures,
|
||||
},
|
||||
{
|
||||
name: `${curSuit} 套装`,
|
||||
equs: suits,
|
||||
},
|
||||
{
|
||||
name: '称号',
|
||||
equs: title,
|
||||
},
|
||||
{
|
||||
name: '宠物',
|
||||
equs: pet,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export const formatStone = (total: IEquipment[], curSuit?: string) => {
|
||||
// 套装
|
||||
const item_suits = total.filter((equip) => equip.suit.length == 1)
|
||||
const suits: (IEquipment | undefined)[] = []
|
||||
const raritiyList = Array.from(new Set(item_suits.map((item) => item.rarity)))
|
||||
raritiyList.forEach((rarity) => {
|
||||
const armor = item_suits.filter((item) => item.rarity === rarity && item.itemType === '防具')
|
||||
if (armor.length > 0) {
|
||||
suits.push(...armor, undefined)
|
||||
}
|
||||
|
||||
const special = item_suits.filter(
|
||||
(item) => item.rarity === rarity && item.itemType === '特殊装备',
|
||||
)
|
||||
if (special.length > 0) {
|
||||
suits.push(...special)
|
||||
if (suits.length % rowCount > 0) {
|
||||
suits.push(...Array(rowCount - (suits.length % rowCount)).fill(undefined))
|
||||
}
|
||||
}
|
||||
|
||||
const jewelry = item_suits.filter((item) => item.rarity === rarity && item.itemType === '首饰')
|
||||
const jewelryNormal = jewelry.filter((a) => !a.name.includes('黑牙')) ?? []
|
||||
const jewelryHY = jewelry.filter((a) => a.name.includes('黑牙')) ?? []
|
||||
if (jewelry.length > 0) {
|
||||
suits.push(
|
||||
...Array(3 - jewelryHY.length + 2).fill(undefined),
|
||||
...jewelryHY,
|
||||
undefined,
|
||||
...jewelryNormal,
|
||||
)
|
||||
suits.push(...Array(rowCount - (suits.length % rowCount)).fill(undefined))
|
||||
}
|
||||
if (suits.length % rowCount > 0) {
|
||||
suits.push(...Array(rowCount - (suits.length % rowCount)).fill(undefined))
|
||||
}
|
||||
})
|
||||
|
||||
const item_suits_wns = total.filter((equip) => equip.categorize.includes('维纳斯通宝'))
|
||||
const wns: (IEquipment | undefined)[] = []
|
||||
const categorizeList = Array.from(new Set(item_suits_wns.map((item) => item.categorize)))
|
||||
categorizeList.forEach((categorize) => {
|
||||
;['神器', '史诗'].forEach((raritiy) => {
|
||||
const temp = item_suits_wns.filter(
|
||||
(item) => item.categorize === categorize && item.rarity === raritiy,
|
||||
)
|
||||
const remind = temp.length % (rowCount / 2)
|
||||
wns.push(...temp, ...Array(temp.length == 0 ? rowCount / 2 - remind : remind).fill(undefined))
|
||||
})
|
||||
})
|
||||
|
||||
const item_suits_nbe = total.filter((equip) => equip.categorize.includes('纳波尔通宝'))
|
||||
const nbes: (IEquipment | undefined)[] = []
|
||||
const nbeList = Array.from(new Set(item_suits_nbe.map((item) => item.categorize)))
|
||||
nbeList.forEach((categorize) => {
|
||||
;['神器', '史诗'].forEach((raritiy) => {
|
||||
const temp = item_suits_nbe.filter(
|
||||
(item) => item.categorize === categorize && item.rarity === raritiy,
|
||||
)
|
||||
const remind = temp.length % (rowCount / 2)
|
||||
nbes.push(
|
||||
...temp,
|
||||
...Array(temp.length == 0 ? rowCount / 2 - remind : remind - 1).fill(undefined),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
const items_suits_rzs = total.filter((equip) => equip.categorize.includes('人造神'))
|
||||
const rzs: (IEquipment | undefined)[] = []
|
||||
const partList = Array.from(new Set(items_suits_rzs.map((item) => item.itemDetailType)))
|
||||
// const raritiy = Array.from(new Set(items_suits_rzs.map((item) => item.rarity)))
|
||||
partList.forEach((part) => {
|
||||
raritiyList.forEach((rarity) => {
|
||||
const temp = items_suits_rzs.filter(
|
||||
(item) => item.itemDetailType === part && item.rarity === rarity,
|
||||
)
|
||||
if (temp.length > 0) {
|
||||
rzs.push(...temp, ...Array(rowCount - temp.length).fill(undefined))
|
||||
}
|
||||
})
|
||||
// const temp = items_suits_rzs.filter((item) => item.itemDetailType === part)
|
||||
// const remind = temp.length % rowCount
|
||||
// rzs.push(...temp, ...Array(temp.length == 0 ? rowCount-remind : remind).fill(undefined))
|
||||
})
|
||||
// const items_sj = total.filter((equip) => equip.categorize.includes('神界'))
|
||||
// const sj: (IEquipment | undefined)[] = []
|
||||
// sj.push(...items_sj, ...Array(rowCount * 2 - (items_sj.length % rowCount)).fill(undefined))
|
||||
// const temp = items_suits_rzs.filter((item) => item.itemDetailType === part)
|
||||
// const remind = temp.length % rowCount
|
||||
// rzs.push(...temp, ...Array(temp.length == 0 ? rowCount-remind : remind).fill(undefined))
|
||||
return [
|
||||
{
|
||||
name: `${curSuit} 套装`,
|
||||
equs: suits,
|
||||
},
|
||||
{
|
||||
name: '维纳斯通宝',
|
||||
equs: wns,
|
||||
},
|
||||
{
|
||||
name: '纳波尔通宝',
|
||||
equs: nbes,
|
||||
},
|
||||
{
|
||||
name: '人造神',
|
||||
equs: rzs,
|
||||
},
|
||||
// {
|
||||
// name: '神界',
|
||||
// equs: sj,
|
||||
// },
|
||||
]
|
||||
}
|
||||
|
||||
export const formatOath = (
|
||||
total: IEquipment[],
|
||||
curSuit?: { name: string; value: number; imageUrl: string },
|
||||
) => {
|
||||
// oath
|
||||
const oath = total.filter(
|
||||
(equip) => equip.itemDetailType == '誓约' && equip.categorize != '通宝装备',
|
||||
)
|
||||
oath.push(...Array(rowCount - (oath.length % rowCount)).fill(undefined))
|
||||
// primer
|
||||
const primer = []
|
||||
const primerItem = total.filter(
|
||||
(equip) => equip.itemDetailType == '星蕴石' && equip.rarity != '稀有',
|
||||
)
|
||||
for (let i = 0; i < 11; i++) {
|
||||
const temp = primerItem.filter((a) => a.oathPos == i.toString())
|
||||
primer.push(
|
||||
...temp,
|
||||
...Array(temp.length == 0 ? rowCount : rowCount - (temp.length % rowCount)).fill(undefined),
|
||||
)
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: `${curSuit?.name} 誓约`,
|
||||
equs: oath,
|
||||
},
|
||||
{
|
||||
name: `${curSuit?.name} 星蕴石`,
|
||||
equs: primer,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<script lang="tsx">
|
||||
import { computed, defineComponent, ref, renderList } from 'vue'
|
||||
import type { IEnchantingInfo } from '@/api/info/type'
|
||||
import { useInfoStore, useConfigStore } from '@/stores'
|
||||
import ClothList from '@/components/dnf/Avatar/List/index.vue'
|
||||
export default defineComponent({
|
||||
name: 'Clothes',
|
||||
setup() {
|
||||
const configStore = useConfigStore()
|
||||
const part = ref('武器装扮')
|
||||
|
||||
// const globalStore = useGlobalStore()
|
||||
|
||||
const basicInfoStore = useInfoStore()
|
||||
const has_enchant = computed(() => {
|
||||
return !['头部', '帽子', '脸部', '胸部', '上衣', '腰带', '下装', '鞋'].includes(
|
||||
part.value as string,
|
||||
)
|
||||
})
|
||||
|
||||
const enchant_list = computed<IEnchantingInfo[] | undefined>(() => {
|
||||
return basicInfoStore.enchants
|
||||
?.filter((item) => item.position.includes(part.value))
|
||||
.sort((a, b) => (b.fame ?? 0) - (a.fame ?? 0))
|
||||
})
|
||||
|
||||
// const emblem_list = computed<IEnchantingInfo[] | undefined>(() => {
|
||||
// return basicInfoStore.emblems.filter((item) => item.position.includes(part.value))
|
||||
// })
|
||||
|
||||
const currentInfo = function <T>(name: string, defaultValue?: T) {
|
||||
return computed<string | number>({
|
||||
get() {
|
||||
let key: 'avatar' | 'equips' = 'avatar'
|
||||
if (part.value == '宠物') key = 'equips'
|
||||
return (
|
||||
(configStore.config[key]?.[part.value] as Record<string, string | number>)?.[name] ??
|
||||
defaultValue ??
|
||||
0
|
||||
)
|
||||
},
|
||||
set(val) {
|
||||
if (val == undefined) {
|
||||
return
|
||||
}
|
||||
let key: 'avatar' | 'equips' = 'avatar'
|
||||
if (part.value == '宠物') key = 'equips'
|
||||
if (!configStore.config[key][part.value]) {
|
||||
configStore.config[key][part.value] =
|
||||
key === 'avatar'
|
||||
? { ...configStore.defaultAvatarConfig }
|
||||
: { ...configStore.defaultEqusConfig }
|
||||
}
|
||||
if (configStore.config[key][part.value]) {
|
||||
;(configStore.config[key][part.value] as Record<string, string | number>)[name] = val
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 附魔
|
||||
const enchant = currentInfo<string | number>('enchant')
|
||||
|
||||
// // 镶嵌栏1
|
||||
// const emblem_0 = currentInfo<string | number>('emblem_0', 0)
|
||||
|
||||
// // 镶嵌栏2
|
||||
// const emblem_1 = currentInfo<string | number>('emblem_1')
|
||||
|
||||
// 品级
|
||||
const id = currentInfo<string | number>('id')
|
||||
|
||||
// 属性
|
||||
const option = currentInfo<string | number>('option')
|
||||
|
||||
/**
|
||||
* 同步徽章1到徽章2
|
||||
* @param val
|
||||
*/
|
||||
// function changeSocket(val: number) {
|
||||
// emblem_1.value = val
|
||||
// }
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<>
|
||||
<div class="h-120px flex justify-center items-center">
|
||||
<ClothList v-model:part={part.value} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap equ-profile">
|
||||
<div class="equ-profile-item">
|
||||
<div class="mr-10px row-name">当前部位</div>
|
||||
<span> {part.value}</span>
|
||||
</div>
|
||||
{has_enchant.value ? (
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">{part.value == '宠物' ? '附魔' : '属性'}</div>
|
||||
<calc-select v-model={enchant.value} class="flex-1 !h-20px">
|
||||
<calc-option value={0}>无</calc-option>
|
||||
{renderList(enchant_list.value ?? [], (item) => (
|
||||
<calc-option value={item.id}>{item.detail}</calc-option>
|
||||
))}
|
||||
</calc-select>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">品级</div>
|
||||
<calc-select v-model={id.value} class="flex-1 !h-20px">
|
||||
{renderList(basicInfoStore.avatars[part.value] ?? [], (item) => (
|
||||
<calc-option value={item.id}>{item.suit}</calc-option>
|
||||
))}
|
||||
</calc-select>
|
||||
</div>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">属性</div>
|
||||
<calc-select v-model={option.value} class="flex-1 !h-20px">
|
||||
<calc-option value={0}>无</calc-option>
|
||||
{renderList(
|
||||
basicInfoStore.avatars[part.value].find((a) => a.id == id.value)?.options ??
|
||||
[],
|
||||
(item) => (
|
||||
<calc-option value={item}>{item}</calc-option>
|
||||
),
|
||||
)}
|
||||
</calc-select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div class="h-179px flex justify-center items-center">
|
||||
<EmblemList :withBg="true" v-model:part="part" :detail="configStore.config.emblems" />
|
||||
</div>
|
||||
<div class="flex flex-wrap equ-profile">
|
||||
<div class="equ-profile-item">
|
||||
<div class="mr-10px row-name">当前槽位</div>
|
||||
<span>{{ part }}</span>
|
||||
</div>
|
||||
<div class="equ-profile-item" v-if="part != '白金'">
|
||||
<div class="row-name">等级</div>
|
||||
<calc-select v-model="lv" class="flex-1 !h-20px">
|
||||
<template v-for="(i, index) in lvs" :key="index">
|
||||
<calc-option :value="index">{{ i }}</calc-option>
|
||||
</template>
|
||||
</calc-select>
|
||||
</div>
|
||||
<div class="flex w-full">
|
||||
<div class="flex-basis-50px text-center mr-10px">徽章</div>
|
||||
<div class="flex-1 grid gap-4px" :style="emblemGridStyle">
|
||||
<div
|
||||
v-for="(emblem, index) in emblemMaxCount"
|
||||
:style="emblemItemStyle(index)"
|
||||
class="flex"
|
||||
:key="index"
|
||||
>
|
||||
<calc-select v-model="emblems[index]" class="min-w-0 flex-1 !h-20px">
|
||||
<template v-for="(i, index) in emblemOptions" :key="index">
|
||||
<calc-option :value="i.id">[{{ i.rarity }}]{{ i.value }}</calc-option>
|
||||
</template>
|
||||
</calc-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="Emblems">
|
||||
import EmblemList from '@/components/dnf/Emblem/List/index.vue'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
|
||||
const part = ref('彩色')
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const infoStore = useInfoStore()
|
||||
|
||||
const lvs = [
|
||||
'稀有Lv.1',
|
||||
'稀有Lv.2',
|
||||
'稀有Lv.3',
|
||||
'稀有Lv.4',
|
||||
'稀有Lv.5',
|
||||
'神器Lv.1',
|
||||
'神器Lv.2',
|
||||
'神器Lv.3',
|
||||
'神器Lv.4',
|
||||
'神器Lv.5',
|
||||
'传说Lv.1',
|
||||
'传说Lv.2',
|
||||
'传说Lv.3',
|
||||
'传说Lv.4',
|
||||
'传说Lv.5',
|
||||
'史诗Lv.1',
|
||||
]
|
||||
|
||||
const emblemOptions = computed(() => {
|
||||
if (part.value == '彩色') {
|
||||
return infoStore.emblems.filter((a) => a.type != '白金').sort((a, b) => b.fame - a.fame)
|
||||
}
|
||||
return infoStore.emblems
|
||||
.filter((a) => {
|
||||
const color = part.value[0]
|
||||
// if (part.value == '红色' || part.value == '绿色') {
|
||||
// return a.type == part.value || a.type == '红绿' || a.type == '彩色'
|
||||
// }
|
||||
// if (part.value == '黄色' || part.value == '蓝色') {
|
||||
// return a.type == part.value || a.type == '蓝黄' || a.type == '彩色'
|
||||
// }
|
||||
return a.type == part.value || a.type == '彩色' || a.type.includes(color)
|
||||
})
|
||||
.sort((a, b) => b.fame - a.fame)
|
||||
})
|
||||
|
||||
const emblemMaxCount = computed(() => {
|
||||
if (part.value == '彩色') {
|
||||
return 6
|
||||
}
|
||||
if (part.value == '白金') {
|
||||
return 2
|
||||
}
|
||||
return 4
|
||||
})
|
||||
|
||||
const emblemGridStyle = computed(() => {
|
||||
return {
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
}
|
||||
})
|
||||
|
||||
const emblemItemStyle = (index: number) => {
|
||||
const count = emblemMaxCount.value
|
||||
const remainder = count % 2
|
||||
|
||||
if (remainder === 1 && index === count - 1) {
|
||||
return {
|
||||
gridColumn: 'span 2 / span 2',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
gridColumn: 'span 1 / span 1',
|
||||
}
|
||||
}
|
||||
|
||||
const ensurePartConfig = (partName: string) => {
|
||||
if (!configStore.config.emblems[partName]) {
|
||||
configStore.config.emblems[partName] = configStore.createDefaultEmblemConfig()
|
||||
}
|
||||
const target = configStore.config.emblems[partName]
|
||||
if (!Array.isArray(target.items)) {
|
||||
target.items = ['', '', '', '', '', '']
|
||||
}
|
||||
if (target.items.length < 6) {
|
||||
target.items = [...target.items, ...Array.from({ length: 6 - target.items.length }, () => '')]
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
part,
|
||||
(newPart) => {
|
||||
ensurePartConfig(newPart)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const currentInfo = function <T>(name: string, defaultValue?: T) {
|
||||
return computed<string | number>({
|
||||
get() {
|
||||
ensurePartConfig(part.value)
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(configStore.config.emblems?.[part.value] as Record<string, any>)?.[name] ??
|
||||
defaultValue ??
|
||||
0
|
||||
)
|
||||
},
|
||||
set(val) {
|
||||
if (val == undefined) {
|
||||
return
|
||||
}
|
||||
// if (name == 'emblem_1' && !has_emblem_1.value) {
|
||||
// // configStore.setForge(part.value, name, 0)
|
||||
// return
|
||||
// }
|
||||
|
||||
ensurePartConfig(part.value)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(configStore.config.emblems[part.value] as Record<string, any>)[name] = val
|
||||
if (part.value == '彩色' && name == 'lv') {
|
||||
// 同步其他部位的等级
|
||||
const lv = val as number
|
||||
for (const p of ['红色', '蓝色', '黄色', '绿色']) {
|
||||
ensurePartConfig(p)
|
||||
configStore.config.emblems[p]['lv'] = Math.max(
|
||||
configStore.config.emblems[p]['lv'] || 0,
|
||||
lv,
|
||||
)
|
||||
}
|
||||
} else if (part.value != '彩色' && name == 'lv') {
|
||||
// 同步彩色部位的等级
|
||||
const lv = val as number
|
||||
ensurePartConfig('彩色')
|
||||
configStore.config.emblems['彩色']['lv'] = Math.min(
|
||||
configStore.config.emblems['彩色']['lv'] || 0,
|
||||
lv,
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const lv = currentInfo('lv', 0)
|
||||
|
||||
const emblems = currentInfo<string[]>('items', ['', '', '', '', '', '']) as unknown as Ref<string[]>
|
||||
</script>
|
||||
@@ -0,0 +1,251 @@
|
||||
<script lang="tsx">
|
||||
import { computed, defineComponent, ref, renderList } from 'vue'
|
||||
import type { IEnchantingInfo } from '@/api/info/type'
|
||||
import { useInfoStore, useConfigStore } from '@/stores'
|
||||
import EquList from '@/components/dnf/Equipment/List/index.vue'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
export default defineComponent({
|
||||
name: 'Equip',
|
||||
setup() {
|
||||
const configStore = useConfigStore()
|
||||
const part = ref('头肩')
|
||||
const can_upgrade = computed(() => {
|
||||
return !['称号', '宠物'].includes(part.value as string)
|
||||
})
|
||||
|
||||
// const globalStore = useGlobalStore()
|
||||
|
||||
/** 是否可以设置秘宝精度 */
|
||||
const can_set_secret = computed(() => {
|
||||
return ['武器', '辅助装备', '魔法石', '耳环'].includes(part.value as string)
|
||||
})
|
||||
const basicInfoStore = useInfoStore()
|
||||
|
||||
const enchant_list = computed<IEnchantingInfo[] | undefined>(() => {
|
||||
return basicInfoStore.enchants
|
||||
?.filter(
|
||||
(item) =>
|
||||
item.position.includes(part.value) &&
|
||||
!item.position.includes('武器装扮') &&
|
||||
!item.position.includes('宠物装备'),
|
||||
)
|
||||
.sort((a, b) => (b.fame ?? 0) - (a.fame ?? 0))
|
||||
})
|
||||
|
||||
// const emblem_list = computed<IEnchantingInfo[] | undefined>(() => {
|
||||
// return basicInfoStore.emblems.filter((item) => item.position.includes(part.value))
|
||||
// })
|
||||
|
||||
const global_change = ref(false)
|
||||
|
||||
const currentInfo = function <T>(name: string, defaultValue?: T) {
|
||||
return computed<string | number>({
|
||||
get() {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(configStore.config.equips?.[part.value] as Record<string, any>)?.[name] ??
|
||||
defaultValue ??
|
||||
0
|
||||
)
|
||||
},
|
||||
set(val) {
|
||||
if (val == undefined && name !== 'weaponFusion') {
|
||||
return
|
||||
}
|
||||
// if (name == 'emblem_1' && !has_emblem_1.value) {
|
||||
// // configStore.setForge(part.value, name, 0)
|
||||
// return
|
||||
// }
|
||||
|
||||
let parts: string[] = []
|
||||
|
||||
if (global_change.value && name !== 'weaponFusion') {
|
||||
if (name === 'enchant') {
|
||||
const enchant = basicInfoStore?.enchants?.find((item) => item.id == val) as
|
||||
| IEnchantingInfo
|
||||
| undefined
|
||||
if (enchant?.position) {
|
||||
parts = enchant.position
|
||||
}
|
||||
}
|
||||
// if (['emblem_0', 'emblem_1'].includes(name)) {
|
||||
// const emblem = basicInfoStore?.emblems?.find(
|
||||
// (item) => item.id.toString() == val.toString(),
|
||||
// ) as IEnchantingInfo | undefined
|
||||
// if (emblem?.position) {
|
||||
// parts = emblem.position.filter(
|
||||
// (item) => !['皮肤', '武器装扮', '光环'].includes(item),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
if (['reinforceType', 'reinforce', 'adaptation'].includes(name)) {
|
||||
parts = basicInfoStore.parts.filter((e) => !['称号', '宠物'].includes(e))
|
||||
}
|
||||
} else {
|
||||
parts = [part.value]
|
||||
}
|
||||
for (const part of parts) {
|
||||
if (!configStore.config.equips[part]) {
|
||||
configStore.config.equips[part] = { ...configStore.defaultEqusConfig }
|
||||
}
|
||||
if (configStore.config.equips[part]) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(configStore.config.equips[part] as Record<string, any>)[name] = val
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 附魔
|
||||
const enchant = currentInfo<string | number>('enchant')
|
||||
|
||||
// // 镶嵌栏1
|
||||
// const emblem_0 = currentInfo<string | number>('emblem_0', 0)
|
||||
|
||||
// // 镶嵌栏2
|
||||
// const emblem_1 = currentInfo<string | number>('emblem_1')
|
||||
|
||||
// 增幅
|
||||
const reinforce_type = currentInfo<string | number>('reinforceType', 1)
|
||||
|
||||
// 增幅数值
|
||||
const reinforce = currentInfo<string | number>('reinforce')
|
||||
|
||||
// 锻造数值
|
||||
const refine = currentInfo<string | number>('refine')
|
||||
|
||||
// 调适
|
||||
const adaptation = currentInfo<string | number>('adaptation')
|
||||
|
||||
//精度
|
||||
const precision = currentInfo<number>('precision', 0)
|
||||
|
||||
// 武器融合
|
||||
return () => {
|
||||
return (
|
||||
<>
|
||||
<div class="h-150px flex justify-center items-center">
|
||||
<EquList
|
||||
v-model:part={part.value}
|
||||
withBg={false}
|
||||
withPet={false}
|
||||
with-sub-weapon={useInfoStore().infos?.alter.includes('vegabond')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap equ-profile">
|
||||
<div class="equ-profile-item">
|
||||
<div class="mr-10px row-name">当前部位</div>
|
||||
<span> {part.value}</span>
|
||||
{can_upgrade.value ? (
|
||||
<calc-checkbox
|
||||
style="margin-left:auto"
|
||||
v-model={global_change.value}
|
||||
label="全局修改"
|
||||
></calc-checkbox>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
</div>
|
||||
{part.value == '副武器' ? (
|
||||
<>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">增幅</div>
|
||||
<calc-select v-model={reinforce_type.value} class="flex-1 !h-20px">
|
||||
<calc-option value={0}>强化</calc-option>
|
||||
<calc-option value={1}>增幅</calc-option>
|
||||
</calc-select>
|
||||
<calc-select v-model={reinforce.value} class="flex-1 !h-20px">
|
||||
{renderList(31, (i) => (
|
||||
<calc-option value={i - 1}>{i - 1}</calc-option>
|
||||
))}
|
||||
</calc-select>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{can_upgrade.value ? (
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">增幅</div>
|
||||
<calc-select v-model={reinforce_type.value} class="flex-1 !h-20px">
|
||||
<calc-option value={0}>强化</calc-option>
|
||||
<calc-option value={1}>增幅</calc-option>
|
||||
</calc-select>
|
||||
<calc-select v-model={reinforce.value} class="flex-1 !h-20px">
|
||||
{renderList(31, (i) => (
|
||||
<calc-option value={i - 1}>{i - 1}</calc-option>
|
||||
))}
|
||||
</calc-select>
|
||||
{part.value == '武器' && (
|
||||
<calc-select v-model={refine.value} class="flex-1 !h-20px">
|
||||
{renderList(9, (i) => (
|
||||
<calc-option value={i - 1}>锻造+ {i - 1}</calc-option>
|
||||
))}
|
||||
</calc-select>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">附魔</div>
|
||||
<calc-select v-model={enchant.value} class="flex-1 !h-20px">
|
||||
<calc-option value={''}>无</calc-option>
|
||||
{renderList(enchant_list.value ?? [], (item) => (
|
||||
<calc-option value={item.id}>{item.detail}</calc-option>
|
||||
))}
|
||||
</calc-select>
|
||||
</div>
|
||||
|
||||
{can_upgrade.value ? (
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">调适</div>
|
||||
{/* <calc-slider
|
||||
showValue={true}
|
||||
showPercent={false}
|
||||
v-model={adaptation.value}
|
||||
min={0}
|
||||
max={3}
|
||||
step={1}
|
||||
class="flex-1 !h-20px"
|
||||
/> */}
|
||||
<calc-select v-model={adaptation.value} class="flex-1 !h-20px">
|
||||
{renderList(4, (i) => (
|
||||
<calc-option value={i - 1}>{i - 1}</calc-option>
|
||||
))}
|
||||
</calc-select>
|
||||
</div>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
{can_set_secret.value ? (
|
||||
<>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">精度</div>
|
||||
<calc-slider
|
||||
showPercent={true}
|
||||
showValue={false}
|
||||
v-model={precision.value}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
class="flex-1 !h-20px"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="key in ['0', '1', '2', '3']" :key="key">
|
||||
<div class="flex gap-2 mt-5px">
|
||||
<calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].id">
|
||||
<calc-option :value="0">无</calc-option>
|
||||
<calc-option v-for="item in infoStore.jades" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
<!-- <calc-slider
|
||||
v-model="configStore.config.jades[key].value"
|
||||
:formatValue="formatValue(configStore.config.jades[key].id)"
|
||||
:min="Number(currentJade(configStore.config.jades[key].id)?.min)"
|
||||
:max="Number(currentJade(configStore.config.jades[key].id)?.max)"
|
||||
:step="Number(currentJade(configStore.config.jades[key].id)?.pre)"
|
||||
></calc-slider> -->
|
||||
<calc-select class="flex-1 !h-20px" v-model="configStore.config.jades[key].value">
|
||||
<calc-option
|
||||
v-for="item in valueList(configStore.config.jades[key].id)"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.value }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IJade } from '@/api/info/type'
|
||||
import { useConfigStore } from '@/stores'
|
||||
import { useInfoStore } from '@/stores'
|
||||
import { getFloat } from '@/utils'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const infoStore = useInfoStore()
|
||||
|
||||
// const currentJade = computed(() => {
|
||||
// return function (id: number = -1) {
|
||||
// if (id == -1) {
|
||||
// return undefined
|
||||
// } else {
|
||||
// const cur = infoStore.jades?.find((e) => e.id == id) as IJade
|
||||
// return cur
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
// const formatValue = (id: number) => {
|
||||
// return (value: number) => {
|
||||
// if (currentJade.value(id)?.unit.includes('等级')) {
|
||||
// return `$等级 +${value}`
|
||||
// }
|
||||
// return `${getFloat(value, 1)}${currentJade.value(id)?.unit}`
|
||||
// }
|
||||
// }
|
||||
|
||||
const valueList = computed(() => {
|
||||
return function (id: number = -1) {
|
||||
if (id == -1) {
|
||||
return undefined
|
||||
} else {
|
||||
const res = []
|
||||
const cur = infoStore.jades?.find((e) => e.id == id) as IJade
|
||||
if (cur) {
|
||||
const { max, min, unit, pre } = cur
|
||||
//
|
||||
if (unit.includes('等级')) {
|
||||
res.push({
|
||||
id: 1,
|
||||
value: unit,
|
||||
})
|
||||
} else {
|
||||
for (let i = Number(max) as number; i >= Number(min);) {
|
||||
if (i != 0) {
|
||||
res.push({
|
||||
id: i,
|
||||
value: (i > 0 ? '+' : '') + getFloat(i, 1) + unit,
|
||||
})
|
||||
}
|
||||
i = Number((i - Number(pre)).toFixed(1))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="h-150px flex justify-center items-center">
|
||||
<Oath v-model:part="part" :withBg="false" />
|
||||
</div>
|
||||
<div class="flex flex-wrap equ-profile">
|
||||
<div class="equ-profile-item">
|
||||
<div class="mr-10px row-name">当前部位</div>
|
||||
<span>{{ part == '11' ? '誓约' : `星蕴石-${parseInt(part) + 1}` }}</span>
|
||||
<calc-checkbox
|
||||
style="margin-left: auto"
|
||||
v-model="global_change"
|
||||
label="全局修改"
|
||||
v-if="part !== '11'"
|
||||
></calc-checkbox>
|
||||
</div>
|
||||
<div class="equ-profile-item" v-if="part !== '11'">
|
||||
<div class="row-name">调适</div>
|
||||
<calc-select v-model="adaptation" class="flex-1 !h-20px">
|
||||
<template v-for="i in Array.from({ length: 4 }, (_, i) => i)" :key="i">
|
||||
<calc-option :value="i">{{ i }}</calc-option>
|
||||
</template>
|
||||
</calc-select>
|
||||
</div>
|
||||
<div class="equ-profile-item" v-else>
|
||||
<div class="row-name">誓约升级</div>
|
||||
<template v-for="i in Array.from({ length: 4 }, (_, i) => i)" :key="i">
|
||||
<calc-select
|
||||
:model-value="getOathUpgradeValue(i)"
|
||||
@update:model-value="setOathUpgradeValue(i, $event)"
|
||||
class="flex-1 !h-20px"
|
||||
>
|
||||
<calc-option :value="0">无</calc-option>
|
||||
<calc-option :value="1">技攻 +4%</calc-option>
|
||||
<calc-option :value="2">CD -8%</calc-option>
|
||||
</calc-select>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="Oaths">
|
||||
import Oath from '@/components/dnf/Equipment/Oath/index.vue'
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
|
||||
const part = ref('0')
|
||||
|
||||
const global_change = ref(false)
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const infoStore = useInfoStore()
|
||||
|
||||
const currentInfo = function <T>(name: string, defaultValue?: T) {
|
||||
return computed<T>({
|
||||
get() {
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(configStore.config.oaths?.[part.value] as Record<string, any>)?.[name] ?? defaultValue ?? 0
|
||||
)
|
||||
},
|
||||
set(val) {
|
||||
if (val == undefined) {
|
||||
return
|
||||
}
|
||||
// if (name == 'emblem_1' && !has_emblem_1.value) {
|
||||
// // configStore.setForge(part.value, name, 0)
|
||||
// return
|
||||
// }
|
||||
let parts: string[] = []
|
||||
if (global_change.value) {
|
||||
parts = infoStore.oathParts
|
||||
} else {
|
||||
parts = [part.value]
|
||||
}
|
||||
|
||||
for (const part of parts) {
|
||||
if (!configStore.config.oaths[part]) {
|
||||
configStore.config.oaths[part] = configStore.createDefaultOathConfig()
|
||||
}
|
||||
if (configStore.config.oaths[part]) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(configStore.config.oaths[part] as Record<string, any>)[name] = val
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 调适
|
||||
const adaptation = currentInfo<string | number>('adaptation')
|
||||
// 誓约升级
|
||||
const oath_upgrade = currentInfo<number[]>('upgrade', [0, 0, 0, 0])
|
||||
|
||||
const getOathUpgradeValue = (index: number) => oath_upgrade.value[index] ?? 0
|
||||
|
||||
const setOathUpgradeValue = (index: number, val: number) => {
|
||||
const nextUpgrade = [...oath_upgrade.value]
|
||||
nextUpgrade[index] = val
|
||||
oath_upgrade.value = nextUpgrade
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="flex flex-wrap equ-profile">
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">迷雾系统</div>
|
||||
<calc-slider
|
||||
:showPercent="true"
|
||||
:formatValue="(lv: number) => `${lv} 级`"
|
||||
v-model="sundryData['fog']"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
class="flex-1 !h-20px"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div class="equ-profile-item">
|
||||
<div class="row-name">勋章</div>
|
||||
<calc-select
|
||||
v-for="(key, index) in ['medal_rarity', 'medal_reinforce']"
|
||||
:key="index"
|
||||
v-model="sundryData[key as keyof typeof sundryData]"
|
||||
class="flex-1 !h-20px"
|
||||
>
|
||||
<calc-option v-for="(item, idx) in list(key)" :key="idx" :value="item.value">
|
||||
{{ item.name }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">宝石</div>
|
||||
<calc-select
|
||||
v-for="(key, index) in ['medal_gem_0', 'medal_gem_1', 'medal_gem_2', 'medal_gem_3']"
|
||||
:key="index"
|
||||
v-model="sundryData[key as keyof typeof sundryData]"
|
||||
class="flex-1 !h-20px !min-w-10"
|
||||
>
|
||||
<calc-option v-for="(item, idx) in list(key)" :key="idx" :value="item.value">
|
||||
{{ item.name }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div> -->
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">冒险团</div>
|
||||
<calc-slider
|
||||
:showPercent="true"
|
||||
:formatValue="(lv: number) => `${lv} 级`"
|
||||
v-model="sundryData['adventure']"
|
||||
:min="1"
|
||||
:max="40"
|
||||
:step="1"
|
||||
class="flex-1 !h-20px"
|
||||
/>
|
||||
</div>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">结婚系统</div>
|
||||
<calc-select
|
||||
v-for="(key, index) in ['marriage_house', 'marriage_ring']"
|
||||
:key="index"
|
||||
v-model="sundryData[key as keyof typeof sundryData]"
|
||||
class="flex-1 !h-20px !min-w-10"
|
||||
>
|
||||
<calc-option v-for="(item, idx) in list(key)" :key="idx" :value="item.value">
|
||||
{{ item.name }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">晶体契约</div>
|
||||
<calc-select v-model="sundryData['contract']" class="flex-1 !h-20px">
|
||||
<calc-option v-for="(item, idx) in list('contract')" :key="idx" :value="item.value">
|
||||
{{ item.name }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">收集箱</div>
|
||||
<calc-select
|
||||
v-for="(key, index) in ['collection_type', 'collection_num_0', 'collection_num_1']"
|
||||
:key="index"
|
||||
v-model="sundryData[key as keyof typeof sundryData]"
|
||||
class="flex-1 !h-20px !min-w-10"
|
||||
>
|
||||
<calc-option v-for="(item, idx) in list(key)" :key="idx" :value="item.value">
|
||||
{{ item.name }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div>
|
||||
<div class="equ-profile-item">
|
||||
<div class="row-name">名称装扮</div>
|
||||
<calc-select v-model="sundryData['costume_card']" class="flex-1 !h-20px !min-w-10">
|
||||
<calc-option v-for="(item, idx) in list('costume_card')" :key="idx" :value="item.value">
|
||||
{{ item.name }}
|
||||
</calc-option>
|
||||
</calc-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const infoStore = useInfoStore()
|
||||
const currentInfo = function <T>(name: string, defaultValue?: T) {
|
||||
return computed<string | number>({
|
||||
get() {
|
||||
return (configStore.config.sundry?.[name] ?? defaultValue ?? 0) as string | number
|
||||
},
|
||||
set(val) {
|
||||
if (val == undefined) {
|
||||
return
|
||||
}
|
||||
configStore.config.sundry[name] = val
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const list = computed(() => {
|
||||
return (key: string) => {
|
||||
return infoStore.sundries[key].options
|
||||
}
|
||||
})
|
||||
|
||||
const sundryData = reactive({
|
||||
medal_rarity: currentInfo('medal_rarity', 5),
|
||||
medal_reinforce: currentInfo('medal_reinforce', 12),
|
||||
medal_gem_0: currentInfo('medal_gem_0', 4),
|
||||
medal_gem_1: currentInfo('medal_gem_1', 4),
|
||||
medal_gem_2: currentInfo('medal_gem_2', 4),
|
||||
medal_gem_3: currentInfo('medal_gem_3', 4),
|
||||
adventure: currentInfo('adventure', 30),
|
||||
fog: currentInfo('fog', 10),
|
||||
marriage_house: currentInfo('marriage_house', 4),
|
||||
marriage_ring: currentInfo('marriage_ring', 2),
|
||||
contract: currentInfo('contract', 1),
|
||||
collection_type: currentInfo('collection_type', 1),
|
||||
collection_num_0: currentInfo('collection_num_0', 5),
|
||||
collection_num_1: currentInfo('collection_num_1', 5),
|
||||
costume_card: currentInfo('costume_card', 1),
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="w-480px pr-10px overflow-auto">
|
||||
<calc-collapse class="w-100%" title="装备/时装/徽章打造(点击部位切换)">
|
||||
<calc-tabs v-model="partTab" class="!mb-2">
|
||||
<calc-tab :value="0">装备</calc-tab>
|
||||
<calc-tab :value="2">誓约</calc-tab>
|
||||
<calc-tab :value="1">时装</calc-tab>
|
||||
<calc-tab :value="3">徽章</calc-tab>
|
||||
</calc-tabs>
|
||||
<equipment v-if="partTab === 0"></equipment>
|
||||
<clothes v-if="partTab === 1"></clothes>
|
||||
<oaths v-if="partTab === 2"></oaths>
|
||||
<emblems v-if="partTab === 3"></emblems>
|
||||
</calc-collapse>
|
||||
<calc-collapse class="w-100%" title="辟邪玉">
|
||||
<jade></jade>
|
||||
</calc-collapse>
|
||||
<calc-collapse class="w-100%" title="其它">
|
||||
<sundry></sundry>
|
||||
</calc-collapse>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import equipment from './components/Equipments.vue'
|
||||
import clothes from './components/Avatars.vue'
|
||||
import emblems from './components/Emblems.vue'
|
||||
import oaths from './components/Oaths.vue'
|
||||
import jade from './components/Jades.vue'
|
||||
import sundry from './components/Sundry.vue'
|
||||
const partTab = ref(0)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.equ-profile {
|
||||
width: 100%;
|
||||
.equ-profile-item {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
align-items: center;
|
||||
|
||||
.row-name {
|
||||
flex: 0 0 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:not(:last-child) {
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
|
||||
.i-select-label {
|
||||
margin-right: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="w-100% h-100% main flex" :class="infoStore.infos?.alter">
|
||||
<div class="header">
|
||||
<calc-tabs route :query="route.query">
|
||||
<calc-tab :value="`/character/equipment/${props.alter}`">装备设置</calc-tab>
|
||||
<calc-tab :value="`/character/skill/${props.alter}`">技能设置</calc-tab>
|
||||
<calc-tab :value="`/character/forge/${props.alter}`">打造设置</calc-tab>
|
||||
</calc-tabs>
|
||||
</div>
|
||||
<div class="content flex gap-1">
|
||||
<div
|
||||
class="flex h-auto p-1 overflow-y-hidden flex-1 box-border"
|
||||
v-if="!!infoStore.infos?.alter"
|
||||
>
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
<div class="m-1 ml-auto h-auto z-2 box-border">
|
||||
<Result></Result>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer flex">
|
||||
<calc-checkbox round v-model="configStore.config.DSB">系统奶</calc-checkbox>
|
||||
<calc-checkbox round v-model="configStore.config.BUFF">组队</calc-checkbox>
|
||||
<calc-button title="打造导入" @click="showImport" class="ml-1" icon="import"></calc-button>
|
||||
<calc-button
|
||||
title="技能导入"
|
||||
@click="showImportSkill"
|
||||
class="ml-1"
|
||||
icon="exchange"
|
||||
></calc-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<calc-dialog lazy header="打造导入" v-model:visible="importVisible">
|
||||
<ImportVue></ImportVue>
|
||||
</calc-dialog>
|
||||
|
||||
<calc-dialog lazy header="技能导入" v-model:visible="importSkillVisible">
|
||||
<ImportSkillVue></ImportSkillVue>
|
||||
</calc-dialog>
|
||||
|
||||
<calc-dialog lazy header="助手打造导入" v-model:visible="helperImport">
|
||||
<ImportDnfhelper :alter="toImport" :uid="helperStore.helperInfo?.id"></ImportDnfhelper>
|
||||
</calc-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="Index">
|
||||
import { useInfoStore } from '@/stores/info'
|
||||
import { useConfigStore } from '@/stores'
|
||||
import ImportVue from './Config/import.vue'
|
||||
import ImportSkillVue from './Config/importSkill.vue'
|
||||
import ImportDnfhelper from './Config/importDnfhelper.vue'
|
||||
import Result from './Result/Index.vue'
|
||||
import { useDnfHelperStore } from '@/stores/dnfhelper.ts'
|
||||
import { type IHelperRoleInfo } from '@/api/info/type'
|
||||
const props = defineProps<{
|
||||
alter: string
|
||||
}>()
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
const helperImport = ref(false)
|
||||
const importVisible = ref(false)
|
||||
const importSkillVisible = ref(false)
|
||||
const helperStore = useDnfHelperStore()
|
||||
const toImport = ref<IHelperRoleInfo>()
|
||||
|
||||
// const shouldWatch = ref(true)
|
||||
|
||||
const isExecuting = ref(false)
|
||||
|
||||
const stopWatch = watch(
|
||||
() => {
|
||||
return JSON.stringify(configStore.config)
|
||||
},
|
||||
async () => {
|
||||
if (isExecuting.value) return
|
||||
|
||||
isExecuting.value = true
|
||||
try {
|
||||
;(await configStore.result).execute()
|
||||
} finally {
|
||||
isExecuting.value = false
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const showImport = () => {
|
||||
importVisible.value = true
|
||||
}
|
||||
|
||||
const showImportSkill = () => {
|
||||
importSkillVisible.value = true
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const saveConfigBeforeUnload = () => {
|
||||
configStore.saveConfig()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await infoStore.createCharacter(props.alter, '0')
|
||||
window.addEventListener('beforeunload', saveConfigBeforeUnload)
|
||||
// 检测router的传参
|
||||
const { roleId } = route.query
|
||||
if (roleId) {
|
||||
toImport.value = helperStore.helperInfo?.roleList.find((a) => a.roleId === roleId)
|
||||
if (toImport.value) {
|
||||
helperImport.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopWatch()
|
||||
window.removeEventListener('beforeunload', saveConfigBeforeUnload)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-color: black;
|
||||
|
||||
color: #e9c558;
|
||||
// font-size: 12px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
z-index: 2;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
height: 40px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.main::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(50, 50, 50, 0.75);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div class="flex gap-1 h-full" v-if="configStore.result.state">
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="flex gap-5 items-center">
|
||||
<calc-button class="flex-1" @click="setStandard">设为基准</calc-button>
|
||||
<calc-button class="flex-1" @click="clearStandard">清空基准</calc-button>
|
||||
</div>
|
||||
<div class="flex bg-#101012 h-40px text-#6C5E4A font-bold z-10">
|
||||
<div
|
||||
class="flex-1 text-15px flex items-center justify-center"
|
||||
@click="chooseCategory('equ')"
|
||||
:class="{ active: type === 'equ' }"
|
||||
>
|
||||
装备
|
||||
</div>
|
||||
<div class="w-auto text-18px flex items-center justify-center !font-normal">|</div>
|
||||
<div
|
||||
class="flex-1 text-15px flex items-center justify-center"
|
||||
@click="chooseCategory('oath')"
|
||||
:class="{ active: type === 'oath' }"
|
||||
>
|
||||
誓约
|
||||
</div>
|
||||
</div>
|
||||
<Info
|
||||
:suits="configStore.result.state.suits"
|
||||
:info="configStore.result.state.info"
|
||||
:equs="configStore.config.equips"
|
||||
:avatar="configStore.config.avatarLink"
|
||||
:oaths="configStore.config.oaths"
|
||||
:display="type"
|
||||
v-model:active-skill-id="configStore.config.oathSkill"
|
||||
></Info>
|
||||
</div>
|
||||
<Skill
|
||||
v-if="!configStore.result.state.buffer"
|
||||
:data="configStore.result.state.skills"
|
||||
:standard="infoStore.standard"
|
||||
></Skill>
|
||||
<BufferSkill
|
||||
v-else
|
||||
:data="configStore.result.state.skills"
|
||||
:stadard="infoStore.standard"
|
||||
:standard="infoStore.standard"
|
||||
></BufferSkill>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useConfigStore, useInfoStore } from '@/stores'
|
||||
import Info from './components/Info.vue'
|
||||
import Skill from './components/Skill.vue'
|
||||
import BufferSkill from './components/BufferSkill.vue'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const infoStore = useInfoStore()
|
||||
const type = ref<'equ' | 'oath'>('equ')
|
||||
|
||||
const setStandard = () => {
|
||||
if (!configStore?.result?.state?.uuid) return
|
||||
infoStore.setStandard(configStore.result.state, configStore.skillCountConfig)
|
||||
}
|
||||
|
||||
const clearStandard = () => {
|
||||
infoStore.setStandard(undefined)
|
||||
}
|
||||
|
||||
const chooseCategory = (cat: 'equ' | 'oath') => {
|
||||
type.value = cat
|
||||
// console.log(type.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.active {
|
||||
color: #ccc088;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 15px;
|
||||
opacity: 0.35;
|
||||
background: #bf8d20;
|
||||
filter: blur(7px);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: url('@/assets/img/active.png') no-repeat;
|
||||
width: 148px;
|
||||
height: 16px;
|
||||
bottom: 2px;
|
||||
transform: translate(0, 50%);
|
||||
z-index: 4;
|
||||
}
|
||||
}
|
||||
|
||||
.item-head {
|
||||
background: linear-gradient(#2b2817, #171407);
|
||||
border-top: 1px solid #423d2c;
|
||||
border-bottom: 1px solid #211d15;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-hex-000000/60 p-2 border-solid border-#FFFF/15 overflow-hidden flex"
|
||||
v-if="data && data.length > 0"
|
||||
>
|
||||
<div
|
||||
class="bg-hex-000000/40 text-white border-solid border-#FFFF/15 border-t-0 overflow-hidden flex-1 flex flex-col"
|
||||
>
|
||||
<div class="flex px-1px gap-1px">
|
||||
<div
|
||||
v-for="(header, key) in headers"
|
||||
:key="key"
|
||||
class="item-head"
|
||||
:style="{ width: `${header.width}px` }"
|
||||
@click="header.sortKey && sortValue(header.sortKey)"
|
||||
>
|
||||
{{ header.label }}
|
||||
<template v-if="header.sortKey && sort === header.sortKey">
|
||||
<span>{{ order === 'desc' ? '△' : '▽' }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden flex">
|
||||
<div class="overflow-y-auto flex-1">
|
||||
<div class="flex px-1px gap-1px h-8.5" v-for="(skill, _) in data" :key="_">
|
||||
<div class="flex w-180px items-center gap-10px">
|
||||
<div class="w-28px h-28px relative">
|
||||
<img
|
||||
:src="getImageURL(skill.icon)"
|
||||
alt=""
|
||||
:class="['h-28px w-28px', skill.type === 'passive' ? 'passive' : '']"
|
||||
/>
|
||||
<div v-if="skill.lv" class="skill-lv-min" :data-text="`${skill.lv}`">
|
||||
{{ skill.lv }}
|
||||
</div>
|
||||
</div>
|
||||
<div>{{ skill.name }}</div>
|
||||
</div>
|
||||
<div class="w-50px flex items-center justify-center">
|
||||
{{ typeof skill.cd === 'number' ? `${skill.cd.toFixed(1)}s` : '-' }}
|
||||
</div>
|
||||
<component :is="formatDamage(skill)"></component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div
|
||||
v-if="!totalStandard"
|
||||
class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15"
|
||||
>
|
||||
{{ showTotal.toFixed(0) }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15"
|
||||
:style="{ color: showTotal >= 0 ? '#37fa38' : '#ff0000' }"
|
||||
>
|
||||
{{ showTotal.toFixed(2) }}%
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Skill">
|
||||
import type { IResultSkill, IResultSkillCount } from '@/api/calc/type'
|
||||
import { useConfigStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import { h } from 'vue'
|
||||
|
||||
const headers: Array<{ label: string; width: number; sortKey?: string }> = [
|
||||
{ label: '技能', width: 180, sortKey: 'name' },
|
||||
{ label: 'CD', width: 50 },
|
||||
{ label: '适用加成', width: 80, sortKey: 'MAIN' },
|
||||
{ label: '三攻', width: 80, sortKey: 'ATK' },
|
||||
{ label: '力智', width: 80, sortKey: 'STRINT' },
|
||||
{ label: '额外增伤', width: 80, sortKey: 'ratio' },
|
||||
]
|
||||
|
||||
const props = defineProps<{
|
||||
data: IResultSkill[]
|
||||
standard?: {
|
||||
skills: IResultSkill[]
|
||||
skillCount: IResultSkillCount[]
|
||||
}
|
||||
}>()
|
||||
|
||||
const sort = ref<string>('STRINT')
|
||||
const order = ref<'asc' | 'desc'>('desc')
|
||||
|
||||
const config = useConfigStore()
|
||||
|
||||
const data = computed(() => {
|
||||
return [...props.data].sort((a, b) => {
|
||||
let aValue = 0
|
||||
let bValue = 0
|
||||
if (['cd', 'name'].includes(sort.value)) {
|
||||
// @ts-ignore
|
||||
aValue = a[sort.value]
|
||||
// @ts-ignore
|
||||
bValue = b[sort.value]
|
||||
}
|
||||
const key = ['MAIN', 'ATK', 'STRINT', 'ratio'].findIndex((a) => sort.value == a)
|
||||
|
||||
aValue = a.buff?.[key]
|
||||
bValue = b.buff?.[key]
|
||||
|
||||
return order.value == 'asc' ? aValue - bValue : bValue - aValue
|
||||
})
|
||||
})
|
||||
|
||||
const sortValue = (key: string) => {
|
||||
if (sort.value == key) {
|
||||
order.value = order.value == 'asc' ? 'desc' : 'asc'
|
||||
} else {
|
||||
order.value = 'desc'
|
||||
}
|
||||
sort.value = key
|
||||
}
|
||||
|
||||
const skillCount = (skill: IResultSkill) => {
|
||||
return computed({
|
||||
get() {
|
||||
let countConfig = config.skillCountConfig.find(
|
||||
(a) => a.name == skill.name && a.mode == skill.mode,
|
||||
)
|
||||
if (!countConfig) {
|
||||
countConfig = { name: skill.name, mode: skill.mode, count: 1 }
|
||||
config.skillCountConfig.push(countConfig)
|
||||
}
|
||||
return countConfig.count
|
||||
},
|
||||
set(val) {
|
||||
const index = config.skillCountConfig.findIndex(
|
||||
(a) => a.name == skill.name && a.mode == skill.mode,
|
||||
)
|
||||
if (index > -1) {
|
||||
config.skillCountConfig[index].count = val
|
||||
} else {
|
||||
config.skillCountConfig.push({ name: skill.name, mode: skill.mode, count: val })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const total = computed(() => {
|
||||
return [0, 1, 2, 3].map((index) =>
|
||||
props.data.reduce((acc, skill) => {
|
||||
return acc + skill.buff?.[index]
|
||||
}, 0),
|
||||
)
|
||||
})
|
||||
|
||||
const totalStandard = computed(() => {
|
||||
if (!props.standard) return undefined
|
||||
return [0, 1, 2, 3].map((index) =>
|
||||
props.standard?.skills.reduce((acc, skill) => {
|
||||
return acc + skill.buff?.[index]
|
||||
}, 0),
|
||||
)
|
||||
})
|
||||
|
||||
const formatDamage = (skill: IResultSkill) => {
|
||||
const standardSkill = props.standard?.skills?.find((a) => a.name == skill.name)
|
||||
if (standardSkill) {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: 'flex items-center justify-center',
|
||||
},
|
||||
[0, 1, 2, 3].map((index) => {
|
||||
const value = skill.buff?.[index] - standardSkill.buff?.[index]
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '80px',
|
||||
color: value >= 0 ? '#37fa38' : '#ff0000',
|
||||
},
|
||||
},
|
||||
`${value >= 0 ? '+' : ''}${
|
||||
index == 3
|
||||
? `${Math.round(value).toLocaleString()}%`
|
||||
: Math.round(value).toLocaleString()
|
||||
}`,
|
||||
)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: 'flex items-center justify-center',
|
||||
},
|
||||
[0, 1, 2, 3].map((index) => {
|
||||
const value = skill.buff?.[index]
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '80px',
|
||||
},
|
||||
},
|
||||
index == 3
|
||||
? `${Math.round(value).toLocaleString()}%`
|
||||
: Math.round(value).toLocaleString(),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.item-head {
|
||||
background: linear-gradient(#2b2817, #171407);
|
||||
border-top: 1px solid #423d2c;
|
||||
border-bottom: 1px solid #211d15;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.skill-lv-min {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: -3px;
|
||||
right: -4px;
|
||||
font-size: 12px;
|
||||
transform: scale(0.7);
|
||||
color: black;
|
||||
text-shadow: none;
|
||||
font-weight: bolder;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
.skill-lv-min::before {
|
||||
content: attr(data-text);
|
||||
position: absolute;
|
||||
-webkit-text-stroke: 2.5px #37fa38;
|
||||
font-family: Arial;
|
||||
text-shadow: none;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<div class="bg-#000000bf w-266px flex-1 overflow-hidden flex flex-col">
|
||||
<div class="h-177px flex-shrink-0">
|
||||
<EquList
|
||||
:detail="props.equs"
|
||||
:with-sub-weapon="(infoStore.infos?.subweapons?.length ?? 0) > 0"
|
||||
:avatar="props.avatar"
|
||||
v-show="display == 'equ'"
|
||||
></EquList>
|
||||
<Oath :detail="props.oaths" v-show="display == 'oath'"></Oath>
|
||||
</div>
|
||||
<div class="flex flex-wrap border-y-1px my-1px border-y-solid border-#FFFF/10 gap-1% px-1%">
|
||||
<template v-for="info in props.info" :key="info.id">
|
||||
<div
|
||||
class="flex box-border mt-2px"
|
||||
:style="{ width: info.value.toString().includes('/') ? '100%' : '49%' }"
|
||||
>
|
||||
<div class="w-60px text-#836832">{{ info.name }}</div>
|
||||
<div class="ml-auto text-#3ea74e">{{ info.value }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="border-y-1px border-y-solid my-1px border-#FFFF/10 bg-black">
|
||||
<div class="flex items-center px-5px" v-if="suit">
|
||||
<img
|
||||
:src="getImageURL(`/equipment${suit.imageUrl}`)"
|
||||
alt=""
|
||||
class="w-20px h-20px object-contain"
|
||||
/>
|
||||
|
||||
<div class="flex px-5px flex-1" :class="rarityClass(suit.rarity)">
|
||||
<span class="flex-1 mr-auto block">{{ suit.name }} 套装</span>
|
||||
<span class="w-50px text-right block">
|
||||
{{ suit.rarity }} {{ lvFormat(suit.level, suit.rarity) }}
|
||||
</span>
|
||||
<span class="w-50px text-right block">{{ suit.point }}点</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="flex justify-end mr-10px text-#836832" v-if="correction > 0">
|
||||
补正:{{ correction }}点
|
||||
</div> -->
|
||||
|
||||
<div class="flex items-center px-5px" v-if="oathSuit">
|
||||
<img
|
||||
:src="getImageURL(`/equipment${oathSuit.imageUrl}`)"
|
||||
alt=""
|
||||
class="w-20px h-20px object-contain"
|
||||
/>
|
||||
<div class="flex px-5px flex-1" :class="rarityClass(oathSuit.rarity)">
|
||||
<span class="flex-1 mr-auto block">{{ oathSuit.name }} 誓约</span>
|
||||
<span class="w-50px text-right block">
|
||||
{{ oathSuit.rarity }} {{ lvFormat(oathSuit.level, oathSuit.rarity) }}
|
||||
</span>
|
||||
<span class="w-50px text-right block">{{ oathSuit.point }}点</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="oathSkills" class="flex justify-around my-5px oath-skill">
|
||||
<!-- :class="['flex-1', skill.active ? 'bg-#3ea74e text-white' : 'bg-#000000bf text-#836832']" -->
|
||||
<template v-for="skill in oathSkills" :key="skill.id">
|
||||
<div
|
||||
class="flex items-center px-5px flex-col skill-item"
|
||||
:class="{ active: skill.active, disabled: !skill.enable }"
|
||||
@click="changeSkillId(skill)"
|
||||
>
|
||||
<div
|
||||
:style="{
|
||||
backgroundImage: `url(${getImageURL(`${skill.imageUrl}`)})`,
|
||||
backgroundSize: 'contain',
|
||||
}"
|
||||
class="w-28px h-28px skill-icon"
|
||||
/>
|
||||
<!-- <span class="pt-4px">{{ skill.name }}</span> -->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="border-y-1px border-y-solid my-1px border-#FFFF/10 flex-1 flex flex-col overflow-hidden"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="flex-1 flex items-center justify-center bg-#221A08 text-#9A7E4A">
|
||||
穿戴{{ display == 'equ' ? '装备' : '誓约' }}信息
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<template v-if="display == 'equ'">
|
||||
<template v-for="part in partOrder" :key="part">
|
||||
<div class="flex p-2px partItem gap-5px">
|
||||
<img
|
||||
:src="getImageURL(`/equipment/part/${part}.png`)"
|
||||
alt=""
|
||||
class="w-20px h-20px object-contain"
|
||||
/>
|
||||
<div class="flex items-center justify-center" v-if="adaptation(part) > 0">
|
||||
<img
|
||||
:src="getImageURL(`/equipment/adaptation/${adaptation(part)}.png`)"
|
||||
alt=""
|
||||
class="w-11px h-10px object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center"
|
||||
v-if="props.equs[part]?.reinforce > 0"
|
||||
:class="props.equs[part]?.reinforceType == 1 ? 'artifact' : 'advanced'"
|
||||
>
|
||||
+{{ props.equs[part]?.reinforce }}
|
||||
</div>
|
||||
<div class="rare" v-if="props.equs[part]?.refine > 0">
|
||||
({{ props.equs[part]?.refine }})
|
||||
</div>
|
||||
<div :class="rarityClass(equ(props.equs?.[part]?.id)?.rarity ?? '')">
|
||||
{{ equ(props.equs?.[part]?.id)?.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-for="part in oathOrder" :key="part">
|
||||
<div class="flex p-2px partItem gap-5px">
|
||||
<img
|
||||
:src="getImageURL(`/equipment/part/${part == '11' ? '誓约' : '星蕴石'}.png`)"
|
||||
alt=""
|
||||
class="w-20px h-20px object-contain"
|
||||
/>
|
||||
<div class="flex items-center justify-center" v-if="oathAdaptation(part) > 0">
|
||||
<img
|
||||
:src="getImageURL(`/equipment/adaptation/${oathAdaptation(part)}.png`)"
|
||||
alt=""
|
||||
class="w-11px h-10px object-contain"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="flex items-center"
|
||||
v-if="props.equs[part]?.reinforce > 0"
|
||||
:class="props.equs[part]?.reinforceType == 1 ? 'artifact' : 'advanced'"
|
||||
>
|
||||
+{{ props.equs[part]?.reinforce }}
|
||||
</div> -->
|
||||
<!-- <div class="rare" v-if="props.equs[part]?.refine > 0">
|
||||
({{ props.equs[part]?.refine }})
|
||||
</div> -->
|
||||
{{ equ(props.equs?.[part]?.id) }}
|
||||
<div :class="rarityClass(equ(props.oaths?.[part]?.id)?.rarity ?? '')">
|
||||
{{ equ(props.oaths?.[part]?.id)?.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="h-40px w-full"></div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Info">
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import { rarityClass } from '@/utils'
|
||||
import type { IResultUserInfo, IResultSuit } from '@/api/calc/type'
|
||||
import EquList from '@/components/dnf/Equipment/List/index.vue'
|
||||
import Oath from '@/components/dnf/Equipment/Oath/index.vue'
|
||||
import type { IConfigEquip, IConfigOath } from '@/stores/config'
|
||||
import { useInfoStore } from '@/stores'
|
||||
|
||||
const props = defineProps<{
|
||||
info: IResultUserInfo[]
|
||||
suits: {
|
||||
suit?: IResultSuit
|
||||
oath?: IResultSuit
|
||||
correction?: number
|
||||
}
|
||||
equs: Record<string, IConfigEquip>
|
||||
oaths: Record<string, IConfigOath>
|
||||
avatar: string
|
||||
display: 'oath' | 'equ'
|
||||
}>()
|
||||
|
||||
const partOrder = [
|
||||
'武器',
|
||||
'上衣',
|
||||
'下装',
|
||||
'头肩',
|
||||
'腰带',
|
||||
'鞋',
|
||||
'项链',
|
||||
'手镯',
|
||||
'戒指',
|
||||
'辅助装备',
|
||||
'魔法石',
|
||||
'耳环',
|
||||
]
|
||||
|
||||
const oathOrder = Array.from({ length: 12 }, (_, i) => (11 - i).toString())
|
||||
const infoStore = useInfoStore()
|
||||
|
||||
const activeSkillId = defineModel<string>('activeSkillId', { default: '0' })
|
||||
|
||||
const suit = computed(() => {
|
||||
return props.suits.suit
|
||||
})
|
||||
|
||||
const oathSuit = computed(() => {
|
||||
return props.suits.oath
|
||||
})
|
||||
|
||||
// const correction = computed(() => {
|
||||
// return props.suits.correction ?? 0
|
||||
// })
|
||||
|
||||
const changeSkillId = (skill: { skillId: string; enable: boolean }) => {
|
||||
// console.log(skill)
|
||||
if (!skill.enable) return
|
||||
if (activeSkillId.value !== skill.skillId) {
|
||||
activeSkillId.value = skill.skillId
|
||||
}
|
||||
}
|
||||
|
||||
const oathSkills = computed(() => {
|
||||
const skillOrder = ['2', '1', '3']
|
||||
|
||||
const res = [...(props.suits.oath?.skills ?? [])].sort((left, right) => {
|
||||
const leftIndex = skillOrder.indexOf(left.skillId)
|
||||
const rightIndex = skillOrder.indexOf(right.skillId)
|
||||
const normalizedLeftIndex = leftIndex === -1 ? skillOrder.length : leftIndex
|
||||
const normalizedRightIndex = rightIndex === -1 ? skillOrder.length : rightIndex
|
||||
|
||||
return normalizedLeftIndex - normalizedRightIndex
|
||||
})
|
||||
|
||||
return res?.map((a) => ({
|
||||
...a,
|
||||
active:
|
||||
(a.enable && a.id == props.suits.oath?.id) || (a.enable && a.skillId == activeSkillId.value),
|
||||
}))
|
||||
})
|
||||
|
||||
const equ = (id: string) => {
|
||||
if (props.display == 'oath') {
|
||||
// console.log(props.oaths, id)
|
||||
return infoStore.oaths.find((e) => e.id == id)
|
||||
}
|
||||
return infoStore.equips.find((e) => e.id == id)
|
||||
}
|
||||
|
||||
const adaptation = (part: string) => {
|
||||
return Math.min(
|
||||
equ(props.equs?.[part]?.id)?.max_adaptation ?? 0,
|
||||
props.equs[part]?.adaptation ?? 0,
|
||||
)
|
||||
}
|
||||
|
||||
const oathAdaptation = (part: string) => {
|
||||
return Math.min(
|
||||
equ(props.oaths?.[part]?.id)?.max_adaptation ?? 0,
|
||||
props.oaths[part]?.adaptation ?? 0,
|
||||
)
|
||||
}
|
||||
|
||||
const lvFormat = (lv: number, rarity: string) => {
|
||||
if (rarity == '太初') return ''
|
||||
switch (lv) {
|
||||
case 1:
|
||||
return 'I'
|
||||
case 2:
|
||||
return 'II'
|
||||
case 3:
|
||||
return 'III'
|
||||
case 4:
|
||||
return 'IV'
|
||||
case 5:
|
||||
return 'V'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.partItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.partItem:nth-child(2n + 1) {
|
||||
background-color: #0b090b;
|
||||
}
|
||||
|
||||
.partItem:nth-child(2n) {
|
||||
background-color: #000100;
|
||||
}
|
||||
|
||||
.oath-skill {
|
||||
.skill-item {
|
||||
&.active {
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.skill-icon {
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&.disabled .skill-icon {
|
||||
position: relative;
|
||||
filter: grayscale(100%);
|
||||
|
||||
&::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('@/assets/img/oath/disabled.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-hex-000000/60 p-2 border-solid border-#FFFF/15 overflow-hidden flex"
|
||||
v-if="data && data.length > 0"
|
||||
>
|
||||
<div
|
||||
class="bg-hex-000000/40 text-white border-solid border-#FFFF/15 border-t-0 overflow-hidden flex-1 flex flex-col"
|
||||
>
|
||||
<div class="flex px-1px gap-1px">
|
||||
<div
|
||||
v-for="(header, key) in headers"
|
||||
:key="key"
|
||||
class="item-head"
|
||||
:style="{ width: `${header.width}px` }"
|
||||
@click="header.sortKey && sortValue(header.sortKey)"
|
||||
>
|
||||
{{ header.label }}
|
||||
<template v-if="header.sortKey && sort === header.sortKey">
|
||||
<span>{{ order === 'desc' ? '△' : '▽' }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="item-head w-60px">占比</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden flex">
|
||||
<div class="overflow-y-auto flex-1">
|
||||
<div class="flex px-1px gap-1px h-8.5" v-for="(skill, _) in data" :key="_">
|
||||
<div class="flex w-180px items-center gap-10px">
|
||||
<div class="w-28px h-28px relative">
|
||||
<img :src="getImageURL(skill.icon)" alt="" class="w-28px h-28px" />
|
||||
<div v-if="skill.lv" class="skill-lv-min" :data-text="`Lv${skill.lv}`">
|
||||
Lv{{ skill.lv }}
|
||||
</div>
|
||||
</div>
|
||||
<div>{{ skill.name }} {{ !skill.mode ? '' : `[${skill.mode}]` }}</div>
|
||||
</div>
|
||||
<div class="w-50px flex items-center justify-center">{{ skill.cd.toFixed(1) }}s</div>
|
||||
<div class="w-50px flex items-center justify-center">
|
||||
<calc-input
|
||||
type="number"
|
||||
class="!w-40px !min-w-40px !pl-0 flex items-center justify-center"
|
||||
v-model="skillCount(skill).value"
|
||||
></calc-input>
|
||||
</div>
|
||||
<component :is="formatDamage(skill)"></component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!totalStandard"
|
||||
class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15"
|
||||
>
|
||||
{{ showTotal.toFixed(0) }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="h-40px flex items-center justify-center text-18px border-t-1px border-t-solid border-t-#FFFF/15"
|
||||
:style="{ color: showTotal >= 0 ? '#37fa38' : '#ff0000' }"
|
||||
>
|
||||
{{ showTotal.toFixed(2) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Skill">
|
||||
import type { IResultSkill, IResultSkillCount } from '@/api/calc/type'
|
||||
import { useConfigStore } from '@/stores'
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import { h } from 'vue'
|
||||
|
||||
const headers: Array<{ label: string; width: number; sortKey?: string }> = [
|
||||
{ label: '技能', width: 180, sortKey: 'learnLv' },
|
||||
{ label: 'CD', width: 50, sortKey: 'cd' },
|
||||
{ label: '次数', width: 50 },
|
||||
{ label: '秒伤', width: 100, sortKey: 'dps' },
|
||||
{ label: '总伤害', width: 110, sortKey: 'total' },
|
||||
]
|
||||
|
||||
const props = defineProps<{
|
||||
data: IResultSkill[]
|
||||
standard?: {
|
||||
skills: IResultSkill[]
|
||||
skillCount: IResultSkillCount[]
|
||||
}
|
||||
}>()
|
||||
|
||||
const sort = ref<string>('learnLv')
|
||||
const order = ref<'asc' | 'desc'>('desc')
|
||||
|
||||
const config = useConfigStore()
|
||||
|
||||
const data = computed(() => {
|
||||
return props.data.sort((a, b) => {
|
||||
let aValue = 0
|
||||
let bValue = 0
|
||||
if (['damage', 'cd', 'learnLv'].includes(sort.value)) {
|
||||
// @ts-ignore
|
||||
aValue = a[sort.value]
|
||||
// @ts-ignore
|
||||
bValue = b[sort.value]
|
||||
}
|
||||
if (sort.value == 'total') {
|
||||
aValue = a.damage * skillCount(a).value
|
||||
bValue = b.damage * skillCount(b).value
|
||||
}
|
||||
if (sort.value == 'dps') {
|
||||
aValue = a.damage / a.cd
|
||||
bValue = b.damage / b.cd
|
||||
}
|
||||
if (sort.value == 'name') {
|
||||
// @ts-ignore
|
||||
return order.value == 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue)
|
||||
}
|
||||
return order.value == 'asc' ? aValue - bValue : bValue - aValue
|
||||
})
|
||||
})
|
||||
|
||||
const sortValue = (key: string) => {
|
||||
if (sort.value == key) {
|
||||
order.value = order.value == 'asc' ? 'desc' : 'asc'
|
||||
} else {
|
||||
order.value = 'desc'
|
||||
}
|
||||
sort.value = key
|
||||
}
|
||||
|
||||
const skillCount = (skill: IResultSkill) => {
|
||||
return computed({
|
||||
get() {
|
||||
let countConfig = config.skillCountConfig.find(
|
||||
(a) => a.name == skill.name && a.mode == skill.mode,
|
||||
)
|
||||
if (!countConfig) {
|
||||
countConfig = { name: skill.name, mode: skill.mode, count: 1 }
|
||||
config.skillCountConfig.push(countConfig)
|
||||
}
|
||||
return countConfig.count
|
||||
},
|
||||
set(val) {
|
||||
const index = config.skillCountConfig.findIndex(
|
||||
(a) => a.name == skill.name && a.mode == skill.mode,
|
||||
)
|
||||
if (index > -1) {
|
||||
config.skillCountConfig[index].count = val
|
||||
} else {
|
||||
config.skillCountConfig.push({ name: skill.name, mode: skill.mode, count: val })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const totalDamage = computed(() => {
|
||||
return props.data.reduce((acc, skill) => {
|
||||
return acc + skill.damage * skillCount(skill).value
|
||||
}, 0)
|
||||
})
|
||||
|
||||
const totalStandard = computed(() => {
|
||||
if (!props.standard) return undefined
|
||||
return props.standard.skills.reduce((acc, skill) => {
|
||||
const standardSkillCount = props.standard?.skillCount.find(
|
||||
(a) => a.name == skill.name && a.mode == skill.mode,
|
||||
)?.count
|
||||
return acc + skill.damage * (standardSkillCount || 0)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
const showTotal = computed(() => {
|
||||
if (!totalStandard.value || totalStandard.value == 0) return totalDamage.value
|
||||
return ((totalDamage.value - totalStandard.value) / totalStandard.value) * 100
|
||||
})
|
||||
|
||||
const formatDamage = (skill: IResultSkill) => {
|
||||
const standardSkill = props.standard?.skills?.find(
|
||||
(a) => a.name == skill.name && a.mode == skill.mode,
|
||||
)
|
||||
const standardSkillCount = props.standard?.skillCount.find(
|
||||
(a) => a.name == skill.name && a.mode == skill.mode,
|
||||
)?.count
|
||||
const rate = h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '60px',
|
||||
},
|
||||
},
|
||||
(((skill.damage * skillCount(skill).value) / totalDamage.value) * 100).toFixed(2) + '%',
|
||||
)
|
||||
if (standardSkill && standardSkill.damage > 0 && standardSkill.cd > 0 && !!standardSkillCount) {
|
||||
const currentSkillCount = skillCount(skill).value
|
||||
const damageValue =
|
||||
(skill.damage * currentSkillCount - standardSkill.damage * standardSkillCount) /
|
||||
(standardSkill.damage * standardSkillCount)
|
||||
const damage = h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '110px',
|
||||
color: damageValue >= 0 ? '#37fa38' : '#ff0000',
|
||||
},
|
||||
},
|
||||
(damageValue * 100).toFixed(2) + '%',
|
||||
)
|
||||
const dpsValue =
|
||||
(skill.damage / skill.cd - standardSkill.damage / standardSkill.cd) /
|
||||
(standardSkill.damage / standardSkill.cd)
|
||||
const dps = h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '110px',
|
||||
color: dpsValue >= 0 ? '#37fa38' : '#ff0000',
|
||||
},
|
||||
},
|
||||
(dpsValue * 100).toFixed(2) + '%',
|
||||
)
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: 'flex items-center justify-center',
|
||||
},
|
||||
[dps, damage, rate],
|
||||
)
|
||||
} else {
|
||||
const damage = h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '110px',
|
||||
},
|
||||
},
|
||||
Math.round(skill.damage * skillCount(skill).value).toLocaleString(),
|
||||
)
|
||||
const dps = h(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '110px',
|
||||
},
|
||||
},
|
||||
Math.round(skill.damage / skill.cd).toLocaleString(),
|
||||
)
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: 'flex items-center justify-center',
|
||||
},
|
||||
[dps, damage, rate],
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.item-head {
|
||||
background: linear-gradient(#2b2817, #171407);
|
||||
border-top: 1px solid #423d2c;
|
||||
border-bottom: 1px solid #211d15;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.skill-lv-min {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: -3px;
|
||||
right: -4px;
|
||||
font-size: 12px;
|
||||
transform: scale(0.7);
|
||||
color: black;
|
||||
text-shadow: none;
|
||||
font-weight: bolder;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
.skill-lv-min::before {
|
||||
content: attr(data-text);
|
||||
position: absolute;
|
||||
-webkit-text-stroke: 2.5px #37fa38;
|
||||
font-family: Arial;
|
||||
text-shadow: none;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
import { asyncComputed } from '@vueuse/core'
|
||||
import api from '@/api'
|
||||
import './style.scss'
|
||||
import CalcTabs from '@/components/calc/tabs/index.vue'
|
||||
import CalcTab from '@/components/calc/tab/index.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SkillDetail',
|
||||
props: {
|
||||
skillId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const currentSkill = computed(() => props.skillId)
|
||||
const currentLevel = computed(() => props.level)
|
||||
|
||||
const skillInfo = asyncComputed(async () => {
|
||||
if (!currentSkill.value && !currentLevel.value) return undefined
|
||||
const res = await api.skillDetail(currentSkill.value, currentLevel.value)
|
||||
return res
|
||||
})
|
||||
|
||||
const curTab = ref(0)
|
||||
|
||||
const basic = computed(() =>
|
||||
!skillInfo.value
|
||||
? []
|
||||
: [
|
||||
{ label: '技能等级', value: skillInfo.value?.attribute?.level ?? props.level },
|
||||
{ label: '学习等级', value: skillInfo.value?.requiredLevel },
|
||||
{ label: '等级上限', value: skillInfo.value?.maxLevel },
|
||||
{
|
||||
label: '冷却时间',
|
||||
value: skillInfo.value?.attribute?.coolTime
|
||||
? `${skillInfo.value?.attribute?.coolTime.toFixed(1)}秒`
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
label: '施放时间',
|
||||
value: !skillInfo.value?.attribute?.castingTime
|
||||
? '瞬发'
|
||||
: `${skillInfo.value?.attribute?.castingTime.toFixed(1)}秒`,
|
||||
},
|
||||
{ label: '魔法值', value: skillInfo.value?.attribute?.consumeMp },
|
||||
{
|
||||
label: '消耗无色小晶块',
|
||||
value: skillInfo.value?.consumeItem?.value
|
||||
? `${skillInfo.value?.consumeItem?.value}个`
|
||||
: undefined,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
const desc = computed(() => skillInfo.value?.descDetail.split('\n'))
|
||||
|
||||
const detail = computed(() => {
|
||||
const items =
|
||||
skillInfo.value?.attribute?.optionDesc?.split('\n').filter((item) => !!item) ?? []
|
||||
const details = items?.map((a) => {
|
||||
const beforeReplace = a.match(/^(.+?)\s*(\{value\d+\}.*)$/)
|
||||
if (beforeReplace) {
|
||||
a = `${beforeReplace[1]}<span class="skill_info_value">${beforeReplace[2]}</span>`
|
||||
}
|
||||
const matches = a.match(/{([^}]+)}/g)
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
const key = match.replace(/[{}]/g, '')
|
||||
// Only access if key matches the expected pattern
|
||||
if (/^value.+/.test(key)) {
|
||||
const value = skillInfo.value?.attribute?.optionValue[key as `value${string}`] ?? '-'
|
||||
if (value) {
|
||||
a = a.replace(match, `<span style="padding:0px 2px;color:#4AA256">${value}</span>`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return a
|
||||
})
|
||||
return details || []
|
||||
})
|
||||
|
||||
const renderSkillDetail = () => (
|
||||
<>
|
||||
<div class="skill_title">基本信息</div>
|
||||
<div class="flex flex-col gap-2px">
|
||||
{basic.value.map(
|
||||
(item) =>
|
||||
!!item.value && (
|
||||
<div class="flex justify-between item">
|
||||
<span class="label">{item.label}</span>
|
||||
<span class="value">{item.value}</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<div class="skill_title">技能描述</div>
|
||||
<div class="h-auto max-h-200px">
|
||||
<div class="h-full overflow-y-auto bg-#161816 p-2px">
|
||||
{desc.value?.map((item) => (
|
||||
// item.startsWith('[') || item.startsWith('-') || item.startsWith('<') ? (
|
||||
// <div class="min-h-10px">{item}</div>
|
||||
// ) :
|
||||
<div class="min-h-10px" style="white-space:pre-wrap;">
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{detail.value.length > 0 && (
|
||||
<>
|
||||
<div class="skill_title">技能属性</div>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class=" flex flex-col gap-2px">
|
||||
{detail.value.map((item) => (
|
||||
<div class="flex justify-between item items-center" v-html={item}></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
const renderSkillVp = () => (
|
||||
<>
|
||||
<div class="h-auto">
|
||||
<div class="h-full flex flex-col gap-5px">
|
||||
{skillInfo.value?.evolution?.map((item) => (
|
||||
<>
|
||||
<div class="flex flex-col gap-2px">
|
||||
<div class="skill_vp_title">{item.name}</div>
|
||||
<div class="skill_title">技能描述</div>
|
||||
<div class="h-auto bg-#161816 max-h-300px overflow-y-auto">
|
||||
<div class="h-auto p-2px" style="white-space: pre-wrap;">
|
||||
{item.descDetail}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
watchEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
;(skillInfo.value?.evolution?.length ?? 0) == 0 && (curTab.value = 0)
|
||||
})
|
||||
})
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div class="flex flex-col gap-2px mx-2px text-#907B54 h-full">
|
||||
<div class="skill_name h-20px">{skillInfo.value?.name}</div>
|
||||
<div class="relative">
|
||||
<CalcTabs v-model={curTab.value}>
|
||||
<CalcTab class="flex-1 flex justify-center" value={0}>
|
||||
技能信息
|
||||
</CalcTab>
|
||||
<CalcTab
|
||||
class="flex-1 flex justify-center"
|
||||
value={1}
|
||||
disabled={!skillInfo.value?.evolution?.length}
|
||||
>
|
||||
技能进化
|
||||
</CalcTab>
|
||||
</CalcTabs>
|
||||
</div>
|
||||
{curTab.value === 0 && renderSkillDetail()}
|
||||
{curTab.value === 1 && renderSkillVp()}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,97 @@
|
||||
.skill_name {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-image-source: linear-gradient(90deg, #7d5f07 0%, #2e302e 100%);
|
||||
border-image-slice: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
height: 20px;
|
||||
z-index: 5;
|
||||
transform-style: preserve-3d;
|
||||
color: #c1a238;
|
||||
&::before {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 50%;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg, #281e06 0%, #121212 100%);
|
||||
transform: translateZ(-1px);
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 50%;
|
||||
top: 0;
|
||||
background: linear-gradient(90deg, #563e02 0%, #121212 100%);
|
||||
transform: translateZ(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.skill_vp_title {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-image-source: linear-gradient(90deg, #691b86 0%, #2e302e 100%);
|
||||
border-image-slice: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
height: 20px;
|
||||
z-index: 5;
|
||||
transform-style: preserve-3d;
|
||||
color: #9443be;
|
||||
&::before {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 50%;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg, #3a0f47 0%, #121212 100%);
|
||||
transform: translateZ(-1px);
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 50%;
|
||||
top: 0;
|
||||
background: linear-gradient(90deg, #1e0b24 0%, #171717 100%);
|
||||
transform: translateZ(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.skill_title {
|
||||
border: 1px solid #3e4040;
|
||||
color: #c1a238;
|
||||
background: linear-gradient(180deg, #2b292b 0%, #0b0b0b 100%);
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
.item {
|
||||
background-color: #171616;
|
||||
padding: 0px 5px;
|
||||
.label {
|
||||
color: #907b54;
|
||||
}
|
||||
.value {
|
||||
color: #4aa256;
|
||||
}
|
||||
}
|
||||
|
||||
.skill_info_value {
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
margin-top: auto;
|
||||
color: #4aa256;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_43_35)">
|
||||
<rect x="1" y="1" width="16" height="16" fill="url(#paint0_linear_43_35)"/>
|
||||
</g>
|
||||
<rect x="0.5" y="0.5" width="17" height="17" stroke="#5D4F30"/>
|
||||
<rect x="8" y="4" width="2" height="10" fill="#B7975F"/>
|
||||
<rect x="14" y="8" width="2" height="10" transform="rotate(90 14 8)" fill="#B7975F"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_43_35" x="0" y="0" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="1" operator="erode" in="SourceAlpha" result="effect1_innerShadow_43_35"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_43_35"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_43_35" x1="9" y1="1" x2="9" y2="17" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#451A1F"/>
|
||||
<stop offset="0.407132" stop-color="#391619"/>
|
||||
<stop offset="1" stop-color="#271111"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,26 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_43_36)">
|
||||
<rect x="1" y="1" width="16" height="16" fill="url(#paint0_linear_43_36)"/>
|
||||
</g>
|
||||
<rect x="0.5" y="0.5" width="17" height="17" stroke="#5D4F30"/>
|
||||
<path d="M9 9L3.75 14.1962V3.80385L9 9Z" fill="#B7975F"/>
|
||||
<path d="M15 9L9.75 14.1962V3.80385L15 9Z" fill="#B7975F"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_43_36" x="0" y="0" width="18" height="19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="1" operator="erode" in="SourceAlpha" result="effect1_innerShadow_43_36"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_43_36"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_43_36" x1="9" y1="1" x2="9" y2="17" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#451A1F"/>
|
||||
<stop offset="0.407132" stop-color="#391619"/>
|
||||
<stop offset="1" stop-color="#271111"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,25 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_43_33)">
|
||||
<rect x="1" y="1" width="16" height="16" fill="url(#paint0_linear_43_33)"/>
|
||||
</g>
|
||||
<rect x="0.5" y="0.5" width="17" height="17" stroke="#5D4F30"/>
|
||||
<path d="M9 9L14.25 3.80385V14.1962L9 9Z" fill="#B7975F"/>
|
||||
<path d="M3 9L8.25 3.80385V14.1962L3 9Z" fill="#B7975F"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_43_33" x="0" y="0" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="1" operator="erode" in="SourceAlpha" result="effect1_innerShadow_43_33"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_43_33"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_43_33" x1="9" y1="1" x2="9" y2="17" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#103562"/>
|
||||
<stop offset="1" stop-color="#092343"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 545 B |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,24 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_43_34)">
|
||||
<rect x="1" y="1" width="16" height="16" fill="url(#paint0_linear_43_34)"/>
|
||||
</g>
|
||||
<rect x="0.5" y="0.5" width="17" height="17" stroke="#5D4F30"/>
|
||||
<rect x="14" y="8" width="2" height="10" transform="rotate(90 14 8)" fill="#B7975F"/>
|
||||
<defs>
|
||||
<filter id="filter0_i_43_34" x="0" y="0" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="1" operator="erode" in="SourceAlpha" result="effect1_innerShadow_43_34"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_43_34"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_43_34" x1="9" y1="1" x2="9" y2="17" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#103562"/>
|
||||
<stop offset="1" stop-color="#092343"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,280 @@
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import type { ISkill } from '@/api/info/type'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import CalcButton from '@/components/calc/button/index.vue'
|
||||
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="relative w-35px h-55px">
|
||||
<div class="flex w-full items-center justify-center gap-2px absolute top--18px left-1px">
|
||||
{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={[
|
||||
'w-36px absolute h-48px flex flex-col box-border items-center py-3px z-2',
|
||||
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={['h-28px w-28px', skill.type === 'passive' ? 'passive' : '']}
|
||||
src={getImageURL(skill.icon)}
|
||||
alt={skill.name}
|
||||
></img>
|
||||
<div class="w-100% flex flex-1 justify-center" style={{ color }}>
|
||||
{info?.lv ?? 0}
|
||||
</div>
|
||||
</div>
|
||||
{skill.learnLv == 100 && skill.bind && (
|
||||
<>
|
||||
<div
|
||||
class={`pos-absolute w-196px h-54px box-border translate-x--50% left-18px top--3px flex justify-between items-center py-2px px-3px awake_${bindAwake.value}`}
|
||||
>
|
||||
<div
|
||||
class="w-36px flex flex-col box-border items-center py-2px z-2"
|
||||
onClick={() => chooseBindAwake(50)}
|
||||
>
|
||||
<img
|
||||
class="h-28px w-28px mt-2px"
|
||||
src={getImageURL(awakeSkill_1.value?.icon ?? '')}
|
||||
></img>
|
||||
<div class="w-100% mt-1px flex items-center justify-center text-#ffc701">
|
||||
1次
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w-36px flex flex-col box-border items-center py-2px z-2"
|
||||
onClick={() => chooseBindAwake(85)}
|
||||
>
|
||||
<img
|
||||
class="h-28px w-28px mt-1px"
|
||||
src={getImageURL(awakeSkill_2.value?.icon ?? '')}
|
||||
></img>
|
||||
<div class="w-100% mt-1px flex items-center justify-center text-#ffc701">
|
||||
2次
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const renderSkillAction = (skill: ISkill) => (
|
||||
<>
|
||||
<>
|
||||
<div class="absolute skill-active w-88px h-78px translate-x--50% left-16px top--1px z-5">
|
||||
<div class="flex justify-between items-center px-5px relative top-54px">
|
||||
<CalcButton
|
||||
icon="min"
|
||||
disabled={lvInfo.value[skill.id.toString()]?.lv == 0}
|
||||
onClick={(e: Event) => {
|
||||
e.stopPropagation()
|
||||
actionSkillLv(skill, 'subMax')
|
||||
}}
|
||||
></CalcButton>
|
||||
<CalcButton
|
||||
icon="reduce"
|
||||
disabled={lvInfo.value[skill.id.toString()]?.lv == 0}
|
||||
onClick={(e: Event) => {
|
||||
e.stopPropagation()
|
||||
actionSkillLv(skill, 'sub')
|
||||
}}
|
||||
></CalcButton>
|
||||
<CalcButton
|
||||
icon="increase"
|
||||
onClick={(e: Event) => {
|
||||
e.stopPropagation()
|
||||
actionSkillLv(skill, 'add')
|
||||
}}
|
||||
></CalcButton>
|
||||
<CalcButton
|
||||
icon="max"
|
||||
disabled={lvInfo.value[skill.id.toString()]?.lv >= skill.maxLearnLv}
|
||||
onClick={(e: Event) => {
|
||||
e.stopPropagation()
|
||||
actionSkillLv(skill, 'addMax')
|
||||
}}
|
||||
></CalcButton>
|
||||
{/* <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 onClick={() => (activeSkill.value = -1)}>
|
||||
{((getSkill(0, 15, -1) ?? []) as ISkill[]).length > 0 && (
|
||||
<>
|
||||
<div class="skill-tree-line flex items-center gap-8px px-5px">
|
||||
<div class="w-34px h-34px flex items-center justify-center text-white">
|
||||
其<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 flex items-center gap-8px px-5px" key={index}>
|
||||
{lv == 1 || lv % 10 == 0 ? (
|
||||
<div class="w-34px h-34px flex items-center justify-center text-white">{lv}</div>
|
||||
) : (
|
||||
<div class="w-34px h-34px"></div>
|
||||
)}
|
||||
{columns.map((column) => {
|
||||
const skill = getSkill(lv, lvList[index + 1] ?? 150, column) as ISkill
|
||||
return skill ? renderSkill(skill, index) : <div class="w-34px h-34px"></div>
|
||||
})}
|
||||
<div class="w-5px h-34px" onClick={() => (activeSkill.value = -1)}></div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
.skill-tree-line {
|
||||
height: 80px;
|
||||
width: auto;
|
||||
&:nth-child(n + 1) {
|
||||
background-color: #000000;
|
||||
}
|
||||
&:nth-child(2n + 1) {
|
||||
background-color: #202020;
|
||||
}
|
||||
}
|
||||
|
||||
.passive {
|
||||
filter: grayscale(100%) invert(3%) sepia(14%) saturate(874%) hue-rotate(65deg) brightness(105%)
|
||||
contrast(102%);
|
||||
}
|
||||
|
||||
.skill {
|
||||
background-image: url('./img/skill.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.skill-active {
|
||||
background-image: url('./img/skillActive.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.awake_50 {
|
||||
background-image: url('./img/awake_50.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.awake_85 {
|
||||
background-image: url('./img/awake_85.png');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.vp_1 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/vp_1.png);
|
||||
}
|
||||
|
||||
.vp_2 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/vp_2.png);
|
||||
}
|
||||
|
||||
.up_1 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/up_1.png);
|
||||
}
|
||||
.up_2 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/up_2.png);
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 724 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,307 @@
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import type { ISkill } from '@/api/info/type'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import CalcButton from '@/components/calc/button/index.vue'
|
||||
import CaclIconSelect from '@/components/calc/iconselect/index.vue'
|
||||
import CalcOption from '@/components/calc/option/index.vue'
|
||||
import { renderList } from 'vue'
|
||||
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; up: number }>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
buffer: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const lvInfo = useVModel(props, 'lvInfo')
|
||||
const skills = (index: number = 0) => {
|
||||
index -= 1
|
||||
const upList = Object.keys(lvInfo.value).filter(
|
||||
(a) => !!lvInfo.value[a].up && lvInfo.value[a].up > 0,
|
||||
)
|
||||
const currentUPSkill = index >= 0 ? upList[index] : undefined
|
||||
return computed(() =>
|
||||
props.skills
|
||||
.filter(
|
||||
(a) =>
|
||||
a.id.toString() === currentUPSkill ||
|
||||
(a.hasUP &&
|
||||
(!lvInfo.value[a.id]?.up || lvInfo.value[a.id]?.up == 0) &&
|
||||
lvInfo.value[a.id].lv > 0),
|
||||
)
|
||||
.sort((a, b) => a.learnLv - b.learnLv),
|
||||
)
|
||||
}
|
||||
|
||||
const itemChecked = (index: number, up: number) => {
|
||||
index -= 1
|
||||
return computed<string | undefined>({
|
||||
get() {
|
||||
const upList = Object.keys(lvInfo.value).filter(
|
||||
(a) => !!lvInfo.value[a].up && lvInfo.value[a].up > 0,
|
||||
)
|
||||
const currentUPSkill = upList[index]
|
||||
const currentUP = lvInfo.value[currentUPSkill]?.up
|
||||
return currentUP == up ? currentUPSkill : undefined
|
||||
},
|
||||
set(value: string | undefined) {
|
||||
if (!value) return
|
||||
const upList = Object.keys(lvInfo.value).filter(
|
||||
(a) => !!lvInfo.value[a].up && lvInfo.value[a].up > 0,
|
||||
)
|
||||
const currentUPSkill = upList[index]
|
||||
if (currentUPSkill) {
|
||||
lvInfo.value[currentUPSkill].up = 0
|
||||
}
|
||||
lvInfo.value[value].up = up
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const exchange = (index: number) => {
|
||||
const skill = itemChecked(index, 1).value || itemChecked(index, 2).value
|
||||
if (!skill) return
|
||||
lvInfo.value[skill].up = lvInfo.value[skill].up == 1 ? 2 : 1
|
||||
}
|
||||
|
||||
return () => (
|
||||
<>
|
||||
{lvInfo.value && Object.keys(lvInfo.value).length > 0 ? (
|
||||
<div class="flex bg-black">
|
||||
<div class="skill-up flex justify-between">
|
||||
<div class="left">
|
||||
<div class="w-full">
|
||||
<div class="flex justify-between">
|
||||
<div class="up_head_1">
|
||||
<div class="icon"></div>
|
||||
<div class="text-14px font-bold text-center">技能攻击力增加</div>
|
||||
</div>
|
||||
<div class="up_head_2">
|
||||
<div class="icon"></div>
|
||||
<div class="text-14px font-bold text-center">
|
||||
技能冷却时间减少
|
||||
<br />
|
||||
技能攻击力增加
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-5px px-20px py-10px">
|
||||
{renderList(3, (index) => (
|
||||
<>
|
||||
<div class="flex items-center w-full justify-between">
|
||||
<div class={`up_${!itemChecked(index, 1).value ? index : 'active'}`}>
|
||||
<CaclIconSelect
|
||||
v-model:modelValue={itemChecked(index, 1).value}
|
||||
class="!bg-white/0 !border-white/0"
|
||||
columnNum={4}
|
||||
>
|
||||
{
|
||||
<CalcOption value={'-1'}>
|
||||
<div class="w-28px h-28px"></div>
|
||||
</CalcOption>
|
||||
}
|
||||
{renderList(skills(index).value, (item) => (
|
||||
<>
|
||||
<CalcOption value={item.id.toString()}>
|
||||
<img
|
||||
class="mt-2px"
|
||||
src={getImageURL(item.icon)}
|
||||
alt={item.name}
|
||||
/>
|
||||
</CalcOption>
|
||||
</>
|
||||
))}
|
||||
</CaclIconSelect>
|
||||
</div>
|
||||
<CalcButton icon="exchange" onClick={() => exchange(index)}></CalcButton>
|
||||
<div class={`up_${!itemChecked(index, 2).value ? index : 'active'}`}>
|
||||
<CaclIconSelect
|
||||
v-model:modelValue={itemChecked(index, 2).value}
|
||||
class=" !bg-white/0 !border-white/0"
|
||||
columnNum={4}
|
||||
>
|
||||
{renderList(skills(index).value, (item) => (
|
||||
<>
|
||||
<CalcOption value={item.id.toString()}>
|
||||
<img
|
||||
class="mt-2px"
|
||||
src={getImageURL(item.icon)}
|
||||
alt={item.name}
|
||||
/>
|
||||
</CalcOption>
|
||||
</>
|
||||
))}
|
||||
</CaclIconSelect>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="title text-16px text-center leading-48px">目标技能</div>
|
||||
<div class="flex flex-wrap gap-8px content-start h-110px overflow-y-auto mb-8px w-245px m-auto">
|
||||
{renderList(skills().value, (item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
class="w-[28px] h-[28px] rounded-[1px] border-[1px] border-solid border-[#6d4e2b] bg-[linear-gradient(180deg,_#191919_0%,_#000000_100%)] [box-shadow:0px_4px_4px_#00000040_inset] rounded-4px"
|
||||
>
|
||||
<img src={getImageURL(item.icon)} alt={item.name} />
|
||||
</div>
|
||||
))}
|
||||
{renderList(36 - skills().value.length, (item) => (
|
||||
<div
|
||||
key={item}
|
||||
class="w-[28px] h-[28px] rounded-[1px] border-[1px] border-solid border-[#6d4e2b] bg-[linear-gradient(180deg,_#191919_0%,_#000000_100%)] [box-shadow:0px_4px_4px_#00000040_inset] rounded-4px"
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
<div class="flex flex-col gap-10px">
|
||||
{renderList(
|
||||
Object.keys(lvInfo.value).filter(
|
||||
(a) => !!lvInfo.value[a].up && lvInfo.value[a].up > 0,
|
||||
),
|
||||
(index) => {
|
||||
const skill = props.skills.find((a) => a.id.toString() === index)
|
||||
let tooltip = ''
|
||||
if (skill) {
|
||||
if (lvInfo.value[skill.id.toString()].up == 1) {
|
||||
if (skill.upType == 'damage') {
|
||||
tooltip = `技能攻击力 +${skill.learnLv < 35 ? '60' : 55}%`
|
||||
} else {
|
||||
tooltip = `生命值恢复量 +20%`
|
||||
}
|
||||
}
|
||||
if (lvInfo.value[skill.id.toString()].up == 2) {
|
||||
if (skill.upType == 'damage') {
|
||||
tooltip = `技能冷却时间 -15%<br/>技能攻击力 +${skill.learnLv < 35 ? '43' : 38}%`
|
||||
} else {
|
||||
tooltip = `技能冷却时间 -10%<br/>生命值恢复量 +5%`
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props.buffer) {
|
||||
tooltip += ` / 主属性 +40`
|
||||
}
|
||||
return skill ? (
|
||||
<div class="flex gap-10px items-center bg-[linear-gradient(180deg,_#1b1c1c_0%,_#000000_100%)] rounded-4px p-5px border-1px border-solid border-#2E2E2E">
|
||||
<div class="up_1 !w-38px !h-38px">
|
||||
<img src={getImageURL(skill?.icon)} alt={skill.name} />
|
||||
</div>
|
||||
<div class="text-#947E4E" v-html={tooltip}></div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div class="absolute left-76px top-137px w-250px flex flex-col gap-21.5px">
|
||||
{renderList(3, (index) => (
|
||||
<>
|
||||
<div class="w-169px h-36px flex items-center justify-between">
|
||||
<div class="m-4px w-28px h-28px">
|
||||
<CaclIconSelect
|
||||
v-model:modelValue={itemChecked(index, 1).value}
|
||||
class="!bg-white/0 !border-white/0 m-4px"
|
||||
columnNum={4}
|
||||
>
|
||||
{renderList(skills(index).value, (item) => (
|
||||
<>
|
||||
<CalcOption value={item.id.toString()}>
|
||||
<img src={getImageURL(item.icon)} alt={item.name} />
|
||||
</CalcOption>
|
||||
</>
|
||||
))}
|
||||
</CaclIconSelect>
|
||||
</div>
|
||||
<CalcButton icon="exchange" onClick={() => exchange(index)}></CalcButton>
|
||||
<div class="m-4px w-28px h-28px">
|
||||
<CaclIconSelect
|
||||
v-model:modelValue={itemChecked(index, 2).value}
|
||||
class=" !bg-white/0 !border-white/0"
|
||||
columnNum={4}
|
||||
>
|
||||
{renderList(skills(index).value, (item) => (
|
||||
<>
|
||||
<CalcOption value={item.id.toString()}>
|
||||
<img src={getImageURL(item.icon)} alt={item.name} />
|
||||
</CalcOption>
|
||||
</>
|
||||
))}
|
||||
</CaclIconSelect>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
<div class="absolute top-70px left-310px w-320px h-270px py-5px px-30px box-border flex flex-col">
|
||||
<div class="flex flex-wrap gap-10px content-start h-120px overflow-y-auto mb-10px">
|
||||
{renderList(skills().value, (item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
class="w-[28px] h-[28px] rounded-[1px] border-[1px] border-solid border-[#6d4e2b] bg-[linear-gradient(180deg,_#191919_0%,_#000000_100%)] [box-shadow:0px_4px_4px_#00000040_inset] rounded-4px"
|
||||
>
|
||||
<img src={getImageURL(item.icon)} alt={item.name} />
|
||||
</div>
|
||||
))}
|
||||
{renderList(36 - skills().value.length, (item) => (
|
||||
<div
|
||||
key={item}
|
||||
class="w-[28px] h-[28px] rounded-[1px] border-[1px] border-solid border-[#6d4e2b] bg-[linear-gradient(180deg,_#191919_0%,_#000000_100%)] [box-shadow:0px_4px_4px_#00000040_inset] rounded-4px"
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
<div class="flex flex-col gap-10px">
|
||||
{renderList(
|
||||
Object.keys(lvInfo.value).filter(
|
||||
(a) => !!lvInfo.value[a].up && lvInfo.value[a].up > 0,
|
||||
),
|
||||
(index) => {
|
||||
const skill = props.skills.find((a) => a.id.toString() === index)
|
||||
let tooltip = ''
|
||||
if (skill) {
|
||||
if (lvInfo.value[skill.id.toString()].up == 1) {
|
||||
tooltip = `技能攻击力 +${skill.learnLv < 35 ? '60' : 55}%`
|
||||
}
|
||||
if (lvInfo.value[skill.id.toString()].up == 2) {
|
||||
tooltip = `技能攻击力 +${skill.learnLv < 35 ? '43' : 38}%<br/>技能冷却时间 -15%`
|
||||
}
|
||||
}
|
||||
|
||||
return skill ? (
|
||||
<div class="flex gap-5px items-center">
|
||||
<div class="w-[28px] h-[28px] rounded-[1px] border-[1px] border-solid border-[#6d4e2b] bg-[linear-gradient(180deg,_#191919_0%,_#000000_100%)] [box-shadow:0px_4px_4px_#00000040_inset] rounded-4px">
|
||||
<img src={getImageURL(skill?.icon)} alt={skill.name} />
|
||||
</div>
|
||||
<div class="text-#947E4E" v-html={tooltip}></div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,75 @@
|
||||
.skill-up {
|
||||
width: 540px;
|
||||
// height: 334px;
|
||||
// background-image: url('./img/bg.png');
|
||||
// background-size: 100% 100%;
|
||||
padding: 5px 10px;
|
||||
padding-bottom: 10px;
|
||||
background-color: black;
|
||||
|
||||
.left {
|
||||
width: 240px;
|
||||
height: 325px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #2e2e2e;
|
||||
background: linear-gradient(to bottom, #1c1c1c 0%, #0a0d0a 100%);
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
|
||||
.up_head_1,
|
||||
.up_head_2 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background-position: center center;
|
||||
}
|
||||
}
|
||||
|
||||
.up_head_1 .icon {
|
||||
background-image: url('./img/attack.png');
|
||||
}
|
||||
|
||||
.up_head_2 .icon {
|
||||
background-image: url('./img/cd.png');
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
.title {
|
||||
width: 261px;
|
||||
height: 48px;
|
||||
background: url('./img/title.png') no-repeat center center;
|
||||
}
|
||||
}
|
||||
|
||||
.up_1,
|
||||
.up_2,
|
||||
.up_3,
|
||||
.up_active {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.up_1 {
|
||||
background: url('./img/up_1.png') no-repeat center center;
|
||||
}
|
||||
|
||||
.up_2 {
|
||||
background: url('./img/up_2.png') no-repeat center center;
|
||||
}
|
||||
|
||||
.up_3 {
|
||||
background: url('./img/up_3.png') no-repeat center center;
|
||||
}
|
||||
|
||||
.up_active {
|
||||
background: url('./img/up_active.png') no-repeat center center;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 724 B |
@@ -0,0 +1,132 @@
|
||||
import { getImageURL } from '@/utils/images'
|
||||
import type { ISkill } from '@/api/info/type'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import CalcCheckbox from '@/components/calc/checkbox/index.vue'
|
||||
import { renderList } from 'vue'
|
||||
import './style.scss'
|
||||
export default defineComponent({
|
||||
name: 'SkillVP',
|
||||
props: {
|
||||
skills: {
|
||||
type: Array as PropType<ISkill[]>,
|
||||
default: () => [],
|
||||
},
|
||||
lvInfo: {
|
||||
type: Object as PropType<Record<string, { lv: number; vp: number }>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['chooseSkill'],
|
||||
setup(props, { emit }) {
|
||||
const lvInfo = useVModel(props, 'lvInfo')
|
||||
const skills = computed(() =>
|
||||
props.skills.filter((a) => a.hasVP && lvInfo).sort((a, b) => a.learnLv - b.learnLv),
|
||||
)
|
||||
|
||||
const currentUsedVP = computed(() => {
|
||||
return Object.keys(lvInfo.value)
|
||||
.filter((a) => skills.value.find((skill) => skill.id.toString() === a))
|
||||
.map((a) => lvInfo.value[a].vp)
|
||||
.filter((a) => !!a && a > 0).length
|
||||
})
|
||||
|
||||
const itemChecked = (skillID: string, vp: number) => {
|
||||
return computed<boolean>({
|
||||
get() {
|
||||
if (!lvInfo.value[skillID].vp) {
|
||||
lvInfo.value[skillID].vp = 0
|
||||
}
|
||||
return lvInfo.value[skillID]?.vp == vp
|
||||
},
|
||||
set(value: boolean) {
|
||||
// 选中当前VP形态 并且当前已选VP大于5 并且当前技能没有VP
|
||||
if (value && currentUsedVP.value >= 5 && lvInfo.value[skillID]?.vp == 0) {
|
||||
return
|
||||
}
|
||||
if (value) {
|
||||
lvInfo.value[skillID].vp = vp
|
||||
} else {
|
||||
lvInfo.value[skillID].vp = 0
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div class="h-auto py-2 flex flex-col bg-#080808 box-content">
|
||||
{renderList(skills.value, (item) => (
|
||||
<div class="h-140px flex items-center vp pl-26px pr-16px box-content">
|
||||
<div class="w-30px text-center text-#816f4c">{item.learnLv}</div>
|
||||
<div class="w-150px flex gap-5px items-center">
|
||||
<div class="border-1px border-solid border-#3F382E rounded-4px w-28px h-28px">
|
||||
<img class="w-28px h-28px" src={getImageURL(item.icon)} alt={item.name} />
|
||||
</div>
|
||||
<div class="skill-name">{item.name}</div>
|
||||
</div>
|
||||
{renderList(2, (index) => (
|
||||
<div
|
||||
class={[
|
||||
'flex items-center flex-col vp-item py-10px',
|
||||
index == 1 ? 'ml-auto' : 'ml-2px',
|
||||
]}
|
||||
onClick={() => {
|
||||
emit('chooseSkill', item.id)
|
||||
itemChecked(item.id.toString(), index).value = !itemChecked(
|
||||
item.id.toString(),
|
||||
index,
|
||||
).value
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
<div
|
||||
class={[
|
||||
'box-border w-full h-full pt-5px m-2px',
|
||||
itemChecked(item.id.toString(), index).value ? 'selected' : '',
|
||||
]}
|
||||
>
|
||||
<div class="flex justify-between w-full h-30px px-10px box-border">
|
||||
<div class="flex items-start">
|
||||
<CalcCheckbox
|
||||
modelValue={itemChecked(item.id.toString(), index).value}
|
||||
disabled={
|
||||
currentUsedVP.value >= 5 && lvInfo.value[item.id.toString()]?.vp == 0
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div class="relative inline-block border-1px border-solid border-#3F382E rounded-4px w-28px h-28px">
|
||||
<div
|
||||
class="bg-#a128f4 w-26px h-26px top-1px left-1px absolute z-5"
|
||||
style="mix-blend-mode: hue;"
|
||||
></div>
|
||||
<img
|
||||
style="filter:brightness(1) contrast(1) saturate(1.5)"
|
||||
class="w-28px h-28px"
|
||||
src={getImageURL(item.icon)}
|
||||
alt={item.name}
|
||||
/>
|
||||
<div class={`vp_${index} absolute bottom--8px left-5px z-10`}></div>
|
||||
</div>
|
||||
<div class="w-16px"></div>
|
||||
</div>
|
||||
<div class="text-13px mt-5px text-center selected-name">
|
||||
{item.vps[index - 1]?.name}
|
||||
</div>
|
||||
{renderList(
|
||||
item.vps[index - 1]?.desc
|
||||
? item.vps[index - 1].desc.replace(/<br\/>/g, '\n').split('\n')
|
||||
: [],
|
||||
(desc) => (
|
||||
<div class="text-12px text-#816f4c text-center leading-none">{desc}</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
.vp {
|
||||
&:nth-child(n + 1) {
|
||||
background-color: #000000;
|
||||
}
|
||||
&:nth-child(2n + 1) {
|
||||
background-color: #080808;
|
||||
}
|
||||
|
||||
.vp-item {
|
||||
height: 120px;
|
||||
width: 148px;
|
||||
padding: 1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-image: url(./img/bg.png);
|
||||
// padding:5px 10px ;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-image: url(./img/selected.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: calc(100% - 2.5px) calc(100% + 2.5px);
|
||||
|
||||
.selected-name {
|
||||
color: #cf57ff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vp_1 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/vp_1.png);
|
||||
}
|
||||
|
||||
.vp_2 {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(../img/vp_2.png);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1001 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 736 B |
|
After Width: | Height: | Size: 681 B |
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="flex relative" ref="skillTreeRef">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex bg-#101012 h-40px text-#6C5E4A font-bold">
|
||||
<div
|
||||
class="flex-1 text-15px flex items-center justify-center"
|
||||
@click="tab = 0"
|
||||
:class="{ active: tab === 0 }"
|
||||
>
|
||||
技能加点
|
||||
</div>
|
||||
<div class="w-auto text-18px flex items-center justify-center !font-normal">|</div>
|
||||
<div
|
||||
class="flex-1 text-15px flex items-center justify-center"
|
||||
@click="tab = 1"
|
||||
:class="{ active: tab === 1 }"
|
||||
>
|
||||
技能定制
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 w-550px overflow-x-hidden bg-#101012">
|
||||
<div class="overflow-y-auto h-full box-border" v-if="tab === 0">
|
||||
<SkillTree
|
||||
:skills="infoStore.skills"
|
||||
v-model:lvInfo="configStore.config.skills"
|
||||
v-model:bindAwake="configStore.config.bindAwake"
|
||||
@chooseSkill="setCurrentSkill"
|
||||
></SkillTree>
|
||||
</div>
|
||||
<div v-if="tab === 1" class="flex flex-col overflow-y-hidden h-full">
|
||||
<div class="item-head" 角色等级>技能突破</div>
|
||||
<SkillUP
|
||||
class="flex-1"
|
||||
:skills="infoStore.skills"
|
||||
v-model:lvInfo="configStore.config.skills"
|
||||
v-model:bindAwake="configStore.config.bindAwake"
|
||||
:buffer="infoStore.infos?.buffer"
|
||||
></SkillUP>
|
||||
<div class="item-head" 角色等级>技能进化</div>
|
||||
<div class="flex-1 overflow-y-auto w-full">
|
||||
<SkillVP
|
||||
:skills="infoStore.skills"
|
||||
v-model:lvInfo="configStore.config.skills"
|
||||
v-model:bindAwake="configStore.config.bindAwake"
|
||||
@chooseSkill="setCurrentSkill"
|
||||
></SkillVP>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute right--65px"
|
||||
v-if="curCurrentSkill.skillId && curCurrentSkill.level > 0 && !showSkillDetail"
|
||||
>
|
||||
<div class="text-#AE8D5A p-1px box-border flex pl-2 w-auto">
|
||||
<div
|
||||
class="border-1px border-solid border-#322E20 border-l-none flex p-1px box-border pl-0"
|
||||
>
|
||||
<div
|
||||
class="border-1px border-#514531 border-solid rounded-2px flex flex-col items-center justify-center p-5px"
|
||||
style="background: linear-gradient(to bottom, #313330 0%, #0f110f 100%)"
|
||||
@click="showSkillDetail = true"
|
||||
>
|
||||
<img class="w-30px h-30px" src="./components/img/detail.png" />
|
||||
技能详情
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-#101012 w-300px h-full absolute"
|
||||
:style="style"
|
||||
ref="SkillDetailRef"
|
||||
v-if="curCurrentSkill.skillId && curCurrentSkill.level > 0 && showSkillDetail"
|
||||
>
|
||||
<SkillDetail :skillId="curCurrentSkill.skillId" :level="curCurrentSkill.level"></SkillDetail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="Index">
|
||||
import { useInfoStore } from '@/stores/info'
|
||||
import SkillTree from './components/SkillTree'
|
||||
import SkillVP from './components/SkillVP'
|
||||
import SkillUP from './components/SkillUP'
|
||||
import { useConfigStore } from '@/stores'
|
||||
import SkillDetail from './components/SkillDetail'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
const props = defineProps<{
|
||||
alter: string
|
||||
}>()
|
||||
const tab = ref(0)
|
||||
const infoStore = useInfoStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
const curSkillId = ref()
|
||||
|
||||
const curCurrentSkill = computed(() => {
|
||||
const skill = infoStore.skills.find((skill) => skill.uuid === curSkillId.value)
|
||||
return {
|
||||
skillId: skill?.uuid ?? '',
|
||||
level: configStore.config.skills[skill?.id || 0]?.lv || 0,
|
||||
}
|
||||
})
|
||||
|
||||
const showSkillDetail = ref(false)
|
||||
|
||||
const skillTreeRef = ref<HTMLHtmlElement>()
|
||||
const SkillDetailRef = ref<HTMLHtmlElement>()
|
||||
|
||||
const style = computed(() => {
|
||||
return {
|
||||
zIndex: 99,
|
||||
height: `${skillTreeRef.value?.clientHeight ?? 0}px`,
|
||||
left: `${(skillTreeRef.value?.clientWidth ?? 0) + 5}px`,
|
||||
}
|
||||
})
|
||||
|
||||
const setCurrentSkill = (skillId: number) => {
|
||||
curSkillId.value = infoStore.skills.find((skill) => skill.id === skillId)?.uuid || ''
|
||||
}
|
||||
|
||||
onClickOutside(
|
||||
SkillDetailRef,
|
||||
(event) => {
|
||||
showSkillDetail.value = false
|
||||
},
|
||||
{
|
||||
ignore: [skillTreeRef],
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.active {
|
||||
color: #ccc088;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 15px;
|
||||
opacity: 0.35;
|
||||
background: #bf8d20;
|
||||
filter: blur(7px);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: url('@/assets/img/active.png') no-repeat;
|
||||
width: 148px;
|
||||
height: 16px;
|
||||
bottom: 2px;
|
||||
transform: translate(0, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
.item-head {
|
||||
background: linear-gradient(#2b2817, #171407);
|
||||
border-top: 1px solid #423d2c;
|
||||
border-bottom: 1px solid #211d15;
|
||||
text-align: center;
|
||||
// 以下后面注释掉
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-weight: 14px;
|
||||
}
|
||||
</style>
|
||||