feat: enhance hidden domains with additional properties and UI updates

- Updated `HiddenDomainInfo` interface to include `cd_years` and `open_prob` properties for better domain management.
- Modified `serialize_active_domains` function to serialize new properties.
- Enhanced `StatusWidget` to display additional information about hidden domains, including cooldown years and open probability.
- Updated localization files for English and Chinese to reflect new status labels for hidden domains.
This commit is contained in:
bridge
2026-02-01 12:41:47 +08:00
parent 07dacb8876
commit 666310e7b4
8 changed files with 198 additions and 31 deletions

View File

@@ -2843,7 +2843,7 @@ msgstr "孔雀翎"
msgid "WEAPON_1077_DESC" msgid "WEAPON_1077_DESC"
msgstr "虽然只是暗器,但其美艳不可方物。当它绽放时,便是敌人的死期。" msgstr "虽然只是暗器,但其美艳不可方物。当它绽放时,便是敌人的死期。"
#. world_info.csv: #. world_info.csv: 简介
msgid "WORLD_INFO_INTRO_NAME" msgid "WORLD_INFO_INTRO_NAME"
msgstr "简介" msgstr "简介"
@@ -2851,7 +2851,11 @@ msgstr "简介"
msgid "WORLD_INFO_INTRO_DESC" msgid "WORLD_INFO_INTRO_DESC"
msgstr "这是一个诸多修士竞相修行的修仙世界。" msgstr "这是一个诸多修士竞相修行的修仙世界。"
#. world_info.csv: #. world_info.csv: 简介
msgid "WORLD_INFO_INTRO_TITLE"
msgstr "简介"
#. world_info.csv: 境界
msgid "WORLD_INFO_REALM_NAME" msgid "WORLD_INFO_REALM_NAME"
msgstr "境界" msgstr "境界"
@@ -2859,15 +2863,23 @@ msgstr "境界"
msgid "WORLD_INFO_REALM_DESC" msgid "WORLD_INFO_REALM_DESC"
msgstr "修仙的境界从弱到强:练气、筑基、金丹、元婴。每个境界分前期、中期、后期。境界间差距很大。" msgstr "修仙的境界从弱到强:练气、筑基、金丹、元婴。每个境界分前期、中期、后期。境界间差距很大。"
#. world_info.csv: #. world_info.csv: 境界
msgid "WORLD_INFO_REALM_TITLE"
msgstr "境界"
#. world_info.csv: 寿元
msgid "WORLD_INFO_LIFESPAN_NAME" msgid "WORLD_INFO_LIFESPAN_NAME"
msgstr "寿元" msgstr "寿元"
#. world_info.csv: 每个角色均有寿元上限。在大限将至的前20年身体会逐渐衰弱有几率老死一旦达到或超过寿元上限... #. world_info.csv: 每个角色均有寿元上限。在大限将至的前20年身体会逐渐衰弱有几率老死一旦达到或超过寿元上限则必死无疑。提升境界和某些宝物、丹药能提高寿元上限。
msgid "WORLD_INFO_LIFESPAN_DESC" msgid "WORLD_INFO_LIFESPAN_DESC"
msgstr "每个角色均有寿元上限。在大限将至的前20年身体会逐渐衰弱有几率老死一旦达到或超过寿元上限则必死无疑。提升境界和某些宝物、丹药能提高寿元上限。" msgstr "每个角色均有寿元上限。在大限将至的前20年身体会逐渐衰弱有几率老死一旦达到或超过寿元上限则必死无疑。提升境界和某些宝物、丹药能提高寿元上限。"
#. world_info.csv: #. world_info.csv: 寿元
msgid "WORLD_INFO_LIFESPAN_TITLE"
msgstr "寿元"
#. world_info.csv: 死亡
msgid "WORLD_INFO_DEATH_NAME" msgid "WORLD_INFO_DEATH_NAME"
msgstr "死亡" msgstr "死亡"
@@ -2875,7 +2887,11 @@ msgstr "死亡"
msgid "WORLD_INFO_DEATH_DESC" msgid "WORLD_INFO_DEATH_DESC"
msgstr "HP降至0以下也会死亡。" msgstr "HP降至0以下也会死亡。"
#. world_info.csv: #. world_info.csv: 死亡
msgid "WORLD_INFO_DEATH_TITLE"
msgstr "死亡"
#. world_info.csv: 区域
msgid "WORLD_INFO_REGION_NAME" msgid "WORLD_INFO_REGION_NAME"
msgstr "区域" msgstr "区域"
@@ -2883,7 +2899,11 @@ msgstr "区域"
msgid "WORLD_INFO_REGION_DESC" msgid "WORLD_INFO_REGION_DESC"
msgstr "不同区域有不同效果,在适当的区域做适当的事情事半功倍。" msgstr "不同区域有不同效果,在适当的区域做适当的事情事半功倍。"
#. world_info.csv: #. world_info.csv: 区域
msgid "WORLD_INFO_REGION_TITLE"
msgstr "区域"
#. world_info.csv: 修炼
msgid "WORLD_INFO_CULTIVATION_NAME" msgid "WORLD_INFO_CULTIVATION_NAME"
msgstr "修炼" msgstr "修炼"
@@ -2891,15 +2911,23 @@ msgstr "修炼"
msgid "WORLD_INFO_CULTIVATION_DESC" msgid "WORLD_INFO_CULTIVATION_DESC"
msgstr "修炼可以增加经验,直到到达突破前的瓶颈。突破是概率事件,突破后会进入下一个境界。" msgstr "修炼可以增加经验,直到到达突破前的瓶颈。突破是概率事件,突破后会进入下一个境界。"
#. world_info.csv: #. world_info.csv: 修炼
msgid "WORLD_INFO_CULTIVATION_TITLE"
msgstr "修炼"
#. world_info.csv: 灵根
msgid "WORLD_INFO_ROOT_NAME" msgid "WORLD_INFO_ROOT_NAME"
msgstr "灵根" msgstr "灵根"
#. world_info.csv: 灵根由五行元素(金、木、水、火、土)组成,分为单灵根、双灵根和天灵根。五行相克:金克木,木克土... #. world_info.csv: 灵根由五行元素(金、木、水、火、土)组成,分为单灵根、双灵根和天灵根。五行相克:金克木,木克土,土克水,水克火,火克金。天灵根包含全部五行元素且突破时有额外加成。在与自身灵根属性匹配的区域修炼效率最高。
msgid "WORLD_INFO_ROOT_DESC" msgid "WORLD_INFO_ROOT_DESC"
msgstr "灵根由五行元素(金、木、水、火、土)组成,分为单灵根、双灵根和天灵根。五行相克:金克木,木克土,土克水,水克火,火克金。天灵根包含全部五行元素且突破时有额外加成。在与自身灵根属性匹配的区域修炼效率最高。" msgstr "灵根由五行元素(金、木、水、火、土)组成,分为单灵根、双灵根和天灵根。五行相克:金克木,木克土,土克水,水克火,火克金。天灵根包含全部五行元素且突破时有额外加成。在与自身灵根属性匹配的区域修炼效率最高。"
#. world_info.csv: #. world_info.csv: 灵根
msgid "WORLD_INFO_ROOT_TITLE"
msgstr "灵根"
#. world_info.csv: 天地灵机
msgid "WORLD_INFO_WORLD_QI_NAME" msgid "WORLD_INFO_WORLD_QI_NAME"
msgstr "天地灵机" msgstr "天地灵机"
@@ -2907,7 +2935,11 @@ msgstr "天地灵机"
msgid "WORLD_INFO_WORLD_QI_DESC" msgid "WORLD_INFO_WORLD_QI_DESC"
msgstr "世界每隔数年会有一次天象变动(如灵气潮汐),影响角色能力。" msgstr "世界每隔数年会有一次天象变动(如灵气潮汐),影响角色能力。"
#. world_info.csv: #. world_info.csv: 天地灵机
msgid "WORLD_INFO_WORLD_QI_TITLE"
msgstr "天地灵机"
#. world_info.csv: 灵石
msgid "WORLD_INFO_SPIRIT_STONE_NAME" msgid "WORLD_INFO_SPIRIT_STONE_NAME"
msgstr "灵石" msgstr "灵石"
@@ -2915,7 +2947,11 @@ msgstr "灵石"
msgid "WORLD_INFO_SPIRIT_STONE_DESC" msgid "WORLD_INFO_SPIRIT_STONE_DESC"
msgstr "修仙界的通用货币。可用于购买法宝丹药,通过采集、交易或掠夺获取。" msgstr "修仙界的通用货币。可用于购买法宝丹药,通过采集、交易或掠夺获取。"
#. world_info.csv: #. world_info.csv: 灵石
msgid "WORLD_INFO_SPIRIT_STONE_TITLE"
msgstr "灵石"
#. world_info.csv: 宗门
msgid "WORLD_INFO_SECT_NAME" msgid "WORLD_INFO_SECT_NAME"
msgstr "宗门" msgstr "宗门"
@@ -2923,7 +2959,11 @@ msgstr "宗门"
msgid "WORLD_INFO_SECT_DESC" msgid "WORLD_INFO_SECT_DESC"
msgstr "修士的庇护所。加入宗门可习得独门功法、获同门庇护;散修自由但资源匮乏。" msgstr "修士的庇护所。加入宗门可习得独门功法、获同门庇护;散修自由但资源匮乏。"
#. world_info.csv: #. world_info.csv: 宗门
msgid "WORLD_INFO_SECT_TITLE"
msgstr "宗门"
#. world_info.csv: 战斗
msgid "WORLD_INFO_BATTLE_NAME" msgid "WORLD_INFO_BATTLE_NAME"
msgstr "战斗" msgstr "战斗"
@@ -2931,7 +2971,11 @@ msgstr "战斗"
msgid "WORLD_INFO_BATTLE_DESC" msgid "WORLD_INFO_BATTLE_DESC"
msgstr "弱肉强食,境界压制极大,高境界者对低境界者有绝对优势。若对方死亡,胜者可掠夺败者财物。" msgstr "弱肉强食,境界压制极大,高境界者对低境界者有绝对优势。若对方死亡,胜者可掠夺败者财物。"
#. world_info.csv: #. world_info.csv: 战斗
msgid "WORLD_INFO_BATTLE_TITLE"
msgstr "战斗"
#. world_info.csv: 动作
msgid "WORLD_INFO_ACTION_NAME" msgid "WORLD_INFO_ACTION_NAME"
msgstr "动作" msgstr "动作"
@@ -2939,23 +2983,35 @@ msgstr "动作"
msgid "WORLD_INFO_ACTION_DESC" msgid "WORLD_INFO_ACTION_DESC"
msgstr "你有一系列可以执行的动作。要注意动作的效果、限制条件、区域和时间。" msgstr "你有一系列可以执行的动作。要注意动作的效果、限制条件、区域和时间。"
#. world_info.csv: #. world_info.csv: 动作
msgid "WORLD_INFO_ACTION_TITLE"
msgstr "动作"
#. world_info.csv: 装备与丹药
msgid "WORLD_INFO_EQUIPMENT_AND_ELIXIR_NAME" msgid "WORLD_INFO_EQUIPMENT_AND_ELIXIR_NAME"
msgstr "装备与丹药" msgstr "装备与丹药"
#. world_info.csv: 通过兵器、辅助装备、丹药等装备,可以获得额外的属性加成,获得或小或大的增益。拥有好的装备或者服... #. world_info.csv: 通过兵器、辅助装备、丹药等装备,可以获得额外的属性加成,获得或小或大的增益。拥有好的装备或者服用好的丹药,能获得很大好处。
msgid "WORLD_INFO_EQUIPMENT_AND_ELIXIR_DESC" msgid "WORLD_INFO_EQUIPMENT_AND_ELIXIR_DESC"
msgstr "通过兵器、辅助装备、丹药等装备,可以获得额外的属性加成,获得或小或大的增益。拥有好的装备或者服用好的丹药,能获得很大好处。" msgstr "通过兵器、辅助装备、丹药等装备,可以获得额外的属性加成,获得或小或大的增益。拥有好的装备或者服用好的丹药,能获得很大好处。"
#. world_info.csv: #. world_info.csv: 装备与丹药
msgid "WORLD_INFO_EQUIPMENT_AND_ELIXIR_TITLE"
msgstr "装备与丹药"
#. world_info.csv: 购物
msgid "WORLD_INFO_SHOPPING_NAME" msgid "WORLD_INFO_SHOPPING_NAME"
msgstr "购物" msgstr "购物"
#. world_info.csv: 在城市区域可以购买练气级别丹药、兵器。购买丹药后会立刻服用强化自身。购买兵器可以帮自己切换兵器... #. world_info.csv: 在城市区域可以购买练气级别丹药、兵器。购买丹药后会立刻服用强化自身。购买兵器可以帮自己切换兵器类型为顺手的类型。
msgid "WORLD_INFO_SHOPPING_DESC" msgid "WORLD_INFO_SHOPPING_DESC"
msgstr "在城市区域可以购买练气级别丹药、兵器。购买丹药后会立刻服用强化自身。购买兵器可以帮自己切换兵器类型为顺手的类型。" msgstr "在城市区域可以购买练气级别丹药、兵器。购买丹药后会立刻服用强化自身。购买兵器可以帮自己切换兵器类型为顺手的类型。"
#. world_info.csv: #. world_info.csv: 购物
msgid "WORLD_INFO_SHOPPING_TITLE"
msgstr "购物"
#. world_info.csv: 拍卖会
msgid "WORLD_INFO_AUCTION_NAME" msgid "WORLD_INFO_AUCTION_NAME"
msgstr "拍卖会" msgstr "拍卖会"
@@ -2963,10 +3019,18 @@ msgstr "拍卖会"
msgid "WORLD_INFO_AUCTION_DESC" msgid "WORLD_INFO_AUCTION_DESC"
msgstr "每隔一段不确定的时间会有神秘人组织的拍卖会,或许有好货出售。" msgstr "每隔一段不确定的时间会有神秘人组织的拍卖会,或许有好货出售。"
#. world_info.csv: #. world_info.csv: 拍卖会
msgid "WORLD_INFO_AUCTION_TITLE"
msgstr "拍卖会"
#. world_info.csv: 秘境
msgid "WORLD_INFO_SECRET_REALM_NAME" msgid "WORLD_INFO_SECRET_REALM_NAME"
msgstr "秘境" msgstr "秘境"
#. world_info.csv: 每隔数年会有秘境开启。进入秘境可能遭遇凶险,也可能获得高阶法宝和功法。 #. world_info.csv: 每隔数年会有秘境开启。进入秘境可能遭遇凶险,也可能获得高阶法宝和功法。
msgid "WORLD_INFO_SECRET_REALM_DESC" msgid "WORLD_INFO_SECRET_REALM_DESC"
msgstr "每隔数年会有秘境开启。进入秘境可能遭遇凶险,也可能获得高阶法宝和功法。" msgstr "每隔数年会有秘境开启。进入秘境可能遭遇凶险,也可能获得高阶法宝和功法。"
#. world_info.csv: 秘境
msgid "WORLD_INFO_SECRET_REALM_TITLE"
msgstr "秘境"

View File

@@ -204,7 +204,9 @@ def serialize_active_domains(world: World) -> List[dict]:
"max_realm": str(d.max_realm), "max_realm": str(d.max_realm),
"danger_prob": d.danger_prob, "danger_prob": d.danger_prob,
"drop_prob": d.drop_prob, "drop_prob": d.drop_prob,
"is_open": is_open "is_open": is_open,
"cd_years": d.cd_years,
"open_prob": d.open_prob
}) })
return domains_data return domains_data

View File

@@ -0,0 +1,48 @@
import pytest
from src.utils.df import load_game_configs
from src.i18n import t
def test_all_csv_ids_have_translations():
"""
遍历加载的所有游戏配置 CSV检查所有以 _id 结尾的字段(如 name_id, desc_id, title_id
确保它们在中文环境下都有对应的翻译。
如果 t(key) 返回的结果等于 key 本身,且 key 是全大写(典型的 ID 格式),则视为缺失翻译。
"""
# 1. 加载配置 (force_chinese_language fixture 已经在 session 级别生效)
configs = load_game_configs()
missing_keys = []
# 需要检查的 ID 后缀
target_suffixes = ('_id', '_ID')
# 一些已知的例外或不需要翻译的字段可以加在这里
ignored_keys = set()
# 2. 遍历所有配置表
for config_name, rows in configs.items():
if not rows:
continue
print(f"Checking config: {config_name}.csv ({len(rows)} rows)")
for i, row in enumerate(rows):
# 遍历行中的所有键值对
for key, value in row.items():
# 检查 Key 是否以 _id 结尾 (例如 title_id)
# 并且 Value 是字符串且非空
if key.lower().endswith("id") and isinstance(value, str) and value.strip():
# 也可以进一步过滤,例如只检查全大写的 Value或者特定前缀
# 这里假设所有需要翻译的 ID 都是全大写且包含下划线
if value.isupper() and "_" in value:
translated = t(value)
# 如果翻译结果和原文一样,且原文不是空的,视为缺失
# 注意:有些 ID 本身就是英文显示(极少),这里主要针对需要转中文的情况
if translated == value:
missing_keys.append(f"[{config_name}.csv Row {i+1}] {key}={value}")
# 3. 断言
if missing_keys:
error_msg = f"Found {len(missing_keys)} missing translations in zh-CN:\n" + "\n".join(missing_keys)
pytest.fail(error_msg)

View File

@@ -1,7 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { NPopover, NList, NListItem, NTag, NEmpty } from 'naive-ui' import { NPopover, NList, NListItem, NTag, NEmpty } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import type { HiddenDomainInfo } from '../../types/core' import type { HiddenDomainInfo } from '../../types/core'
const { t } = useI18n()
interface Props { interface Props {
// 触发器显示 // 触发器显示
label: string label: string
@@ -56,8 +59,12 @@ const emit = defineEmits(['trigger-click'])
<div class="d-header"> <div class="d-header">
<div class="d-title-group"> <div class="d-title-group">
<span class="d-name">{{ item.name }}</span> <span class="d-name">{{ item.name }}</span>
<n-tag v-if="!item.is_open" size="small" :bordered="false" class="d-status closed">未开启</n-tag> <n-tag v-if="!item.is_open" size="small" :bordered="false" class="d-status closed">
<n-tag v-else size="small" :bordered="false" type="success" class="d-status open">开启中</n-tag> {{ t('game.status_bar.hidden_domain.status_closed') }}
</n-tag>
<n-tag v-else size="small" :bordered="false" type="success" class="d-status open">
{{ t('game.status_bar.hidden_domain.status_open') }}
</n-tag>
</div> </div>
<n-tag size="small" :bordered="false" type="warning" class="d-tag"> <n-tag size="small" :bordered="false" type="warning" class="d-tag">
{{ item.max_realm }} {{ item.max_realm }}
@@ -67,6 +74,8 @@ const emit = defineEmits(['trigger-click'])
<div class="d-stats"> <div class="d-stats">
<span>💀 {{ (item.danger_prob * 100).toFixed(0) }}%</span> <span>💀 {{ (item.danger_prob * 100).toFixed(0) }}%</span>
<span>🎁 {{ (item.drop_prob * 100).toFixed(0) }}%</span> <span>🎁 {{ (item.drop_prob * 100).toFixed(0) }}%</span>
<span> {{ item.cd_years }}</span>
<span>🎲 {{ (item.open_prob * 100).toFixed(0) }}%</span>
</div> </div>
</div> </div>
</n-list-item> </n-list-item>
@@ -95,20 +104,58 @@ const emit = defineEmits(['trigger-click'])
font-size: 14px; font-size: 14px;
} }
.domain-item { padding: 4px 0; } .domain-item {
.domain-item.is-closed { opacity: 0.5; filter: grayscale(0.8); } padding: 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.03);
margin-bottom: 4px;
}
/* 移除之前的 opacity 和 grayscale改用颜色控制 */
.domain-item.is-closed {
background: rgba(0, 0, 0, 0.2);
}
/* 未开启时的标题颜色变暗 */
.domain-item.is-closed .d-name {
color: #888;
}
/* 开启时背景稍微亮一点 */
.domain-item:not(.is-closed) {
background: rgba(250, 219, 20, 0.05); /* 淡淡的金色背景 */
border: 1px solid rgba(250, 219, 20, 0.1);
}
.d-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; } .d-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.d-title-group { display: flex; align-items: center; gap: 8px; } .d-title-group { display: flex; align-items: center; gap: 8px; }
.d-name { font-weight: bold; color: #fadb14; font-size: 14px; } .d-name { font-weight: bold; color: #fadb14; font-size: 14px; transition: color 0.3s; }
.d-tag { font-size: 10px; height: 18px; line-height: 18px; } .d-tag { font-size: 10px; height: 18px; line-height: 18px; }
.d-status { font-size: 10px; height: 18px; line-height: 18px; padding: 0 4px; } .d-status { font-size: 10px; height: 18px; line-height: 18px; padding: 0 4px; }
.d-desc { font-size: 12px; color: #aaa; margin-bottom: 8px; line-height: 1.4; } /* 描述文字颜色调整,未开启时不要太暗 */
.d-stats { display: flex; gap: 12px; font-size: 12px; color: #888; } .d-desc {
font-size: 12px;
color: #ccc;
margin-bottom: 8px;
line-height: 1.4;
}
.domain-item.is-closed .d-desc {
color: #999;
}
.d-stats { display: flex; gap: 12px; font-size: 12px; color: #888; flex-wrap: wrap; }
/* 统计数据在开启状态下高亮一点 */
.domain-item:not(.is-closed) .d-stats {
color: #aaa;
}
.empty-state { padding: 20px; } .empty-state { padding: 20px; }
/* Naive UI List Override */ /* Naive UI List Override */
:deep(.n-list-item) { :deep(.n-list-item) {
padding: 8px 12px !important; padding: 4px !important; /* 减少 list item 自身的 padding由 domain-item 控制 */
}
:deep(.n-list-item:hover) {
background: transparent !important; /* 避免双重 hover 背景 */
} }
</style> </style>

View File

@@ -236,7 +236,9 @@
"title": "Hidden Domains List", "title": "Hidden Domains List",
"empty": "No hidden domains data", "empty": "No hidden domains data",
"danger": "Danger", "danger": "Danger",
"drop": "Fortune" "drop": "Fortune",
"status_open": "Open",
"status_closed": "Closed"
} }
}, },
"controls": { "controls": {

View File

@@ -236,7 +236,9 @@
"title": "秘境列表", "title": "秘境列表",
"empty": "暂无秘境数据", "empty": "暂无秘境数据",
"danger": "凶险", "danger": "凶险",
"drop": "机缘" "drop": "机缘",
"status_open": "开启中",
"status_closed": "未开启"
} }
}, },
"controls": { "controls": {

View File

@@ -194,6 +194,8 @@ export interface HiddenDomainInfo {
danger_prob: number; // 凶险度 (0.0 - 1.0) danger_prob: number; // 凶险度 (0.0 - 1.0)
drop_prob: number; // 机缘度 (0.0 - 1.0) drop_prob: number; // 机缘度 (0.0 - 1.0)
is_open: boolean; // 是否开启 is_open: boolean; // 是否开启
cd_years: number; // CD 年份
open_prob: number; // 开启概率
} }
// --- 事件 (Events) --- // --- 事件 (Events) ---