feat(09-10): apply R-2/R-3/R-4/R-5/R-6/R-7 review fixes to equipbox, toolbox & shop

- R-2: render descriptions via TextContent (equipbox/toolbox/shop)
- R-3: add jsDoc to top-level definitions
- R-4: add JSX section comments
- R-5: un-export IHeroAttrItem and IShopItemEntry
- R-6: isNil for literals, ! for objects (no === null / undefined)
- R-7: move top-level openShop call into onMounted
This commit is contained in:
unanmed 2026-10-11 15:57:14 +08:00
parent 4c383f6999
commit fc159ce11f
3 changed files with 77 additions and 44 deletions

View File

@ -1,13 +1,14 @@
import { GameUI, SetupComponentOptions } from '@motajs/system';
import { computed, defineComponent, ref } from 'vue';
import { isNil } from 'lodash-es';
import { Font } from '@motajs/render';
import { Scroll } from '@user/client-base';
import { Scroll, TextContent } from '@user/client-base';
import { EquipStatus, IHeroState } from '@user/data-base';
import { IHeroAttr } from '@user/data-common';
import { FULL_LOC, MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
import { IUIPropsBase } from './types';
export interface IHeroAttrItem {
interface IHeroAttrItem {
/** 勇士属性的键名 */
readonly id: keyof IHeroAttr;
/** 该属性在状态区域显示的中文名 */
@ -21,6 +22,7 @@ export interface EquipBoxProps extends IUIPropsBase {
attributes: readonly IHeroAttrItem[];
}
/** 装备界面组件的 props 声明 */
const equipBoxProps = {
props: ['controller', 'instance', 'state', 'heroState', 'attributes']
} satisfies SetupComponentOptions<EquipBoxProps>;
@ -67,7 +69,9 @@ const titleFont = Font.defaults({ size: 15, weight: 700 });
/** 属性名与描述文本字体 */
const labelFont = Font.defaults({ size: 13 });
/** 装备界面组件 */
export const EquipBox = defineComponent<EquipBoxProps>(props => {
/** 勇士状态对象 */
const hero = props.heroState;
/** 当前选中的装备实例 uid,-1 表示未选中 */
@ -112,7 +116,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
const equippedList = computed(() => {
return hero.equip.slots.map((_, i) => {
const uid = hero.equip.getEquipped(i);
if (uid === undefined) return null;
if (isNil(uid)) return null;
return hero.items.equipment.get(uid);
});
});
@ -142,7 +146,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
const target = getTargetSlot(selected.value);
if (target < 0) return null;
const existing = hero.equip.getEquipped(target);
const another = existing === undefined ? -1 : existing;
const another = isNil(existing) ? -1 : existing;
return hero.equip.compareEquip(selected.value, another, target);
});
@ -150,7 +154,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
const dragNum = computed(() => {
if (dragUid.value < 0) return -1;
const equip = hero.items.equipment.get(dragUid.value);
return equip === null ? -1 : equip.item.num;
return equip ? equip.item.num : -1;
});
/**
@ -237,6 +241,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
onMove={ev => movePress(ev.absoluteX, ev.absoluteY)}
onUp={endPress}
>
{/* 状态区 */}
<text
text="状态"
loc={[8, 8]}
@ -305,10 +310,11 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
})}
</Scroll>
{/* 描述区 */}
<container
loc={[RIGHT_LEFT, DESC_TOP, RIGHT_WIDTH, DESC_HEIGHT]}
>
{equip === null && (
{!equip && (
<text
text="未选择装备"
loc={[8, 12]}
@ -317,7 +323,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
fillStyle="#888888"
/>
)}
{equip !== null && (
{equip && (
<text
text={equip.item.name}
loc={[8, 12]}
@ -326,7 +332,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
fillStyle="gold"
/>
)}
{equip !== null && (
{equip && (
<text
text={`装备孔:${equip.item.equip.slots.join(' / ')}`}
loc={[8, 36]}
@ -335,15 +341,15 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
fillStyle="#bbbbbb"
/>
)}
{equip !== null && (
{equip && (
<Scroll
noscroll
loc={[0, 56, RIGHT_WIDTH, DESC_HEIGHT - 56]}
>
<text
<TextContent
text={equip.item.text}
loc={[8, 4]}
anc={[0, 0]}
width={RIGHT_WIDTH - 16}
loc={[8, 4, RIGHT_WIDTH - 16, DESC_HEIGHT - 60]}
font={labelFont}
fillStyle="#dddddd"
/>
@ -351,6 +357,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
)}
</container>
{/* 装备槽区 */}
<container
loc={[RIGHT_LEFT, SLOT_TOP, RIGHT_WIDTH, SLOT_HEIGHT]}
>
@ -362,7 +369,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
{hero.equip.slots.map((name, i) => {
const slotEquip = equippedList.value[i];
const isSelected =
slotEquip !== null &&
!!slotEquip &&
slotEquip.uid === selected.value;
return (
<container
@ -399,7 +406,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
}
lineWidth={isSelected ? 2 : 1}
/>
{slotEquip !== null && (
{slotEquip && (
<icon
icon={slotEquip.item.num}
loc={[
@ -437,6 +444,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
</Scroll>
</container>
{/* 拥有装备区 */}
<container
loc={[RIGHT_LEFT, OWNED_TOP, RIGHT_WIDTH, OWNED_HEIGHT]}
>
@ -517,6 +525,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
</Scroll>
</container>
{/* 右下角退出按钮 */}
<text
text="退出"
loc={[MAIN_WIDTH - 12, MAIN_HEIGHT - 10]}
@ -527,6 +536,7 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
onClick={exit}
/>
{/* 拖拽浮层 */}
{dragging.value && (
<icon
icon={dragNum.value}
@ -545,4 +555,5 @@ export const EquipBox = defineComponent<EquipBoxProps>(props => {
};
}, equipBoxProps);
/** 装备界面的 UI 控制器 */
export const EquipBoxUI = new GameUI('equipbox', EquipBox);

View File

@ -1,7 +1,7 @@
import { GameUI, SetupComponentOptions } from '@motajs/system';
import { computed, defineComponent, ref } from 'vue';
import { computed, defineComponent, onMounted, ref } from 'vue';
import { Font } from '@motajs/render';
import { Scroll } from '@user/client-base';
import { Scroll, TextContent } from '@user/client-base';
import { IHeroState, IShopState } from '@user/data-base';
import { IShopSystem } from '@user/data-system';
import { IHeroAttr } from '@user/data-common';
@ -17,6 +17,7 @@ export interface ShopProps extends IUIPropsBase {
shopSystem: IShopSystem;
}
/** 商店界面组件的 props 声明 */
const shopProps = {
props: [
'controller',
@ -28,7 +29,7 @@ const shopProps = {
]
} satisfies SetupComponentOptions<ShopProps>;
export interface IShopItemEntry {
interface IShopItemEntry {
/** 道具图块数字 */
readonly num: number;
/** 道具字符串 id */
@ -87,12 +88,15 @@ const titleFont = Font.defaults({ size: 14, weight: 700 });
/** 列表与信息字体 */
const textFont = Font.defaults({ size: 12 });
/** 商店界面组件 */
export const Shop = defineComponent<ShopProps>(props => {
/** 商店状态对象 */
const shop = props.shop;
/** 勇士状态对象 */
const hero = props.heroState;
// 打开数据端当前商店,使其成为购买 / 售出交易的作用对象
props.shopSystem.openShop(shop);
onMounted(() => props.shopSystem.openShop(shop));
/** 当前的购买 / 售出模式 */
const mode = ref<'buy' | 'sell'>('buy');
@ -111,7 +115,7 @@ export const Shop = defineComponent<ShopProps>(props => {
for (const pair of shop.iterateItems()) {
const num = pair[0];
const data = props.state.itemStore.getData(num);
if (data === null) continue;
if (!data) continue;
const id = props.state.tileStore.id(num);
const price = shop.getPrice(num);
list.push({
@ -215,13 +219,14 @@ export const Shop = defineComponent<ShopProps>(props => {
return () => {
const entry = current.value;
let total = 0;
if (entry !== null) {
if (entry) {
const price =
mode.value === 'buy' ? entry.buyPrice : entry.sellPrice;
total = count.value * price;
}
return (
<container loc={FULL_LOC}>
{/* 顶部购买 / 售出切换栏 */}
<container loc={[0, 0, MAIN_WIDTH, MODE_BAR_HEIGHT]}>
<text
text="购买选项"
@ -242,6 +247,7 @@ export const Shop = defineComponent<ShopProps>(props => {
onClick={() => changeMode('sell')}
/>
</container>
{/* 商店列表 */}
<Scroll loc={[0, LIST_TOP, LIST_WIDTH, LIST_HEIGHT]}>
{entries.value.map((item, i) => {
const isSelected = item.num === selected.value;
@ -289,25 +295,28 @@ export const Shop = defineComponent<ShopProps>(props => {
);
})}
</Scroll>
{/* 选中道具名称 */}
<text
text={entry === null ? '未选择道具' : entry.name}
text={entry ? entry.name : '未选择道具'}
loc={[INFO_LEFT + 14, NAME_TOP + 13]}
anc={[0, 0.5]}
font={titleFont}
fillStyle="gold"
/>
{/* 选中道具描述 */}
<Scroll
noscroll
loc={[INFO_LEFT, DESC_TOP, INFO_WIDTH, DESC_HEIGHT]}
>
<text
text={entry === null ? '' : entry.text}
loc={[14, 4]}
anc={[0, 0]}
<TextContent
text={entry ? entry.text : ''}
width={INFO_WIDTH - 28}
loc={[14, 4, INFO_WIDTH - 28, DESC_HEIGHT - 8]}
font={textFont}
fillStyle="#dddddd"
/>
</Scroll>
{/* 价格信息 */}
<container
loc={[
INFO_LEFT,
@ -325,9 +334,9 @@ export const Shop = defineComponent<ShopProps>(props => {
/>
<text
text={
entry === null
? '-'
: core.formatBigNumber(entry.buyPrice)
entry
? core.formatBigNumber(entry.buyPrice)
: '-'
}
loc={[INFO_WIDTH - 14, 10]}
anc={[1, 0.5]}
@ -343,9 +352,9 @@ export const Shop = defineComponent<ShopProps>(props => {
/>
<text
text={
entry === null
? '-'
: core.formatBigNumber(entry.sellPrice)
entry
? core.formatBigNumber(entry.sellPrice)
: '-'
}
loc={[INFO_WIDTH - 14, 30]}
anc={[1, 0.5]}
@ -361,9 +370,9 @@ export const Shop = defineComponent<ShopProps>(props => {
/>
<text
text={
entry === null
? '-'
: core.formatBigNumber(entry.stock)
entry
? core.formatBigNumber(entry.stock)
: '-'
}
loc={[INFO_WIDTH - 14, 50]}
anc={[1, 0.5]}
@ -379,9 +388,9 @@ export const Shop = defineComponent<ShopProps>(props => {
/>
<text
text={
entry === null
? '-'
: core.formatBigNumber(entry.owned)
entry
? core.formatBigNumber(entry.owned)
: '-'
}
loc={[INFO_WIDTH - 14, 70]}
anc={[1, 0.5]}
@ -403,6 +412,7 @@ export const Shop = defineComponent<ShopProps>(props => {
fillStyle="gold"
/>
</container>
{/* 数量选择 */}
<text
text={mode.value === 'buy' ? '购买数量' : '售出数量'}
loc={[INFO_LEFT + INFO_WIDTH / 2, COUNT_LABEL_Y]}
@ -453,6 +463,7 @@ export const Shop = defineComponent<ShopProps>(props => {
fillStyle="#ffffff"
onClick={() => adjustCount(COUNT_STEP)}
/>
{/* 购买 / 售出确认按钮 */}
<container loc={[INFO_LEFT, CONFIRM_Y, INFO_WIDTH, 30]}>
<g-rectr
loc={[14, 0, INFO_WIDTH - 28, 30]}
@ -473,6 +484,7 @@ export const Shop = defineComponent<ShopProps>(props => {
onClick={confirm}
/>
</container>
{/* 右下角退出按钮 */}
<text
text="退出"
loc={[MAIN_WIDTH - 12, MAIN_HEIGHT - 10]}
@ -487,4 +499,5 @@ export const Shop = defineComponent<ShopProps>(props => {
};
}, shopProps);
/** 商店界面的 UI 控制器 */
export const ShopUI = new GameUI('shop', Shop);

View File

@ -1,7 +1,7 @@
import { GameUI, SetupComponentOptions } from '@motajs/system';
import { computed, defineComponent, ref } from 'vue';
import { Font } from '@motajs/render';
import { Scroll } from '@user/client-base';
import { Scroll, TextContent } from '@user/client-base';
import { IHeroItemState, IHeroState } from '@user/data-base';
import { IHeroAttr } from '@user/data-common';
import { FULL_LOC, MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
@ -12,6 +12,7 @@ export interface ToolBoxProps extends IUIPropsBase {
heroState: IHeroState<IHeroAttr>;
}
/** 道具栏组件的 props 声明 */
const toolBoxProps = {
props: ['controller', 'instance', 'state', 'heroState']
} satisfies SetupComponentOptions<ToolBoxProps>;
@ -42,7 +43,9 @@ const itemFont = Font.defaults({ size: 13 });
/** 道具说明字体 */
const descFont = Font.defaults({ size: 12 });
/** 道具栏组件 */
export const ToolBox = defineComponent<ToolBoxProps>(props => {
/** 勇士状态对象 */
const hero = props.heroState;
/** 当前选中的道具图块数字,-1 表示未选中 */
@ -101,6 +104,7 @@ export const ToolBox = defineComponent<ToolBoxProps>(props => {
const current = selectedItem.value;
return (
<container loc={FULL_LOC}>
{/* 永久道具列 */}
<text
text="永久道具"
loc={[8, HEADER_HEIGHT / 2]}
@ -108,6 +112,7 @@ export const ToolBox = defineComponent<ToolBoxProps>(props => {
font={titleFont}
fillStyle="gold"
/>
{/* 可消耗道具列 */}
<text
text="可消耗道具"
loc={[COLUMN_WIDTH + 8, HEADER_HEIGHT / 2]}
@ -222,18 +227,20 @@ export const ToolBox = defineComponent<ToolBoxProps>(props => {
);
})}
</Scroll>
{/* 道具名称 */}
<text
text={current === null ? '未选择道具' : current.raw.name}
text={current ? current.raw.name : '未选择道具'}
loc={[INFO_LEFT + 8, 14]}
anc={[0, 0]}
font={titleFont}
fillStyle="gold"
/>
{/* 道具说明 */}
<Scroll
noscroll
loc={[INFO_LEFT, DESC_TOP, COLUMN_WIDTH, DESC_HEIGHT]}
>
{current === null && (
{!current && (
<text
text="未选择道具"
loc={[8, 4]}
@ -242,16 +249,17 @@ export const ToolBox = defineComponent<ToolBoxProps>(props => {
fillStyle="#888888"
/>
)}
{current !== null && (
<text
{current && (
<TextContent
text={current.raw.text}
loc={[8, 4]}
anc={[0, 0]}
width={COLUMN_WIDTH - 16}
loc={[8, 4, COLUMN_WIDTH - 16, DESC_HEIGHT - 8]}
font={descFont}
fillStyle="#dddddd"
/>
)}
</Scroll>
{/* 右下角退出按钮 */}
<text
text="退出"
loc={[MAIN_WIDTH - 12, MAIN_HEIGHT - 10]}
@ -266,4 +274,5 @@ export const ToolBox = defineComponent<ToolBoxProps>(props => {
};
}, toolBoxProps);
/** 道具栏的 UI 控制器 */
export const ToolBoxUI = new GameUI('toolbox', ToolBox);