This commit is contained in:
xingyu4j
2025-11-13 14:11:01 +08:00
26 changed files with 90 additions and 55 deletions

View File

@@ -17,6 +17,7 @@ export namespace MpAccountApi {
createTime?: Date;
}
// TODO @dylan这个直接使用 Account简化一点
export interface AccountSimple {
id: number;
name: string;

View File

@@ -1 +1,2 @@
// TODO @dylanyudao-ui-admin-vben-v5/apps/web-antd/src/views/mp/components 要不加个 index.ts统一 export 所有的组件
export { default as WxAccountSelect } from './main.vue';

View File

@@ -12,23 +12,19 @@ import { getSimpleAccountList } from '#/api/mp/account';
defineOptions({ name: 'WxAccountSelect' });
// 定义事件
const emit = defineEmits<{
(e: 'change', id: number, name: string): void;
}>();
// 消息弹窗
const { push } = useRouter();
// 当前选中的公众号
const account: MpAccountApi.Account = reactive({
id: -1,
name: '',
});
}); // 当前选中的公众号
const accountList = ref<MpAccountApi.Account[]>([]); // 公众号列表
// 公众号列表
const accountList = ref<MpAccountApi.Account[]>([]);
// 查询公众号列表
/** 查询公众号列表 */
async function handleQuery() {
accountList.value = await getSimpleAccountList();
if (accountList.value.length === 0) {
@@ -46,7 +42,7 @@ async function handleQuery() {
}
}
// 切换选中公众号
/** 切换选中公众号 */
function onChanged(id: SelectValue) {
const found = accountList.value.find((v) => v.id === id);
if (found) {
@@ -55,7 +51,7 @@ function onChanged(id: SelectValue) {
}
}
// 初始化
/** 初始化 */
onMounted(handleQuery);
</script>

View File

@@ -7,6 +7,7 @@ import { Col, Row } from 'ant-design-vue';
defineOptions({ name: 'WxLocation' });
// TODO @dylanapps/web-antd/src/views/mall/trade/delivery/pickUpStore/modules/form.vue 参考这个,从后端拿 key 哈
const props = withDefaults(
defineProps<{
label: string;

View File

@@ -35,20 +35,17 @@ const emit = defineEmits<{
(e: 'selectMaterial', item: any): void;
}>();
// 遮罩层
const loading = ref(false);
// 总条数
const total = ref(0);
// 数据列表
const list = ref<any[]>([]);
// 查询参数
const loading = ref(false); // 遮罩层
const total = ref(0); // 总条数
const list = ref<any[]>([]); // 数据列表
const queryParams = reactive({
accountId: props.accountId,
pageNo: 1,
pageSize: 10,
});
}); // 查询参数
const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
// TODO @dylanany 有 linter 告警;看看别的模块哈
{
field: 'mediaId',
title: '编号',
@@ -83,6 +80,7 @@ const voiceGridColumns: VxeTableGridOptions<any>['columns'] = [
];
const videoGridColumns: VxeTableGridOptions<any>['columns'] = [
// TODO @dylanany 有 linter 告警;看看别的模块哈
{
field: 'mediaId',
title: '编号',
@@ -139,9 +137,11 @@ const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
ajax: {
query: async ({ page }, { accountId }) => {
const finalAccountId = accountId ?? queryParams.accountId;
// TODO @dylan 这里简化成 !finalAccountId 是不是可以哈。
if (finalAccountId === undefined || finalAccountId === null) {
return { list: [], total: 0 };
}
// TODO @dylan不要带 MpMaterialApi
return await MpMaterialApi.getMaterialPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
@@ -158,7 +158,7 @@ const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<any>,
} as VxeTableGridOptions<any>, // TODO @dylan这里有 linter 告警;看看别的模块哈
});
const [VideoGrid, videoGridApi] = useVbenVxeGrid({
@@ -383,6 +383,7 @@ watch(
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
@media (width >= 992px) and (width <= 1300px) {
.waterfall {
column-count: 3;

View File

@@ -10,6 +10,8 @@ import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
import { MsgType } from '../types';
import MsgEvent from './MsgEvent.vue';
// TODO @dylanvue 组件名小写 + 中划线
defineOptions({ name: 'Msg' });
defineProps<{

View File

@@ -1,6 +1,8 @@
<script lang="ts" setup>
import { Tag } from 'ant-design-vue';
// TODO @dylanvue 组件名小写 + 中划线
defineOptions({ name: 'MsgEvent' });
defineProps<{

View File

@@ -6,6 +6,8 @@ import { formatDateTime } from '@vben/utils';
import Msg from './Msg.vue';
// TODO @dylanvue 组件名小写 + 中划线
defineOptions({ name: 'MsgList' });
const props = defineProps<{
@@ -24,6 +26,7 @@ function getAvatar(sendFrom: number) {
: preferences.app.defaultAvatar;
}
// TODO @dylanSendFrom 告警;
function getNickname(sendFrom: SendFrom) {
return sendFrom === SendFrom.User ? props.user.nickname : '公众号';
}
@@ -61,6 +64,8 @@ function getNickname(sendFrom: SendFrom) {
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
/** TODO @dylan看看有没适合 tindwind 的哈。 */
@import url('../comment.scss');
@import url('../card.scss');

View File

@@ -29,21 +29,19 @@ const queryParams = reactive({
pageSize: 14, // 每页显示多少条
});
// 由于微信不再提供昵称,直接使用"用户"展示
const user: User = reactive({
accountId, // 公众号账号编号
avatar: preferences.app.defaultAvatar,
nickname: '用户',
nickname: '用户', // 由于微信不再提供昵称,直接使用"用户"展示
});
// ========= 消息发送 =========
const sendLoading = ref(false); // 发送消息是否加载中
// 微信发送消息
const reply = ref<any>({
accountId: -1,
articles: [],
type: 'text',
});
}); // 微信发送消息
const replySelectRef = ref<InstanceType<typeof WxReplySelect> | null>(null); // WxReplySelect组件ref用于消息发送成功后清除内容
const msgDivRef = ref<HTMLDivElement | null>(null); // 消息显示窗口ref用于滚动到底部
@@ -59,7 +57,7 @@ onMounted(async () => {
refreshChange();
});
// 执行发送
/** 执行发送 */
async function sendMsg() {
if (!unref(reply)) {
return;

View File

@@ -44,6 +44,8 @@ defineExpose({
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.music-card {
display: flex;
padding: 10px;

View File

@@ -52,6 +52,8 @@ defineExpose({
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.news-home {
width: 100%;
margin: auto;

View File

@@ -13,6 +13,8 @@ import { Button, Col, message, Modal, Row, Upload } from 'ant-design-vue';
import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
// TODO @dylan文件名的大小写
defineOptions({ name: 'TabImage' });
const props = defineProps<{
@@ -45,7 +47,7 @@ function beforeImageUpload(rawFile: UploadRawFile) {
return useBeforeUpload(UploadType.Image, 2)(rawFile);
}
// 自定义上传请求
/** 自定义上传请求 */
async function customRequest(options: any) {
const { file, onSuccess, onError } = options;
@@ -165,6 +167,7 @@ function selectMaterial(item: any) {
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.select-item {
width: 280px;
padding: 10px;

View File

@@ -21,6 +21,8 @@ import {
import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
// TODO @dylan文件名的大小写
defineOptions({ name: 'TabMusic' });
const props = defineProps<{
@@ -53,7 +55,7 @@ function beforeImageUpload(rawFile: UploadRawFile) {
return useBeforeUpload(UploadType.Image, 2)(rawFile);
}
// 自定义上传请求
/** 自定义上传请求 */
async function customRequest(options: any) {
const { file, onSuccess, onError } = options;
@@ -182,6 +184,7 @@ function selectMaterial(item: any) {
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.thumb-container {
display: flex;
flex-direction: column;

View File

@@ -12,6 +12,8 @@ import { WxNews } from '#/views/mp/components/wx-news';
import { NewsType } from './types';
// TODO @dylan文件名的大小写
defineOptions({ name: 'TabNews' });
const props = defineProps<{
@@ -94,6 +96,7 @@ function onDelete() {
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.select-item {
width: 280px;
padding: 10px;

View File

@@ -3,6 +3,8 @@ import { computed } from 'vue';
import { Textarea } from 'ant-design-vue';
// TODO @dylan文件名的大小写
const props = defineProps<{
modelValue?: null | string;
}>();

View File

@@ -22,6 +22,8 @@ import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import { WxVideoPlayer } from '#/views/mp/components/wx-video-play';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
// TODO @dylan文件名的大小写
defineOptions({ name: 'TabVideo' });
const props = defineProps<{
@@ -54,7 +56,7 @@ function beforeVideoUpload(rawFile: UploadRawFile) {
return useBeforeUpload(UploadType.Video, 10)(rawFile);
}
// 自定义上传请求
/** 自定义上传请求 */
async function customRequest(options: any) {
const { file, onSuccess, onError } = options;
@@ -184,6 +186,7 @@ function selectMaterial(item: any) {
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.ope-row {
width: 100%;
padding-top: 10px;

View File

@@ -14,6 +14,8 @@ import { WxMaterialSelect } from '#/views/mp/components/wx-material-select';
import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
import { UploadType, useBeforeUpload } from '#/views/mp/hooks/useUpload';
// TODO @dylan文件名的大小写
defineOptions({ name: 'TabVoice' });
const props = defineProps<{
@@ -46,7 +48,7 @@ function beforeVoiceUpload(rawFile: UploadRawFile) {
return useBeforeUpload(UploadType.Voice, 10)(rawFile);
}
// 自定义上传请求
/** 自定义上传请求 */
async function customRequest(options: any) {
const { file, onSuccess, onError } = options;
@@ -166,6 +168,7 @@ function selectMaterial(item: Reply) {
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.select-item {
padding: 10px;
margin: 0 auto 10px;

View File

@@ -37,14 +37,14 @@ interface Props {
modelValue: Reply;
newsType?: NewsType;
}
const reply = computed<Reply>({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
// 作为多个标签保存各自Reply的缓存
const tabCache = new Map<ReplyType, Reply>();
// 采用独立的ref来保存当前tab避免在watch标签变化对reply进行赋值会产生了循环调用
const currentTab = ref<ReplyType>(props.modelValue.type || ReplyType.Text);
const tabCache = new Map<ReplyType, Reply>(); // 作为多个标签保存各自 Reply 的缓存
const currentTab = ref<ReplyType>(props.modelValue.type || ReplyType.Text); // 采用独立的 ref 来保存当前 tab避免在 watch 标签变化,对 reply 进行赋值会产生了循环调用
watch(
currentTab,
@@ -153,6 +153,7 @@ defineExpose({
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.select-item {
width: 280px;
padding: 10px;

View File

@@ -4,8 +4,7 @@ import { ref } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { Tag } from 'ant-design-vue';
// 因为微信语音是 amr 格式,所以需要用到 amr 解码器https://www.npmjs.com/package/benz-amr-recorder
import BenzAMRRecorder from 'benz-amr-recorder';
import BenzAMRRecorder from 'benz-amr-recorder'; // 因为微信语音是 amr 格式,所以需要用到 amr 解码器https://www.npmjs.com/package/benz-amr-recorder
defineOptions({ name: 'WxVoicePlayer' });
@@ -81,6 +80,7 @@ function amrStop() {
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
.wx-voice-div {
display: flex;
flex-direction: column;

View File

@@ -4,6 +4,8 @@ import { IconifyIcon } from '@vben/icons';
import { Button, Spin } from 'ant-design-vue';
// TODO @dylanvue 组件名小写 + 中划线
const props = defineProps<{
list: any[];
loading: boolean;
@@ -43,6 +45,8 @@ const { hasAccessByCodes } = useAccess();
</template>
<style lang="scss" scoped>
/** TODO @dylan看看有没适合 tindwind 的哈。 */
@media (width >= 992px) and (width <= 1300px) {
.waterfall {
column-count: 3;

View File

@@ -59,12 +59,8 @@ const uploadFormRef = ref<FormInstance | null>(null);
const uploadVideoRef = ref<any>(null);
async function submitVideo() {
try {
await uploadFormRef.value?.validate();
uploadVideoRef.value?.submit();
} catch {
// Validation failed
}
await uploadFormRef.value?.validate();
uploadVideoRef.value?.submit();
}
/** 自定义上传 */

View File

@@ -12,6 +12,8 @@ import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { WxVideoPlayer } from '#/views/mp/components/wx-video-play';
// TODO @dylanvue 组件名小写 + 中划线
const props = defineProps<{
list: any[];
loading: boolean;
@@ -85,7 +87,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true,
},
showOverflow: 'tooltip',
} as VxeTableGridOptions<any>,
} as VxeTableGridOptions<any>, // TODO @dylan这里有个告警哈
});
function handleDownload(url: string) {
@@ -108,7 +110,7 @@ watch(
watch(
() => props.loading,
(loading: boolean) => {
gridApi.setLoading(!!loading);
gridApi.setLoading(loading);
},
{ immediate: true },
);

View File

@@ -12,6 +12,8 @@ import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
// TODO @dylanvue 组件名小写 + 中划线
const props = defineProps<{
list: any[];
loading: boolean;
@@ -73,7 +75,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true,
},
showOverflow: 'tooltip',
} as VxeTableGridOptions<any>,
} as VxeTableGridOptions<any>, // TODO @dylan这里有个告警哈
});
function handleDownload(url: string) {
@@ -96,7 +98,7 @@ watch(
watch(
() => props.loading,
(loading: boolean) => {
gridApi.setLoading(!!loading);
gridApi.setLoading(loading);
},
{ immediate: true },
);

View File

@@ -37,13 +37,12 @@ const total = ref(0); // 总条数
const accountId = ref(-1);
provide('accountId', accountId);
// 查询参数
const queryParams = reactive({
accountId,
pageNo: 1,
pageSize: 10,
permanent: true,
});
}); // 查询参数
const showCreateVideo = ref(false); // 是否新建视频的弹窗
/** 侦听公众号变化 */

View File

@@ -15,6 +15,8 @@ import { WxNews } from '#/views/mp/components/wx-news';
import { WxVideoPlayer } from '#/views/mp/components/wx-video-play';
import { WxVoicePlayer } from '#/views/mp/components/wx-voice-play';
// TODO @dylanvue 组件名小写 + 中划线
const props = withDefaults(
defineProps<{
list?: any[];
@@ -33,6 +35,7 @@ const emit = defineEmits<{
}>();
const columns: VxeTableGridOptions<any>['columns'] = [
// TODO @dylanany 有 linter 告警;看看别的模块哈
{
field: 'createTime',
title: '发送时间',
@@ -104,11 +107,6 @@ function updateGridData(data: any[]) {
}
}
onMounted(() => {
updateGridData(normalizeList(props.list));
gridApi.setLoading(!!props.loading);
});
watch(
() => props.list,
(list) => {
@@ -120,9 +118,15 @@ watch(
watch(
() => props.loading,
(loading) => {
gridApi.setLoading(!!loading);
gridApi.setLoading(loading);
},
);
/** 初始化 */
onMounted(() => {
updateGridData(normalizeList(props.list));
gridApi.setLoading(props.loading);
});
</script>
<template>

View File

@@ -30,7 +30,6 @@ const loading = ref(false);
const total = ref(0); // 数据的总页数
const list = ref<any[]>([]); // 当前页的列表数据
// 搜索参数
const queryParams = reactive<{
accountId: number;
createTime: [Dayjs, Dayjs] | undefined;
@@ -45,7 +44,7 @@ const queryParams = reactive<{
pageNo: 1,
pageSize: 10,
type: MsgType.Text,
});
}); // 搜索参数
const queryFormRef = ref(); // 搜索的表单
@@ -53,7 +52,7 @@ const queryFormRef = ref(); // 搜索的表单
const messageBoxVisible = ref(false);
const messageBoxUserId = ref(0);
/** 侦听accountId */
/** 侦听 accountId */
function onAccountChanged(id: number) {
queryParams.accountId = id;
queryParams.pageNo = 1;