feat: 消息迁移
This commit is contained in:
116
apps/web-antd/src/views/mp/components/wx-msg/card.scss
Normal file
116
apps/web-antd/src/views/mp/components/wx-msg/card.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
.avue-card {
|
||||
&__item {
|
||||
box-sizing: border-box;
|
||||
height: 200px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
font-feature-settings: 'tnum';
|
||||
font-variant: tabular-nums;
|
||||
line-height: 1.5;
|
||||
color: rgb(0 0 0 / 65%);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
|
||||
&:hover {
|
||||
border-color: rgb(0 0 0 / 9%);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 9%);
|
||||
}
|
||||
|
||||
&--add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
color: rgb(0 0 0 / 45%);
|
||||
background-color: #fff;
|
||||
border: 1px dashed #000;
|
||||
border-color: #d9d9d9;
|
||||
border-radius: 2px;
|
||||
|
||||
i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
background-color: #fff;
|
||||
border-color: #40a9ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
&__detail {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-right: 12px;
|
||||
overflow: hidden;
|
||||
border-radius: 48px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
color: rgb(0 0 0 / 85%);
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: -webkit-box;
|
||||
height: 64px;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 3;
|
||||
color: rgb(0 0 0 / 45%);
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
color: rgb(0 0 0 / 45%);
|
||||
text-align: center;
|
||||
background: #f7f9fa;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** joolun 额外加的 */
|
||||
.avue-comment__main {
|
||||
flex: unset !important;
|
||||
margin: 0 8px !important;
|
||||
border-radius: 5px !important;
|
||||
}
|
||||
|
||||
.avue-comment__header {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.avue-comment__body {
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
109
apps/web-antd/src/views/mp/components/wx-msg/comment.scss
Normal file
109
apps/web-antd/src/views/mp/components/wx-msg/comment.scss
Normal file
@@ -0,0 +1,109 @@
|
||||
/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */
|
||||
.avue-comment {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30px;
|
||||
|
||||
&--reverse {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.avue-comment__main {
|
||||
&::before,
|
||||
&::after {
|
||||
right: -8px;
|
||||
left: auto;
|
||||
border-width: 8px 0 8px 8px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
border-left-color: #dedede;
|
||||
}
|
||||
|
||||
&::after {
|
||||
margin-right: 1px;
|
||||
margin-left: auto;
|
||||
border-left-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
box-sizing: border-box;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
vertical-align: middle;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 5px 15px;
|
||||
background: #f8f8f8;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
&__author {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&__main {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
margin: 0 20px;
|
||||
border: 1px solid #dedede;
|
||||
border-radius: 2px;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 100%;
|
||||
left: -8px;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
content: ' ';
|
||||
border-color: transparent;
|
||||
border-style: solid solid outset;
|
||||
border-width: 8px 8px 8px 0;
|
||||
}
|
||||
|
||||
&::before {
|
||||
z-index: 1;
|
||||
border-right-color: #dedede;
|
||||
}
|
||||
|
||||
&::after {
|
||||
z-index: 2;
|
||||
margin-left: 1px;
|
||||
border-right-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 15px;
|
||||
overflow: hidden;
|
||||
font-family:
|
||||
'Segoe UI', 'Lucida Grande', Helvetica, Arial, 'Microsoft YaHei',
|
||||
FreeSans, Arimo, 'Droid Sans', 'wenquanyi micro hei', 'Hiragino Sans GB',
|
||||
'Hiragino Sans GB W3', FontAwesome, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 1px 0 1px 15px;
|
||||
margin: 0;
|
||||
font-family:
|
||||
Georgia, 'Times New Roman', Times, Kai, 'Kaiti SC', KaiTi, BiauKai,
|
||||
FontAwesome, serif;
|
||||
border-left: 4px solid #ddd;
|
||||
}
|
||||
}
|
||||
100
apps/web-antd/src/views/mp/components/wx-msg/components/Msg.vue
Normal file
100
apps/web-antd/src/views/mp/components/wx-msg/components/Msg.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts" setup>
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import WxLocation from '#/views/mp/components/wx-location';
|
||||
import WxMusic from '#/views/mp/components/wx-music';
|
||||
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';
|
||||
|
||||
import { MsgType } from '../types';
|
||||
import MsgEvent from './MsgEvent.vue';
|
||||
|
||||
defineOptions({ name: 'Msg' });
|
||||
|
||||
defineProps<{
|
||||
item: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<MsgEvent v-if="item.type === MsgType.Event" :item="item" />
|
||||
|
||||
<div v-else-if="item.type === MsgType.Text">{{ item.content }}</div>
|
||||
|
||||
<div v-else-if="item.type === MsgType.Voice">
|
||||
<WxVoicePlayer :url="item.mediaUrl" :content="item.recognition" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="item.type === MsgType.Image">
|
||||
<a :href="item.mediaUrl" target="_blank">
|
||||
<img :src="item.mediaUrl" style="width: 100px" alt="图片消息" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="item.type === MsgType.Video || item.type === 'shortvideo'"
|
||||
class="text-center"
|
||||
>
|
||||
<WxVideoPlayer :url="item.mediaUrl" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="item.type === MsgType.Link" class="link-card">
|
||||
<a :href="item.url" target="_blank" class="text-success no-underline">
|
||||
<div class="link-title">
|
||||
<IconifyIcon icon="mdi:link" class="mr-1" />
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</a>
|
||||
<div class="link-description">{{ item.description }}</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="item.type === MsgType.Location">
|
||||
<WxLocation
|
||||
:label="item.label"
|
||||
:location-y="item.locationY"
|
||||
:location-x="item.locationX"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="item.type === MsgType.News" class="news-wrapper">
|
||||
<WxNews :articles="item.articles" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="item.type === MsgType.Music">
|
||||
<WxMusic
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:thumb-media-url="item.thumbMediaUrl"
|
||||
:music-url="item.musicUrl"
|
||||
:hq-music-url="item.hqMusicUrl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.link-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.link-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.link-description {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.news-wrapper {
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script lang="ts" setup>
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'MsgEvent' });
|
||||
|
||||
defineProps<{
|
||||
item: any;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="item.event === 'subscribe'">
|
||||
<Tag color="success">关注</Tag>
|
||||
</div>
|
||||
<div v-else-if="item.event === 'unsubscribe'">
|
||||
<Tag color="error">取消关注</Tag>
|
||||
</div>
|
||||
<div v-else-if="item.event === 'CLICK'">
|
||||
<Tag>点击菜单</Tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.event === 'VIEW'">
|
||||
<Tag>点击菜单链接</Tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.event === 'scancode_waitmsg'">
|
||||
<Tag>扫码结果</Tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.event === 'scancode_push'">
|
||||
<Tag>扫码结果</Tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.event === 'pic_sysphoto'">
|
||||
<Tag>系统拍照发图</Tag>
|
||||
</div>
|
||||
<div v-else-if="item.event === 'pic_photo_or_album'">
|
||||
<Tag>拍照或者相册</Tag>
|
||||
</div>
|
||||
<div v-else-if="item.event === 'pic_weixin'">
|
||||
<Tag>微信相册</Tag>
|
||||
</div>
|
||||
<div v-else-if="item.event === 'location_select'">
|
||||
<Tag>选择地理位置</Tag>
|
||||
</div>
|
||||
<div v-else>
|
||||
<Tag color="error">未知事件类型</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,69 @@
|
||||
<script lang="ts" setup>
|
||||
import type { User } from '../types';
|
||||
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import Msg from './Msg.vue';
|
||||
|
||||
defineOptions({ name: 'MsgList' });
|
||||
|
||||
const props = defineProps<{
|
||||
accountId: number;
|
||||
list: any[];
|
||||
user: User;
|
||||
}>();
|
||||
const SendFrom = {
|
||||
MpBot: 2,
|
||||
User: 1,
|
||||
} as const;
|
||||
|
||||
const getAvatar = (sendFrom: number) =>
|
||||
sendFrom === SendFrom.User
|
||||
? props.user.avatar
|
||||
: preferences.app.defaultAvatar;
|
||||
|
||||
const getNickname = (sendFrom: SendFrom) =>
|
||||
sendFrom === SendFrom.User ? props.user.nickname : '公众号';
|
||||
</script>
|
||||
<template>
|
||||
<div class="execution" v-for="item in props.list" :key="item.id">
|
||||
<div
|
||||
class="avue-comment"
|
||||
:class="{ 'avue-comment--reverse': item.sendFrom === SendFrom.MpBot }"
|
||||
>
|
||||
<div class="avatar-div">
|
||||
<img :src="getAvatar(item.sendFrom)" class="avue-comment__avatar" />
|
||||
<div class="avue-comment__author">
|
||||
{{ getNickname(item.sendFrom) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="avue-comment__main">
|
||||
<div class="avue-comment__header">
|
||||
<div class="avue-comment__create_time">
|
||||
{{ formatDateTime(item.createTime) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="avue-comment__body"
|
||||
:style="
|
||||
item.sendFrom === SendFrom.MpBot ? 'background: #6BED72;' : ''
|
||||
"
|
||||
>
|
||||
<Msg :item="item" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
||||
@import url('../comment.scss');
|
||||
@import url('../card.scss');
|
||||
|
||||
.avatar-div {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
3
apps/web-antd/src/views/mp/components/wx-msg/index.ts
Normal file
3
apps/web-antd/src/views/mp/components/wx-msg/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default } from './main.vue';
|
||||
|
||||
export { MsgType } from './types';
|
||||
222
apps/web-antd/src/views/mp/components/wx-msg/main.vue
Normal file
222
apps/web-antd/src/views/mp/components/wx-msg/main.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<script lang="ts" setup>
|
||||
import type { User } from './types';
|
||||
|
||||
import { nextTick, onMounted, reactive, ref, unref } from 'vue';
|
||||
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { Button, message, Spin } from 'ant-design-vue';
|
||||
|
||||
import { getMessagePage, sendMessage } from '#/api/mp/message';
|
||||
import { getUser } from '#/api/mp/user';
|
||||
import WxReplySelect from '#/views/mp/components/wx-reply';
|
||||
|
||||
import MsgList from './components/MsgList.vue';
|
||||
|
||||
defineOptions({ name: 'WxMsg' });
|
||||
|
||||
const props = defineProps<{
|
||||
userId: number;
|
||||
}>();
|
||||
|
||||
const accountId = ref(-1); // 公众号ID,需要通过userId初始化
|
||||
const loading = ref(false); // 消息列表是否正在加载中
|
||||
const hasMore = ref(true); // 是否可以加载更多
|
||||
const list = ref<any[]>([]); // 消息列表
|
||||
const queryParams = reactive({
|
||||
accountId,
|
||||
pageNo: 1, // 当前页数
|
||||
pageSize: 14, // 每页显示多少条
|
||||
});
|
||||
|
||||
// 由于微信不再提供昵称,直接使用"用户"展示
|
||||
const user: User = reactive({
|
||||
accountId, // 公众号账号编号
|
||||
avatar: preferences.app.defaultAvatar,
|
||||
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,用于滚动到底部
|
||||
|
||||
/** 完成加载 */
|
||||
onMounted(async () => {
|
||||
const data = await getUser(props.userId);
|
||||
user.nickname = data.nickname?.length > 0 ? data.nickname : user.nickname;
|
||||
user.avatar = data.avatar?.length > 0 ? data.avatar : user.avatar;
|
||||
accountId.value = data.accountId;
|
||||
reply.value.accountId = data.accountId;
|
||||
|
||||
refreshChange();
|
||||
});
|
||||
|
||||
// 执行发送
|
||||
const sendMsg = async () => {
|
||||
if (!unref(reply)) {
|
||||
return;
|
||||
}
|
||||
// 公众号限制:客服消息,公众号只允许发送一条
|
||||
if (
|
||||
reply.value.type === 'news' &&
|
||||
reply.value.articles &&
|
||||
reply.value.articles.length > 1
|
||||
) {
|
||||
reply.value.articles = [reply.value.articles[0]];
|
||||
message.success('图文消息条数限制在 1 条以内,已默认发送第一条');
|
||||
}
|
||||
|
||||
const data = await sendMessage({
|
||||
...reply.value,
|
||||
userId: props.userId,
|
||||
} as any);
|
||||
sendLoading.value = false;
|
||||
|
||||
list.value = [...list.value, data];
|
||||
await scrollToBottom();
|
||||
|
||||
// 发送后清空数据
|
||||
replySelectRef.value?.clear();
|
||||
};
|
||||
|
||||
const loadMore = () => {
|
||||
queryParams.pageNo++;
|
||||
getPage(queryParams, null);
|
||||
};
|
||||
|
||||
const getPage = async (page: any, params: any = null) => {
|
||||
loading.value = true;
|
||||
const dataTemp = await getMessagePage(
|
||||
Object.assign(
|
||||
{
|
||||
accountId: page.accountId,
|
||||
pageNo: page.pageNo,
|
||||
pageSize: page.pageSize,
|
||||
userId: props.userId,
|
||||
},
|
||||
params,
|
||||
),
|
||||
);
|
||||
|
||||
const scrollHeight = msgDivRef.value?.scrollHeight ?? 0;
|
||||
// 处理数据
|
||||
const data = dataTemp.list.reverse();
|
||||
list.value = [...data, ...list.value];
|
||||
loading.value = false;
|
||||
if (data.length < queryParams.pageSize || data.length === 0) {
|
||||
hasMore.value = false;
|
||||
}
|
||||
queryParams.pageNo = page.pageNo;
|
||||
queryParams.pageSize = page.pageSize;
|
||||
// 滚动到原来的位置
|
||||
if (queryParams.pageNo === 1) {
|
||||
// 定位到消息底部
|
||||
await scrollToBottom();
|
||||
} else if (data.length > 0) {
|
||||
// 定位滚动条
|
||||
await nextTick();
|
||||
if (scrollHeight !== 0 && msgDivRef.value) {
|
||||
msgDivRef.value.scrollTop =
|
||||
msgDivRef.value.scrollHeight - scrollHeight - 100;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const refreshChange = () => {
|
||||
getPage(queryParams);
|
||||
};
|
||||
|
||||
/** 定位到消息底部 */
|
||||
const scrollToBottom = async () => {
|
||||
await nextTick();
|
||||
if (msgDivRef.value) {
|
||||
msgDivRef.value.scrollTop = msgDivRef.value.scrollHeight;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wx-msg-container">
|
||||
<div ref="msgDivRef" class="msg-div">
|
||||
<!-- 加载更多 -->
|
||||
<Spin :spinning="loading" />
|
||||
<div v-if="!loading">
|
||||
<div v-if="hasMore" class="load-more-btn" @click="loadMore">
|
||||
<span>点击加载更多</span>
|
||||
</div>
|
||||
<div v-else class="load-more-btn disabled">
|
||||
<span>没有更多了</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<MsgList :list="list" :account-id="accountId" :user="user" />
|
||||
</div>
|
||||
|
||||
<div class="msg-send">
|
||||
<Spin :spinning="sendLoading">
|
||||
<WxReplySelect ref="replySelectRef" v-model="reply" />
|
||||
<Button type="primary" class="send-but" @click="sendMsg">
|
||||
发送(S)
|
||||
</Button>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wx-msg-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.msg-div {
|
||||
flex: 1;
|
||||
height: 50vh;
|
||||
margin: 0 10px;
|
||||
overflow: auto;
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
||||
.load-more-btn {
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: #909399;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg-send {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.send-but {
|
||||
float: right;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
17
apps/web-antd/src/views/mp/components/wx-msg/types.ts
Normal file
17
apps/web-antd/src/views/mp/components/wx-msg/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export enum MsgType {
|
||||
Event = 'event',
|
||||
Image = 'image',
|
||||
Link = 'link',
|
||||
Location = 'location',
|
||||
Music = 'music',
|
||||
News = 'news',
|
||||
Text = 'text',
|
||||
Video = 'video',
|
||||
Voice = 'voice',
|
||||
}
|
||||
|
||||
export interface User {
|
||||
accountId: number;
|
||||
avatar: string;
|
||||
nickname: string;
|
||||
}
|
||||
Reference in New Issue
Block a user