chore: dev proxy config, sprite animator refactor, cleanups

- add vite dev proxy for `/ingress` to forward requests to backend https://onefeel.brother7.cn
- update .env.development to use relative proxy paths instead of full backend URLs
- move SpriteAnimator component to correct directory, update all imports and global type declarations in components.d.ts
- remove unused CSS styles and fix styling for ChatTopNavBar component, including switching to van-icon and hardcoding temporary site name
- uncomment and enable the ChatMainList component in home page
- clean up unused code, fix formatting, and set default client ID in src/utils/request.ts
- comment out redundant uni API calls in ChatMainList to simplify code
- update WebSocket URL handling to support relative paths in ChatMainList
This commit is contained in:
DEV_DSW
2026-05-27 10:40:21 +08:00
parent 0d46ac0e2c
commit a75bb909f1
11 changed files with 109 additions and 250 deletions

View File

@@ -0,0 +1,118 @@
<template>
<div class="sprite-animator" :style="spriteStyle" />
</template>
<script setup lang="ts">
import { computed, ref, onMounted, onUnmounted } from "vue";
const props = withDefaults(
defineProps<{
src: string;
frameWidth: number;
frameHeight: number;
totalFrames: number;
columns: number;
displayWidth: number;
fps?: number;
autoplay?: boolean;
loop?: boolean;
}>(),
{
fps: 12,
autoplay: true,
loop: true,
},
);
/* JS 精确逐帧方案(使用 setInterval确保整数偏移、无插值 */
const currentFrame = ref(0);
let intervalId: number | null = null;
const play = () => {
stop();
const safeFps = Math.max(1, Math.floor(props.fps || 12));
const durationPerFrame = 1000 / safeFps;
// 强制整数像素偏移,使用 setInterval 在各运行时更可靠
const setter = (globalThis as any).setInterval || setInterval;
intervalId = setter(() => {
const safeTotal = Math.max(1, Math.floor(props.totalFrames || 0));
if (currentFrame.value >= safeTotal - 1) {
if (props.loop) {
currentFrame.value = 0;
} else {
stop();
return;
}
} else {
currentFrame.value++;
}
}, durationPerFrame) as unknown as number;
};
const stop = () => {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
};
onMounted(() => {
console.log("[SpriteAnimator] mounted", {
autoplay: props.autoplay,
fps: props.fps,
});
if (props.autoplay) play();
});
onUnmounted(() => stop());
const spriteStyle = computed(() => {
const {
src,
frameWidth: rawFrameWidth,
frameHeight: rawFrameHeight,
totalFrames: rawTotalFrames,
columns: rawColumns,
} = props;
const frameWidth = Math.max(1, Math.floor(rawFrameWidth || 0));
const frameHeight = Math.max(1, Math.floor(rawFrameHeight || 0));
const totalFrames = Math.max(1, Math.floor(rawTotalFrames || 0));
let columns = Math.floor(Number(rawColumns) || 0);
if (!columns || columns <= 0) columns = Math.min(1, totalFrames);
columns = Math.min(columns, totalFrames);
const displayWidth = props.displayWidth || frameWidth;
const rows = Math.ceil(totalFrames / columns);
const imageWidth = columns * frameWidth;
const imageHeight = rows * frameHeight;
const scale = displayWidth / frameWidth;
const displayHeight = Math.round(frameHeight * scale);
const scaledImageWidth = Math.round(imageWidth * scale);
const scaledImageHeight = Math.round(imageHeight * scale);
if (currentFrame.value < 0) currentFrame.value = 0;
if (currentFrame.value >= totalFrames) currentFrame.value = totalFrames - 1;
const col = currentFrame.value % columns;
const row = Math.floor(currentFrame.value / columns);
const offsetX = Math.round(col * frameWidth * scale);
const offsetY = Math.round(row * frameHeight * scale);
const styleStr = `width: ${Math.round(displayWidth)}px; height: ${displayHeight}px; background-image: url("${src}"); background-repeat: no-repeat; background-size: ${scaledImageWidth}px ${scaledImageHeight}px; background-position: -${offsetX}px -${offsetY}px;`;
return styleStr as any;
});
// 对外暴露
defineExpose({ play, stop, currentFrame });
</script>
<style scoped>
.sprite-animator {
display: block;
will-change: background-position;
}
</style>