1. 整合组件

2. 封装重复代码
pull/706/head
orzi 6 months ago
parent 164a2c0bc5
commit 50db7e5340

@ -1,168 +0,0 @@
<template>
<div class="contact-item">
<n-thing content-indented>
<template #avatar>
<n-avatar :size="54" :src="contact.avatar" />
</template>
<template #header>
<span class="nickname-wrap">
<router-link
@click.stop
class="username-link"
:to="{
name: 'user',
query: { s: contact.username },
}"
>
{{ contact.nickname }}
</router-link>
</span>
<span class="username-wrap"> @{{ contact.username }} </span>
<!-- <n-tag
v-if="contact.is_following"
class="top-tag" type="success" size="small" round>
</n-tag> -->
<div class="user-info">
<span class="info-item">
UID. {{ contact.user_id }}
</span>
<span class="info-item">
{{ formatDate(contact.created_on) }}&nbsp;
</span>
</div>
</template>
<template #header-extra>
<div class="item-header-extra">
<n-dropdown
placement="bottom-end"
trigger="click"
size="small"
:options="actionOpts"
@select="handleAction"
>
<n-button quaternary circle>
<template #icon>
<n-icon>
<more-horiz-filled />
</n-icon>
</template>
</n-button>
</n-dropdown>
</div>
</template>
</n-thing>
</div>
</template>
<script setup lang="ts">
import { h, computed } from 'vue';
import { NIcon, DropdownOption } from 'naive-ui';
import type { Component } from 'vue';
import { formatDate } from '@/utils/formatTime';
import { MoreHorizFilled } from '@vicons/material';
import { PaperPlaneOutline } from '@vicons/ionicons5';
const emit = defineEmits<{
(e: 'send-whisper', user: Item.UserInfo): void;
}>();
const renderIcon = (icon: Component) => {
return () => {
return h(NIcon, null, {
default: () => h(icon),
});
};
};
const props = withDefaults(
defineProps<{
contact: Item.ContactItemProps;
}>(),
{},
);
const actionOpts = computed(() => {
let options: DropdownOption[] = [
{
label: ' @' + props.contact.username,
key: 'whisper',
icon: renderIcon(PaperPlaneOutline),
},
];
return options;
});
const handleAction = (item: 'whisper') => {
switch (item) {
case 'whisper':
const user: Item.UserInfo = {
id: props.contact.user_id,
avatar: props.contact.avatar,
username: props.contact.username,
nickname: props.contact.nickname,
is_admin: false,
is_friend: true,
is_following: false,
created_on: 0,
follows: 0,
followings: 0,
status: 1,
};
emit('send-whisper', user);
break;
default:
break;
}
};
</script>
<style lang="less" scoped>
.contact-item {
width: 100%;
box-sizing: border-box;
padding-left: 16px;
padding-right: 16px;
padding-top: 12px;
padding-bottom: 12px;
&:hover {
background: #f7f9f9;
}
.nickname-wrap {
line-height: 16px;
font-size: 16px;
}
.username-wrap {
line-height: 16px;
font-size: 16px;
}
.top-tag {
transform: scale(0.75);
}
.user-info {
.info-item {
font-size: 14px;
line-height: 14px;
margin-right: 8px;
opacity: 0.75;
}
}
.item-header-extra {
display: flex;
align-items: center;
opacity: 0.75;
}
}
.dark {
.contact-item {
&:hover {
background: #18181c;
}
background-color: rgba(16, 16, 20, 0.75);
}
}
</style>

@ -1,5 +1,5 @@
<template>
<div class="follow-item">
<div class="user-card">
<n-thing content-indented>
<template #avatar>
<n-avatar :size="54" :src="contact.avatar" />
@ -17,9 +17,9 @@
{{ contact.nickname }}
</router-link>
</span>
<span class="username-wrap"> @{{ contact.username }} </span>
<span class="username-wrap"> @{{ contact.username }} </span>
<n-tag
v-if="contact.is_following"
v-if="showFollowingTag && contact.is_following"
class="top-tag" type="success" size="small" round>
</n-tag>
@ -62,11 +62,23 @@ import { NIcon, useDialog, DropdownOption } from 'naive-ui';
import { formatDate } from '@/utils/formatTime';
import { MoreHorizFilled } from '@vicons/material';
import { PaperPlaneOutline, BodyOutline, WalkOutline } from '@vicons/ionicons5';
import { Api } from '@/utils/request';
import UserAction from '@/composables/useUserAction';
const dialog = useDialog();
const props = withDefaults(
defineProps<{
contact: Item.ContactItemProps;
type?: 'contact' | 'follow';
}>(),
{
type: 'contact',
},
);
const showFollowingTag = computed(() => props.type === 'follow');
const enableFollowAction = computed(() => props.type === 'follow');
const emit = defineEmits<{
(e: 'send-whisper', user: Item.UserInfo): void;
(e: 'unfollow-success'): void;
@ -85,7 +97,6 @@ const handleFollowUser = () => {
UserAction.followAction(dialog, props.contact.user_id, props.contact.username, props.contact.is_following)
.then(_action => {
props.contact.is_following = _action;
// 如果是取消关注操作,触发事件
if (wasFollowing && !_action) {
emit('unfollow-success');
}
@ -95,13 +106,6 @@ const handleFollowUser = () => {
});
};
const props = withDefaults(
defineProps<{
contact: Item.ContactItemProps;
}>(),
{},
);
const actionOpts = computed(() => {
let options: DropdownOption[] = [
{
@ -110,19 +114,23 @@ const actionOpts = computed(() => {
icon: renderIcon(PaperPlaneOutline),
},
];
if (props.contact.is_following) {
options.push({
label: ' @' + props.contact.username,
key: 'unfollow',
icon: renderIcon(WalkOutline),
});
} else {
options.push({
label: ' @' + props.contact.username,
key: 'follow',
icon: renderIcon(BodyOutline),
});
if (enableFollowAction.value) {
if (props.contact.is_following) {
options.push({
label: ' @' + props.contact.username,
key: 'unfollow',
icon: renderIcon(WalkOutline),
});
} else {
options.push({
label: ' @' + props.contact.username,
key: 'follow',
icon: renderIcon(BodyOutline),
});
}
}
return options;
});
@ -155,13 +163,10 @@ const handleAction = (item: 'follow' | 'unfollow' | 'whisper') => {
</script>
<style lang="less" scoped>
.follow-item {
display: border-box;
.user-card {
width: 100%;
padding-left: 16px;
padding-right: 16px;
padding-top: 12px;
padding-bottom: 12px;
box-sizing: border-box;
padding: 12px 16px;
&:hover {
background: #f7f9f9;
@ -195,11 +200,11 @@ const handleAction = (item: 'follow' | 'unfollow' | 'whisper') => {
}
.dark {
.follow-item {
.user-card {
&:hover {
background: #18181c;
}
background-color: rgba(16, 16, 20, 0.75);
}
}
</style>
</style>

@ -12,38 +12,33 @@
</div>
<n-list-item class="list-item" v-for="contact in list" :key="contact.user_id">
<contact-item :contact="contact" @send-whisper="onSendWhisper" />
<user-card type="contact" :contact="contact" @send-whisper="onSendWhisper" />
</n-list-item>
</div>
<!-- -->
<whisper :show="showWhisper" :user="whisperReceiver" @success="whisperSuccess" />
</n-list>
<infinite-load-more
:total-page="totalPage"
:no-more="noMore"
complete-text="没有更多好友了"
@load-more="nextPage"
/>
</div>
<n-space v-if="totalPage > 0" justify="center">
<InfiniteLoading class="load-more" :slots="{ complete: '没有更多好友了', error: '加载出错' }" @infinite="nextPage">
<template #spinner>
<div class="load-more-wrap">
<n-spin :size="14" v-if="!noMore" />
<span class="load-more-spinner">{{ noMore ? '' : '' }}</span>
</div>
</template>
</InfiniteLoading>
</n-space>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import InfiniteLoading from 'v3-infinite-loading';
import { useRoute } from 'vue-router';
import { Api } from '@/utils/request';
import { usePagination } from '@/composables/usePagination';
import InfiniteLoadMore from '@/components/infinite-load-more.vue';
import UserCard from '@/components/user-card.vue';
const route = useRoute();
const loading = ref(false);
const noMore = ref(false);
const { loading, noMore, page, pageSize, totalPage } = usePagination(20);
const list = ref<Item.ContactItemProps[]>([]);
const page = ref(+(route.query.p as string) || 1);
const pageSize = ref(20);
const totalPage = ref(0);
const showWhisper = ref(false);
const whisperReceiver = ref<Item.UserInfo>({
id: 0,
@ -59,6 +54,9 @@ const whisperReceiver = ref<Item.UserInfo>({
status: 1,
});
// 初始化页码
page.value = +(route.query.p as string) || 1;
const onSendWhisper = (user: Item.UserInfo) => {
whisperReceiver.value = user;
showWhisper.value = true;
@ -117,22 +115,6 @@ const loadContacts = (scrollToBottom: boolean = false) => {
</script>
<style lang="less" scoped>
.load-more {
margin: 20px;
.load-more-wrap {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 14px;
.load-more-spinner {
font-size: 14px;
opacity: 0.65;
}
}
}
.dark {
.main-content-wrap, .empty-wrap, .skeleton-wrap {
background-color: rgba(16, 16, 20, 0.75);

@ -16,7 +16,7 @@
</div>
<n-list-item v-for="contact in list" :key="contact.user_id">
<follow-item :contact="contact" @send-whisper="onSendWhisper" @unfollow-success="handleUnfollowSuccess" />
<user-card type="follow" :contact="contact" @send-whisper="onSendWhisper" @unfollow-success="handleUnfollowSuccess" />
</n-list-item>
</div>
<!-- -->
@ -24,14 +24,14 @@
</n-list>
</div>
<n-space v-if="totalPage > 0" justify="center">
<InfiniteLoading class="load-more" :slots="{ complete: completeStr, error: '加载出错' }" @infinite="handleNextPage">
<template #spinner>
<div class="load-more-wrap">
<n-spin :size="14" v-if="!noMore" />
<span class="load-more-spinner">{{ noMore ? completeStr : '' }}</span>
</div>
</template>
</InfiniteLoading>
<InfiniteLoading class="load-more" :slots="{ complete: completeStr, error: '加载出错' }" @infinite="handleNextPage">
<template #spinner>
<div class="load-more-wrap">
<n-spin :size="14" v-if="!noMore" />
<span class="load-more-spinner">{{ noMore ? completeStr : '' }}</span>
</div>
</template>
</InfiniteLoading>
</n-space>
</template>
@ -42,6 +42,7 @@ import { useRoute } from 'vue-router';
import { Api } from '@/utils/request';
import { usePagination } from '@/composables/usePagination';
import UserAction from '@/composables/useUserAction';
import UserCard from '@/components/user-card.vue';
const route = useRoute();

@ -3,38 +3,38 @@
<main-nav title="消息" />
<n-list class="main-content-wrap messages-wrap" bordered>
<!-- -->
<!-- -->
<whisper :show="showWhisper" :user="whisperReceiver" @success="whisperSuccess" />
<n-space justify="space-between">
<div class="title title-action">
<n-button text size="small" :focusable="false" @click="handleUnreadMessage">
<template #icon>
<n-icon>
<UnreadIcon />
</n-icon>
</template>
{{ unreadMsgCount }}
</n-button>
<n-divider vertical />
<n-button text size="small" :focusable="false" @click="handleReadAll"></n-button>
</div>
<div class="title title-filter">
<n-dropdown
placement="bottom-end"
trigger="click"
size="small"
:options="options"
@select="handleAction">
<n-button text>
<template #icon>
<n-icon>
<OptionsIcon />
</n-icon>
</template>
{{ messageStyle }}
</n-button>
</n-dropdown>
</div>
<div class="title title-action">
<n-button text size="small" :focusable="false" @click="handleUnreadMessage">
<template #icon>
<n-icon>
<UnreadIcon />
</n-icon>
</template>
{{ unreadMsgCount }}
</n-button>
<n-divider vertical />
<n-button text size="small" :focusable="false" @click="handleReadAll"></n-button>
</div>
<div class="title title-filter">
<n-dropdown
placement="bottom-end"
trigger="click"
size="small"
:options="options"
@select="handleAction">
<n-button text>
<template #icon>
<n-icon>
<OptionsIcon />
</n-icon>
</template>
{{ messageStyle }}
</n-button>
</n-dropdown>
</div>
</n-space>
<div v-if="loading && list.length === 0" class="skeleton-wrap">
<message-skeleton :num="pageSize" />
@ -50,16 +50,12 @@
</div>
</div>
</n-list>
<n-space v-if="totalPage > 0" justify="center">
<InfiniteLoading class="load-more" :slots="{ complete: '没有更多消息了', error: '加载出错' }" @infinite="nextPage">
<template #spinner>
<div class="load-more-wrap">
<n-spin :size="14" v-if="!noMore" />
<span class="load-more-spinner">{{ noMore ? '' : '' }}</span>
</div>
</template>
</InfiniteLoading>
</n-space>
<infinite-load-more
:total-page="totalPage"
:no-more="noMore"
complete-text="没有更多消息了"
@load-more="nextPage"
/>
</div>
</template>
@ -69,7 +65,6 @@ import type { Component } from 'vue';
import { NIcon, DropdownOption } from 'naive-ui';
import { useStoreMain } from '@/store/main';
import { useRoute } from 'vue-router';
import InfiniteLoading from 'v3-infinite-loading';
import {
LayersOutline as AllIcon,
AtOutline as SystemIcon,
@ -81,17 +76,18 @@ import {
import { useStoreUser } from '@/store/user';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
import { usePagination } from '@/composables/usePagination';
import InfiniteLoadMore from '@/components/infinite-load-more.vue';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { unreadMsgCount } = storeToRefs(storeMain);
const route = useRoute();
const loading = ref(false);
const noMore = ref(false);
const page = ref(+(route.query.p as string) || 1);
const pageSize = ref(20);
const totalPage = ref(0);
const { loading, noMore, page, pageSize, totalPage, reset: resetPagination } = usePagination(20);
// 初始化页码
page.value = +(route.query.p as string) || 1;
const list = ref<Item.MessageProps[]>([]);
const messageStyle = ref<
'' | '' | '' | '' | ''
@ -115,9 +111,7 @@ const whisperReceiver = ref<Item.UserInfo>({
});
const reset = () => {
noMore.value = false;
page.value = 1;
totalPage.value = 0;
resetPagination();
list.value = [];
};
@ -363,22 +357,6 @@ onMounted(() => {
</script>
<style lang="less" scoped>
.load-more {
margin: 20px;
.load-more-wrap {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 14px;
.load-more-spinner {
font-size: 14px;
opacity: 0.65;
}
}
}
.title {
padding-top: 4px;
opacity: 0.9;

Loading…
Cancel
Save