fix: todo修复

This commit is contained in:
hw
2025-11-21 18:19:42 +08:00
parent 0251dc2f3b
commit 8d7d3d5fe1
86 changed files with 963 additions and 1195 deletions

View File

@@ -1,5 +0,0 @@
export { createEmptyReply, type Reply, ReplyType } from './types';
export { default } from './wx-reply.vue';
// TODO @hw每个组件下的 index.ts 要不都删除,统一在 mp/components/index.ts 暴露就好了?

View File

@@ -28,12 +28,9 @@ const emit = defineEmits<{
(e: 'update:modelValue', v: Reply): void;
}>();
// TODO @hw直接用 ElMessage
const message = ElMessage;
const accessStore = useAccessStore();
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
// TODO @hw看看要不要和 antd 保持一致的风格;
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
const reply = computed<Reply>({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
@@ -56,7 +53,7 @@ function beforeImageUpload(rawFile: UploadRawFile) {
/** 上传成功 */
function onUploadSuccess(res: any) {
if (res.code !== 0) {
message.error(`上传出错:${res.msg}`);
ElMessage.error(`上传出错:${res.msg}`);
return false;
}
@@ -136,6 +133,7 @@ function selectMaterial(item: any) {
:span="12"
class="float-right h-[160px] w-[49.5%] border border-[rgb(234,234,234)] py-[50px]"
>
{{ uploadData }}
<ElUpload
:action="UPLOAD_URL"
:headers="HEADERS"
@@ -158,4 +156,4 @@ function selectMaterial(item: any) {
</ElCol>
</ElRow>
</div>
</template>
</template>

View File

@@ -1,7 +1,6 @@
<script lang="ts" setup>
import type { UploadRawFile } from 'element-plus';
// TODO @hw类似 tab-image.vue 的建议
import type { Reply } from './types';
import { computed, reactive, ref } from 'vue';
@@ -30,8 +29,6 @@ const emit = defineEmits<{
(e: 'update:modelValue', v: Reply): void;
}>();
const message = ElMessage;
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
const reply = computed<Reply>({
@@ -56,7 +53,7 @@ function beforeImageUpload(rawFile: UploadRawFile) {
/** 上传成功 */
function onUploadSuccess(res: any) {
if (res.code !== 0) {
message.error(`上传出错:${res.msg}`);
ElMessage.error(`上传出错:${res.msg}`);
return false;
}

View File

@@ -3,6 +3,7 @@ import type { Reply } from './types';
import { computed, ref } from 'vue';
import { NewsType } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
import { ElButton, ElCol, ElDialog, ElRow } from 'element-plus';
@@ -10,8 +11,6 @@ import { ElButton, ElCol, ElDialog, ElRow } from 'element-plus';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
import News from '#/views/mp/components/wx-news/wx-news.vue';
import { NewsType } from '../wx-material-select/types';
defineOptions({ name: 'TabNews' });
const props = defineProps<{

View File

@@ -31,18 +31,13 @@ const props = defineProps<{
const emit = defineEmits<{
(e: 'update:modelValue', v: Reply): void;
}>();
// TODO @hw还是用 ElMessage
const message = ElMessage;
const accessStore = useAccessStore();
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
// TODO @hw这里 antd 和 ele 有差异,要统一么?
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
const reply = computed<Reply>({
get: () => props.modelValue,
// TODO @hw这里 antd 和 ele 有差异,要统一么?
set: (val: Reply) => emit('update:modelValue', val),
set: (val) => emit('update:modelValue', val),
});
const showDialog = ref(false);
@@ -62,7 +57,7 @@ function beforeVideoUpload(rawFile: UploadRawFile) {
/** 上传成功 */
function onUploadSuccess(res: any) {
if (res.code !== 0) {
message.error(`上传出错:${res.msg}`);
ElMessage.error(`上传出错:${res.msg}`);
return false;
}

View File

@@ -31,16 +31,13 @@ const emit = defineEmits<{
(e: 'update:modelValue', v: Reply): void;
}>();
// TODO @hw用 ElMessage
const message = ElMessage;
const accessStore = useAccessStore();
const UPLOAD_URL = `${import.meta.env.VITE_BASE_URL}/admin-api/mp/material/upload-temporary`;
// TODO @hwantd 和 ele 写法的统一;
const HEADERS = { Authorization: `Bearer ${useAccessStore().accessToken}` };
const HEADERS = { Authorization: `Bearer ${accessStore.accessToken}` };
const reply = computed<Reply>({
get: () => props.modelValue,
// TODO @hw这里要和 antd 统一么?还是 ele 和它统一
set: (val: Reply) => emit('update:modelValue', val),
set: (val) => emit('update:modelValue', val),
});
const showDialog = ref(false);
@@ -60,7 +57,7 @@ function beforeVoiceUpload(rawFile: UploadRawFile) {
/** 上传成功 */
function onUploadSuccess(res: any) {
if (res.code !== 0) {
message.error(`上传出错:${res.msg}`);
ElMessage.error(`上传出错:${res.msg}`);
return false;
}
@@ -158,4 +155,4 @@ function selectMaterial(item: Reply) {
</ElCol>
</ElRow>
</div>
</template>
</template>

View File

@@ -1,38 +1,28 @@
import type { Ref } from 'vue';
import type { ReplyType } from '@vben/constants';
import { unref } from 'vue';
// TODO @hw和 antd 风格,保持一致;
enum ReplyType {
Image = 'image',
Music = 'music',
News = 'news',
Text = 'text',
Video = 'video',
Voice = 'voice',
}
interface _Reply {
export interface Reply {
accountId: number;
type: ReplyType;
name?: null | string;
articles?: any[];
content?: null | string;
mediaId?: null | string;
url?: null | string;
title?: null | string;
description?: null | string;
thumbMediaId?: null | string;
thumbMediaUrl?: null | string;
musicUrl?: null | string;
hqMusicUrl?: null | string;
introduction?: null | string;
articles?: any[];
mediaId?: null | string;
musicUrl?: null | string;
name?: null | string;
thumbMediaId?: null | string;
thumbMediaUrl?: null | string;
title?: null | string;
type: ReplyType;
url?: null | string;
}
type Reply = _Reply; // Partial<_Reply>
/** 利用旧的 reply[accountId, type] 初始化新的 Reply */
const createEmptyReply = (old: Ref<Reply> | Reply): Reply => {
export const createEmptyReply = (old: Ref<Reply> | Reply): Reply => {
return {
accountId: unref(old).accountId,
type: unref(old).type,
@@ -50,5 +40,3 @@ const createEmptyReply = (old: Ref<Reply> | Reply): Reply => {
articles: [],
};
};
export { createEmptyReply, type Reply, ReplyType };

View File

@@ -1,38 +1,36 @@
<script lang="ts" setup>
import type { Reply } from './types';
import { computed } from 'vue';
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 { NewsType } from '../wx-material-select/types';
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, ReplyType } from './types';
import { createEmptyReply } from './types';
/** 消息回复选择 */
defineOptions({ name: 'WxReplySelect' });
const props = withDefaults(
defineProps<{
modelValue: Reply | undefined;
newsType?: NewsType;
}>(),
{
newsType: () => NewsType.Published,
},
);
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,
@@ -43,6 +41,67 @@ const reply = computed<Reply>({
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);
@@ -105,4 +164,4 @@ defineExpose({
<TabMusic v-model="reply" />
</ElTabPane>
</ElTabs>
</template>
</template>