diff --git a/backend/api/routes/openapi.py b/backend/api/routes/openapi.py index 9cc80e29..3d3bf59f 100644 --- a/backend/api/routes/openapi.py +++ b/backend/api/routes/openapi.py @@ -208,6 +208,34 @@ async def get_skill_data_summary( job (str): 职业名称,必填参数 skills (str, optional): 技能列表字符串,逗号拼接技能名称,不传递或者传递为空时,查询所有技能,默认为'' weapons (str, optional): 武器类型列表字符串,逗号拼接武器类型名称,不传递或者传递为空时,查询所有武器类型,默认为'' + + Response: + { + "code": int, + "message": str, + "data": [ + { + "技能名称": str, + "武器类型": str, + "职业": str, + "技能描述": str, + "技能等级": int, + "冷却时间": float, + "消耗": str, + "伤害": float, + # 已学等级 + "learnedLevel": int | str, + "基础百分比": float | str, + "等效百分比": float | str, + "原始冷却": float | str, + "实际冷却": float | str, + "理论秒伤": float | str, + "SP": float | None | str, + # 所在等级 + "unlockAtRoleLevel": str | None, + } + ] + } """ job = job.replace('(', '(').replace(')', ')') key = f'openapi:skillData:summary:{job}' diff --git a/backend/openapi b/backend/openapi index 8ec11073..20f052e9 160000 --- a/backend/openapi +++ b/backend/openapi @@ -1 +1 @@ -Subproject commit 8ec11073e6c656f838a3709997f8a3902fe6d78a +Subproject commit 20f052e93e58762be450d510dc6d8568c90721ae diff --git a/backend/pyproject.toml b/backend/pyproject.toml index b4e501df..a40503b3 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.10" dependencies = [ "aiohttp>=3.13.3", "fastapi[all]>=0.115.11", - "fastmcp>=3.1.0", + "fastmcp>=3.3.1", "redis>=5.2.1", "requests>=2.32.3", "sqlalchemy>=2.0.38", diff --git a/backend/uv.lock b/backend/uv.lock index c78a559c..df9a9147 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -200,14 +200,15 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.9" +version = "1.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, + { name = "joserfc" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/98/00d3dd826d46959ad8e32af2dbb2398868fd9fd0683c26e56d0789bd0e68/authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04", size = 165134, upload-time = "2026-03-02T07:44:01.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/23/b65f568ed0c22f1efacb744d2db1a33c8068f384b8c9b482b52ebdbc3ef6/authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3", size = 244197, upload-time = "2026-03-02T07:44:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" }, ] [[package]] @@ -533,7 +534,7 @@ dependencies = [ requires-dist = [ { name = "aiohttp", specifier = ">=3.13.3" }, { name = "fastapi", extras = ["all"], specifier = ">=0.115.11" }, - { name = "fastmcp", specifier = ">=3.1.0" }, + { name = "fastmcp", specifier = ">=3.3.1" }, { name = "redis", specifier = ">=5.2.1" }, { name = "requests", specifier = ">=2.32.3" }, { name = "sqlalchemy", specifier = ">=2.0.38" }, @@ -639,12 +640,47 @@ standard = [ [[package]] name = "fastmcp" -version = "3.1.0" +version = "3.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "fastmcp-slim", extra = ["client", "server"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/a9/5c5a01b6abd5346bf60b97cfd29e4a86661940c27dd562bfcda07fd03519/fastmcp-3.3.1.tar.gz", hash = "sha256:979362ea557de42a5f40342563c7e4b236bcc8e7cd192715f50030695d1a71cd", size = 28681699, upload-time = "2026-05-15T15:50:39.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/11/6b1bdada6ccfe647d615ae63f9106f8136aec17971e9361546af01c7d38e/fastmcp-3.3.1-py3-none-any.whl", hash = "sha256:862440c5c4d281363a5995eee59d77f0f7cac1f18869038729cecf03b02fc522", size = 7903, upload-time = "2026-05-15T15:50:36.424Z" }, +] + +[[package]] +name = "fastmcp-slim" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pydantic", extra = ["email"] }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/a0/627103e517e1d0d6f1eec633d5662d13e776f01b45ad188e4f5f7478b438/fastmcp_slim-3.3.1.tar.gz", hash = "sha256:0957835fc59452e143ab2f4b7836d2d2df9b2d9958408edc79ba8b56232b2a88", size = 567007, upload-time = "2026-05-15T15:50:10.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/ee/97047f4cc2d7b1d46670d08d8ad01a96e7a748cc01c0b4b351ad8eddbc7a/fastmcp_slim-3.3.1-py3-none-any.whl", hash = "sha256:6cf1c2d77e3adb0d409d6825ed6b0b2a999062973e00b8eea03bd48bf9b4c043", size = 738644, upload-time = "2026-05-15T15:50:08.336Z" }, +] + +[package.optional-dependencies] +client = [ + { name = "authlib" }, + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "mcp" }, + { name = "opentelemetry-api" }, + { name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] }, +] +server = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, + { name = "griffelib" }, { name = "httpx" }, { name = "jsonref" }, { name = "jsonschema-path" }, @@ -652,22 +688,15 @@ dependencies = [ { name = "openapi-pydantic" }, { name = "opentelemetry-api" }, { name = "packaging" }, - { name = "platformdirs" }, { name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] }, - { name = "pydantic", extra = ["email"] }, { name = "pyperclip" }, - { name = "python-dotenv" }, + { name = "python-multipart" }, { name = "pyyaml" }, - { name = "rich" }, { name = "uncalled-for" }, { name = "uvicorn" }, { name = "watchfiles" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/70/862026c4589441f86ad3108f05bfb2f781c6b322ad60a982f40b303b47d7/fastmcp-3.1.0.tar.gz", hash = "sha256:e25264794c734b9977502a51466961eeecff92a0c2f3b49c40c070993628d6d0", size = 17347083, upload-time = "2026-03-03T02:43:11.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/07/516f5b20d88932e5a466c2216b628e5358a71b3a9f522215607c3281de05/fastmcp-3.1.0-py3-none-any.whl", hash = "sha256:b1f73b56fd3b0cb2bd9e2a144fc650d5cc31587ed129d996db7710e464ae8010", size = 633749, upload-time = "2026-03-03T02:43:09.06Z" }, -] [[package]] name = "frozenlist" @@ -841,6 +870,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112, upload-time = "2024-09-20T17:09:28.753Z" }, ] +[[package]] +name = "griffelib" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, +] + [[package]] name = "h11" version = "0.14.0" @@ -1010,6 +1048,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "joserfc" +version = "1.6.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/dc/5f768c2e391e9afabe5d18e3221346deb5fb6338565f1ccc9e7c6d7befdd/joserfc-1.6.5.tar.gz", hash = "sha256:1482a7db78fb4602e44ed89e51b599d052e091288c7c532c5b694e20149dec48", size = 231881, upload-time = "2026-05-06T04:58:13.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/3b/ad1cb22e75c963b1f07c8a2329bf47227ce7e4361df5eb2fb101b2ce33ef/joserfc-1.6.5-py3-none-any.whl", hash = "sha256:e9878a0f8243fe7b95e11fdda81374ca9f7a689e302751579d3dfdeec559675e", size = 70464, upload-time = "2026-05-06T04:58:11.668Z" }, +] + [[package]] name = "jsonref" version = "1.1.0" @@ -1796,11 +1846,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.20" +version = "0.0.29" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/fe/70bd71a6738b09a0bdf6480ca6436b167469ca4578b2a0efbe390b4b0e70/python_multipart-0.0.29.tar.gz", hash = "sha256:643e93849196645e2dbdd81a0f8829a23123ad7f797a84a364c6fb3563f18904", size = 45678, upload-time = "2026-05-17T17:29:47.654Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, + { url = "https://files.pythonhosted.org/packages/8f/cb/769cfc37177252872a45a71f3fbdde9d51b471a3f3c14bfe95dde3407386/python_multipart-0.0.29-py3-none-any.whl", hash = "sha256:2ddcc971cef266225f54f552d8fa10bcfbb1f14446caec199060daac59ff2d69", size = 29640, upload-time = "2026-05-17T17:29:45.69Z" }, ] [[package]] diff --git a/frontendNew/src/components/ui/accordion/Accordion.vue b/frontendNew/src/components/ui/accordion/Accordion.vue new file mode 100644 index 00000000..28fa7714 --- /dev/null +++ b/frontendNew/src/components/ui/accordion/Accordion.vue @@ -0,0 +1,15 @@ + + + diff --git a/frontendNew/src/components/ui/accordion/AccordionContent.vue b/frontendNew/src/components/ui/accordion/AccordionContent.vue new file mode 100644 index 00000000..f2d3bb0e --- /dev/null +++ b/frontendNew/src/components/ui/accordion/AccordionContent.vue @@ -0,0 +1,19 @@ + + + diff --git a/frontendNew/src/components/ui/accordion/AccordionItem.vue b/frontendNew/src/components/ui/accordion/AccordionItem.vue new file mode 100644 index 00000000..bb951540 --- /dev/null +++ b/frontendNew/src/components/ui/accordion/AccordionItem.vue @@ -0,0 +1,13 @@ + + + diff --git a/frontendNew/src/components/ui/accordion/AccordionTrigger.vue b/frontendNew/src/components/ui/accordion/AccordionTrigger.vue new file mode 100644 index 00000000..b30abee4 --- /dev/null +++ b/frontendNew/src/components/ui/accordion/AccordionTrigger.vue @@ -0,0 +1,21 @@ + + + diff --git a/frontendNew/src/components/ui/accordion/index.ts b/frontendNew/src/components/ui/accordion/index.ts new file mode 100644 index 00000000..96771991 --- /dev/null +++ b/frontendNew/src/components/ui/accordion/index.ts @@ -0,0 +1,4 @@ +export { default as Accordion } from './Accordion.vue' +export { default as AccordionItem } from './AccordionItem.vue' +export { default as AccordionTrigger } from './AccordionTrigger.vue' +export { default as AccordionContent } from './AccordionContent.vue' diff --git a/frontendNew/src/components/ui/scroll-area/ScrollArea.vue b/frontendNew/src/components/ui/scroll-area/ScrollArea.vue new file mode 100644 index 00000000..061cafa8 --- /dev/null +++ b/frontendNew/src/components/ui/scroll-area/ScrollArea.vue @@ -0,0 +1,16 @@ + + + diff --git a/frontendNew/src/components/ui/scroll-area/ScrollBar.vue b/frontendNew/src/components/ui/scroll-area/ScrollBar.vue new file mode 100644 index 00000000..fa1cb7d3 --- /dev/null +++ b/frontendNew/src/components/ui/scroll-area/ScrollBar.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontendNew/src/components/ui/scroll-area/index.ts b/frontendNew/src/components/ui/scroll-area/index.ts new file mode 100644 index 00000000..2bd4fae5 --- /dev/null +++ b/frontendNew/src/components/ui/scroll-area/index.ts @@ -0,0 +1,2 @@ +export { default as ScrollArea } from './ScrollArea.vue' +export { default as ScrollBar } from './ScrollBar.vue' diff --git a/frontendNew/src/pages/Dcalc/Character/Forge/index.vue b/frontendNew/src/pages/Dcalc/Character/Forge/index.vue index a58a875d..166d5c98 100644 --- a/frontendNew/src/pages/Dcalc/Character/Forge/index.vue +++ b/frontendNew/src/pages/Dcalc/Character/Forge/index.vue @@ -367,13 +367,6 @@ const resetAllForge = () => { > 配置编辑 -
diff --git a/frontendNew/src/pages/Dcalc/Character/Index.vue b/frontendNew/src/pages/Dcalc/Character/Index.vue index 94fe566f..2a2ce354 100644 --- a/frontendNew/src/pages/Dcalc/Character/Index.vue +++ b/frontendNew/src/pages/Dcalc/Character/Index.vue @@ -46,7 +46,8 @@ const activeTab = computed(() => { return "equipment"; }); -const showGlobalResultPanel = computed(() => activeTab.value !== "equipment"); +// Result panel is now embedded inside Equipment/Skill/Forge pages to avoid duplicate rendering. +const showGlobalResultPanel = computed(() => false); const loadCharacter = async () => { loading.value = true; diff --git a/frontendNew/src/pages/Dcalc/Character/Result/Index.vue b/frontendNew/src/pages/Dcalc/Character/Result/Index.vue index 499aad7f..59218cfa 100644 --- a/frontendNew/src/pages/Dcalc/Character/Result/Index.vue +++ b/frontendNew/src/pages/Dcalc/Character/Result/Index.vue @@ -5,6 +5,13 @@ import { useConfigStore, useInfoStore } from "@/stores"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import Tabs from "@/components/ui/tabs/Tabs.vue"; import TabsList from "@/components/ui/tabs/TabsList.vue"; import TabsTrigger from "@/components/ui/tabs/TabsTrigger.vue"; @@ -163,6 +170,8 @@ const diffClass = (current: number, baseline: number) => { const hasVisibleResult = computed(() => { return (resultState.value?.skills?.length ?? 0) > 0 || (resultState.value?.info?.length ?? 0) > 0; }); + +const defaultOpenSections = ["equipped", "changes"]; - + + +