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:
118
src/components/SpriteAnimator/index.vue
Normal file
118
src/components/SpriteAnimator/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user