Files
frontend/apps/web-ele/src/views/mp/components/wx-reply/wx-reply.vue
2025-11-21 18:19:42 +08:00

168 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts" setup>
import type { Reply } from './types';
import { computed, ref, unref, watch } from 'vue';
import { NewsType, ReplyType } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
import { ElRow, ElTabPane, ElTabs } from 'element-plus';
import TabImage from './tab-image.vue';
import TabMusic from './tab-music.vue';
import TabNews from './tab-news.vue';
import TabText from './tab-text.vue';
import TabVideo from './tab-video.vue';
import TabVoice from './tab-voice.vue';
import { createEmptyReply } from './types';
/** 消息回复选择 */
defineOptions({ name: 'WxReplySelect' });
const props = withDefaults(defineProps<Props>(), {
newsType: undefined,
});
const emit = defineEmits<{
(e: 'update:modelValue', v: Reply): void;
}>();
interface Props {
modelValue: Reply | undefined;
newsType?: NewsType;
}
const defaultReply: Reply = {
accountId: -1,
type: ReplyType.Text,
}; // 提供一个默认的 Reply 对象,避免 undefined 导致的错误
const reply = computed<Reply>({
get: () => props.modelValue || defaultReply,
set: (val) => emit('update:modelValue', val),
});
const tabCache = new Map<ReplyType, Reply>(); // 作为多个标签保存各自 Reply 的缓存
const currentTab = ref<ReplyType>(props.modelValue?.type || ReplyType.Text); // 采用独立的 ref 来保存当前 tab避免在 watch 标签变化,对 reply 进行赋值会产生了循环调用
// 监听 modelValue 变化,同步更新 currentTab 和缓存
watch(
() => props.modelValue,
(newValue) => {
if (newValue?.type) {
// 如果类型变化,更新 currentTab
if (newValue.type !== currentTab.value) {
currentTab.value = newValue.type;
}
// 如果 modelValue 有数据,更新对应 tab 的缓存
if (newValue.type) {
tabCache.set(newValue.type, { ...newValue });
}
}
},
{ immediate: true, deep: true },
);
watch(
currentTab,
(newTab, oldTab) => {
// 第一次进入oldTab 为 undefined
// 判断 newTab 是因为 Reply 为 Partial
if (oldTab === undefined || newTab === undefined) {
return;
}
// 保存旧tab的数据到缓存
const oldReply = unref(reply);
// 只有当旧tab的reply有实际数据时才缓存避免缓存空数据
if (oldReply && oldTab === oldReply.type) {
tabCache.set(oldTab, oldReply);
}
// 从缓存里面取出新tab内容有则覆盖Reply没有则创建空Reply
const temp = tabCache.get(newTab);
if (temp) {
reply.value = temp;
} else {
// 如果当前reply的类型就是新tab的类型说明这是从外部传入的数据应该保留
const currentReply = unref(reply);
if (currentReply && currentReply.type === newTab) {
// 这是从外部传入的数据,直接缓存并使用
tabCache.set(newTab, currentReply);
// 不需要修改reply.value因为它已经是正确的了
} else {
// 创建新的空reply
const newData = createEmptyReply(reply);
newData.type = newTab;
reply.value = newData;
}
}
},
{
immediate: true,
},
);
/** 清除除了`type`, `accountId`的字段 */
function clear() {
reply.value = createEmptyReply(reply);
}
defineExpose({
clear,
});
</script>
<template>
<ElTabs type="border-card" v-model="reply.type" @tab-change="clear">
<!-- 类型 1文本 -->
<ElTabPane :name="ReplyType.Text">
<template #label>
<ElRow align="middle"><IconifyIcon icon="ep:document" /> 文本</ElRow>
</template>
<TabText v-model="reply.content" />
</ElTabPane>
<!-- 类型 2图片 -->
<ElTabPane :name="ReplyType.Image">
<template #label>
<ElRow align="middle">
<IconifyIcon icon="ep:picture" class="mr-5px" /> 图片
</ElRow>
</template>
<TabImage v-model="reply" />
</ElTabPane>
<!-- 类型 3语音 -->
<ElTabPane :name="ReplyType.Voice">
<template #label>
<ElRow align="middle"><IconifyIcon icon="ep:phone" /> 语音</ElRow>
</template>
<TabVoice v-model="reply" />
</ElTabPane>
<!-- 类型 4视频 -->
<ElTabPane :name="ReplyType.Video">
<template #label>
<ElRow align="middle"><IconifyIcon icon="ep:share" /> 视频</ElRow>
</template>
<TabVideo v-model="reply" />
</ElTabPane>
<!-- 类型 5图文 -->
<ElTabPane :name="ReplyType.News">
<template #label>
<ElRow align="middle"><IconifyIcon icon="ep:reading" /> 图文</ElRow>
</template>
<TabNews v-model="reply" :news-type="newsType" />
</ElTabPane>
<!-- 类型 6音乐 -->
<ElTabPane :name="ReplyType.Music">
<template #label>
<ElRow align="middle"><IconifyIcon icon="ep:service" />音乐</ElRow>
</template>
<TabMusic v-model="reply" />
</ElTabPane>
</ElTabs>
</template>