diff --git a/backend/api/routes/info.py b/backend/api/routes/info.py index 5569fe74..eff41006 100644 --- a/backend/api/routes/info.py +++ b/backend/api/routes/info.py @@ -1,3 +1,4 @@ +import re from api.core.Auth import createToken from api.core.Gzip import register_gzip_request import json @@ -12,6 +13,19 @@ router = APIRouter() register_gzip_request(router) +def replace_placeholders(template): + # 匹配 、、、 等 + pattern = re.compile(r'<(int|float\d*)>') + idx = 0 + + def repl(match): + nonlocal idx + idx += 1 + return f'{{value{idx}}}' + + return pattern.sub(repl, template) + + @router.get('/adventure') async def get_adventure_info(request: Request,redis:RedisDep): @@ -30,6 +44,7 @@ async def getToken(redis:RedisDep, async def get_character_info( request: Request, state: AltersDep ,redis:RedisDep ): + print(state.alter) character = createCharacter(state.alter, state.equVersion) def get_character(): return character.getInfo() @@ -37,3 +52,42 @@ async def get_character_info( # info = character.getInfo() return response(data=info) +@router.get("/skill/{skillId}/{level}") +async def get_skill_info( + state: AltersDep, skillId: str, level: int, redis: RedisDep +): + """ + 获取技能信息 + """ + alter = state.alter + jobId = alter.split(".")[1] + jobGrowId = alter.split(".")[2] + skill_info = {} + key = f'openapi:{jobId}:{jobGrowId}:{skillId}' + try: + def get_skill_info(): + # This function should retrieve the skill info based on job and jobGrow + # For now, we return a placeholder dictionary + with open(f'./openapi/data/{jobId}/{jobGrowId}/cn/skillDetail/{skillId}.json', encoding='utf-8') as f: + skill_data = json.load(f) + if 'levelInfo' in skill_data and 'optionDesc' in skill_data['levelInfo'] and skill_data['levelInfo']['optionDesc'] is not None: + skill_data['levelInfo']['optionDesc'] = replace_placeholders(skill_data['levelInfo']['optionDesc']) + return skill_data + + skill_info = get_redis_info(redis, key, get_skill_info) + if level is not None: + level = max(0, min(level, skill_info.get('maxLevel', 0))) + if 'levelInfo' in skill_info and 'rows' in skill_info['levelInfo']: + detail = list(filter(lambda x: x['level'] == level, skill_info['levelInfo']['rows'])) + if detail and len(detail) > 0: + skill_info['levelInfo'].update(detail[0]) + skill_info['levelInfo']['detail'] = skill_info['levelInfo'].get('optionDesc', '') + for key in skill_info['levelInfo']['optionValue'].keys(): + skill_info['levelInfo']['detail'] = skill_info['levelInfo']['detail'].replace(f'{{{key}}}', str(skill_info['levelInfo']['optionValue'][key])) + del skill_info['levelInfo']['rows'] + skill_info['attribute'] = {} + skill_info['attribute'].update(skill_info['levelInfo']) + del skill_info['levelInfo'] + except FileNotFoundError: + return response(code=404, message=f'技能信息未找到: {jobId} {jobGrowId} {skillId}', data=None) + return response(data=skill_info) diff --git a/backend/core/basic/character.py b/backend/core/basic/character.py index 37f8a9e8..bdeeaf9c 100644 --- a/backend/core/basic/character.py +++ b/backend/core/basic/character.py @@ -475,6 +475,7 @@ class Character(CharacterProperty): 'hasVP': skill.hasVP, 'hasUP': skill.hasUP, 'upType': skill.upType, + 'uuid': skill.uuid, 'vps': [{"name":i["name"],"desc":i["desc"]} for i in skill.vps], } ) diff --git a/backend/core/basic/skill.py b/backend/core/basic/skill.py index abf3d0de..fdad0681 100644 --- a/backend/core/basic/skill.py +++ b/backend/core/basic/skill.py @@ -15,7 +15,6 @@ characterLv = 115 + 5 @cache def get_data(prefix: str, key: int, func): - print(f"loading skill {prefix} {key} data from redis") redis = next(get_redis()) def get_skill_info(): # This function should retrieve the skill info based on job and jobGrow diff --git a/backend/openapi b/backend/openapi index f1389225..f6f172ff 160000 --- a/backend/openapi +++ b/backend/openapi @@ -1 +1 @@ -Subproject commit f138922574b84abf959e4dcd1fb54c2c8be3e406 +Subproject commit f6f172ffeb563bdf0f73b89db0d5d9a1b962d0ca diff --git a/frontend/src/api/info/index.ts b/frontend/src/api/info/index.ts index d2bcfe15..b7729ad2 100644 --- a/frontend/src/api/info/index.ts +++ b/frontend/src/api/info/index.ts @@ -1,5 +1,5 @@ import { defineRequest } from '../common' -import type { IAdventureInfo, ICharacterInfo } from './type' +import type { IAdventureInfo, ICharacterInfo, ISkillInfo } from './type' export default defineRequest((request) => { return { @@ -15,5 +15,9 @@ export default defineRequest((request) => { const res = await request.get('/character') return res.data }, + async skillDetail(skillId: string, level: number) { + const res = await request.get(`/skill/${skillId}/${level}`) + return res.data + } } }) diff --git a/frontend/src/api/info/type.ts b/frontend/src/api/info/type.ts index 7ba13a57..de9f61f3 100644 --- a/frontend/src/api/info/type.ts +++ b/frontend/src/api/info/type.ts @@ -90,7 +90,8 @@ export interface ISkill { name: string desc: string }[] - upType:'damage' |'heal'` + uuid: string + upType:'damage' |'heal' } export interface ISuit { @@ -134,3 +135,56 @@ export interface IJade { pre: number unit: string } + +export interface ISkillInfo { + name: string + type: string + desc: string + descDetail: string + consumeItem: ConsumeItem + descSpecial: string[] + maxLevel: number + requiredLevel: number + requiredLevelRange: number + preRequiredSkill: any + jobId: string + jobName: string + jobGrowLevel: any[] + evolution: Evolution[] + enhancement: Enhancement[] + attribute: Attribute +} + +export interface ConsumeItem { + itemId: string + itemName: string + value: number +} + +export interface Evolution { + type: number + name: string + desc: string + descDetail: string + skills?: string[] +} + +export interface Enhancement { + type: number + status: Status[] +} + +export interface Status { + name: string + value: string +} + +export interface Attribute { + optionDesc: string + level: number + consumeMp: number + coolTime: number + castingTime?: number + optionValue: Record<`value${string}`, string> + detail: string +} diff --git a/frontend/src/pages/Character/Skill/components/SkillDetail/img/skill_title.svg b/frontend/src/pages/Character/Skill/components/SkillDetail/img/skill_title.svg new file mode 100644 index 00000000..1c52c87e --- /dev/null +++ b/frontend/src/pages/Character/Skill/components/SkillDetail/img/skill_title.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/pages/Character/Skill/components/SkillDetail/index.tsx b/frontend/src/pages/Character/Skill/components/SkillDetail/index.tsx new file mode 100644 index 00000000..0f459a3b --- /dev/null +++ b/frontend/src/pages/Character/Skill/components/SkillDetail/index.tsx @@ -0,0 +1,118 @@ +import { asyncComputed } from '@vueuse/core' +import api from '@/api' +import './style.scss' + +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 basic = computed(() => + !skillInfo.value + ? [] + : [ + { label: '技能等级', value: skillInfo.value?.attribute.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').filter((item) => !!item)) + + const detail = computed(() => { + const items = skillInfo.value?.attribute.optionDesc.split('\n').filter((item) => !!item) + const details = items?.map((a) => { + const matches = a.match(/{([^}]+)}/g) + if (matches) { + matches.forEach((match, index) => { + 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, + `${value}`, + ) + } + } + }) + } + return a + }) + return details || [] + }) + + return () => ( + <> +
+
{skillInfo.value?.name}
+
基本信息
+
+ {basic.value.map( + (item) => + !!item.value && ( +
+ {item.label} + {item.value} +
+ ), + )} +
+
技能描述
+
+
+ {desc.value?.map((item) => ( + {item} + ))} +
+
+
技能属性
+
+
+ {detail.value?.map((item) => ( +
+ ))} +
+
+
+ + ) + }, +}) diff --git a/frontend/src/pages/Character/Skill/components/SkillDetail/style.scss b/frontend/src/pages/Character/Skill/components/SkillDetail/style.scss new file mode 100644 index 00000000..5ad5d202 --- /dev/null +++ b/frontend/src/pages/Character/Skill/components/SkillDetail/style.scss @@ -0,0 +1,55 @@ +.skill_title { + 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: 18px; + 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); + } +} + + +.title{ + border: 1px solid #3e4040; + color: #c1a238; + background: linear-gradient(180deg, #2b292b 0%, #0b0b0b 100%); + height: 18px; + display: flex; + align-items: center; + padding: 0px 5px; +} + +.item{ + background-color: #171616; + padding: 0px 5px; + .label{ + color: #907B54; + } + .value{ + color:#4AA256; + } +} diff --git a/frontend/src/pages/Character/Skill/components/SkillTree/index.tsx b/frontend/src/pages/Character/Skill/components/SkillTree/index.tsx index 1ba75aae..83721864 100644 --- a/frontend/src/pages/Character/Skill/components/SkillTree/index.tsx +++ b/frontend/src/pages/Character/Skill/components/SkillTree/index.tsx @@ -20,10 +20,12 @@ export default defineComponent({ default: () => 50, }, }, - setup(props) { + 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) @@ -52,6 +54,7 @@ export default defineComponent({ } if (activeSkill.value == skill.id) actionSkillLv(skill, operation) else activeSkill.value = skill.id + emit('chooseSkill', skill.id) } const awakeSkill_1 = computed(() => @@ -78,11 +81,11 @@ export default defineComponent({ lvInfo.value[skill.id.toString()] = { lv: 0 , vp: 0, up: 0 } return } + emit('chooseSkill', skill.id) } const chooseBindAwake = (lv: number) => { bindAwake.value = lv - console.log(bindAwake.value, lv) } const renderSkill = (skill: ISkill, index: number) => { diff --git a/frontend/src/pages/Character/Skill/components/SkillUP/index.tsx b/frontend/src/pages/Character/Skill/components/SkillUP/index.tsx index 6707acc2..ca2b5e39 100644 --- a/frontend/src/pages/Character/Skill/components/SkillUP/index.tsx +++ b/frontend/src/pages/Character/Skill/components/SkillUP/index.tsx @@ -26,7 +26,6 @@ export default defineComponent({ }, setup(props) { const lvInfo = useVModel(props, 'lvInfo') - const skills = (index: number = 0) => { index -= 1 const upList = Object.keys(lvInfo.value).filter( diff --git a/frontend/src/pages/Character/Skill/index.vue b/frontend/src/pages/Character/Skill/index.vue index 25543d25..f9774b87 100644 --- a/frontend/src/pages/Character/Skill/index.vue +++ b/frontend/src/pages/Character/Skill/index.vue @@ -1,62 +1,63 @@ @@ -66,6 +67,7 @@ import SkillTree from './components/SkillTree' import SkillVP from './components/SkillVP' import SkillUP from './components/SkillUP' import { useConfigStore } from '@/stores' +import SkillDetail from './components/SkillDetail' const props = defineProps<{ alter: string }>() @@ -73,6 +75,20 @@ 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 setCurrentSkill = (skillId: number) => { + curSkillId.value = infoStore.skills.find(skill => skill.id === skillId)?.uuid || '' +} + const linkColg = () => { window.open('https://bbs.colg.cn/page_collect/330.html', '_blank') }