Files
template/packages-user/client-base/src/components/tip.tsx
T
AncTe 0b93426d47 refactor(04-11): 渲染端结构性移植——通用渲染资产下沉 @user/client-base(D-58..D-68)
- MOVE:39 个文件由 client-modules/src/render/ 移入 client-base/src/{components,elements,map,layout}/,删除原件
- D-58:新建 client-base/src/shared/(10 常量纯摘取)+ 被移 map 文件 shared 说明符改指 4 处
- D-63:client-base/src/index.ts 新增 5 条桶导出;14 个实现层消费者改指 @user/client-base
- D-66:client-base/package.json 新增 5 条 workspace:* 依赖 + 根 pnpm i(lockfile importer 同步)
- D-59 / D-60 / D-68:10 处架构耦合以带原因 // @ts-expect-error 暂记,不反向引用、不解耦
- D-62:命名一律不变;D-64 ?raw 不处理;D-65 legacy 一律不管
2026-09-24 17:14:43 +08:00

178 lines
5.4 KiB
TypeScript

import { ElementLocator, Font } from '@motajs/render';
import { logger } from '@motajs/common';
import { computed, defineComponent, onUnmounted, ref } from 'vue';
// @ts-expect-error render/use.ts 按 D-54 留在实现层,本文件已移入系统层,按 D-68 暂以标注记录,不反向引用、不解耦
import { transitioned } from '../use';
import { hyper } from 'mutate-animate';
import { debounce } from 'lodash-es';
import { texture } from '../elements';
import { SetupComponentOptions } from '@motajs/system';
import { DefaultProps } from '@motajs/render-vue';
export interface TipProps extends DefaultProps {
/** 显示的位置 */
loc: ElementLocator;
/** 边距 */
pad?: [number, number];
/** 圆角 */
corner?: number;
/** 显示的图标 */
id?: string;
}
export interface TipExpose {
/**
* 显示提示文本
* @param text 提示文字
* @param icon 显示的图标,不填则不显示
*/
drawTip(text: string, icon?: AllIds | AllNumbers): void;
}
const tipProps = {
props: ['loc', 'pad', 'corner', 'id']
} satisfies SetupComponentOptions<TipProps>;
let id = 0;
function getNextTipId() {
return `@default-tip-${id++}`;
}
export const Tip = defineComponent<TipProps>((props, { expose }) => {
const iconNum = ref<AllNumbers>(0);
const text = ref<string>('');
const textWidth = ref(0);
const font = new Font('normal');
const alpha = transitioned(0, 500, hyper('sin', 'in-out'))!;
const pad = computed(() => props.pad ?? [4, 4]);
const locHeight = computed(() => props.loc[3] ?? 200);
const hidden = computed(() => alpha.ref.value === 0);
const showIcon = computed(() => iconNum.value !== 0);
const iconSize = computed<[number, number]>(() => {
const renderable = texture.getRenderable(iconNum.value);
if (!renderable) return [1, 1];
const [, , width, height] = renderable.render[0];
return [width, height];
});
const iconLoc = computed<ElementLocator>(() => {
const [width, height] = iconSize.value;
const aspect = width / height;
const realHeight = locHeight.value - pad.value[1] * 2;
const realWidth = realHeight * aspect;
return [pad.value[0], pad.value[1], realWidth, realHeight];
});
const textLoc = computed<ElementLocator>(() => {
if (showIcon.value) {
const [, , width] = iconLoc.value;
const x = width! + pad.value[0] + pad.value[1];
return [x, locHeight.value / 2, void 0, void 0, 0, 0.5];
} else {
return [pad.value[0], locHeight.value / 2, void 0, void 0, 0, 0.5];
}
});
const containerLoc = computed<ElementLocator>(() => {
const [x = 0, y = 0, , height = 200] = props.loc;
const iconWidth = iconLoc.value[2] ?? 32;
if (showIcon.value) {
const width =
textWidth.value + iconWidth + pad.value[0] * 2 + pad.value[1];
return [x, y, width, height];
} else {
const width = textWidth.value + pad.value[0] * 2;
return [x, y, width, height];
}
});
const rectLoc = computed<ElementLocator>(() => {
const [, , width = 200, height = 200] = containerLoc.value;
return [1, 1, width - 2, height - 2];
});
const hide = debounce(() => {
alpha.set(0);
}, 3000);
const drawTip = (tipText: string, iconId: AllIds | AllNumbers = 0) => {
if (typeof iconId === 'string') {
const num = texture.idNumberMap[iconId];
iconNum.value = num;
} else {
// 样板竟然会传 null 进来,然后报错
iconNum.value = iconId ?? 0;
}
text.value = core.replaceText(tipText);
alpha.set(0, 0);
alpha.set(1);
hide();
};
const onSetText = (width: number) => {
textWidth.value = width;
};
const ex: TipExpose = { drawTip };
TipStore.use(props.id ?? getNextTipId(), ex);
expose<TipExpose>(ex);
return () => (
<container
loc={containerLoc.value}
alpha={alpha.ref.value}
hidden={hidden.value}
noevent
>
<g-rectr
loc={rectLoc.value}
circle={[props.corner ?? 4]}
fill
fillStyle="rgba(0,0,0,0.8)"
/>
<icon
hidden={!showIcon.value}
icon={iconNum.value}
loc={iconLoc.value}
/>
<text
loc={textLoc.value}
text={text.value}
onResize={onSetText}
font={font}
/>
</container>
);
}, tipProps);
export class TipStore {
static list: Map<string, TipStore> = new Map();
private constructor(private readonly data: TipExpose) {}
/**
* 显示提示文本
* @param text 提示文字
* @param icon 显示的图标,不填则不显示
*/
drawTip(text: string, icon: AllIds | AllNumbers = 0) {
this.data.drawTip(text, icon);
}
static get(id: string) {
return TipStore.list.get(id);
}
static use(id: string, data: TipExpose) {
const store = new TipStore(data);
if (this.list.has(id)) {
logger.warn(60, id);
}
this.list.set(id, store);
onUnmounted(() => {
this.list.delete(id);
});
return store;
}
}