{{ props.reply.user.username }}
@@ -14,7 +14,7 @@
{{ props.reply.at_user.username }}
diff --git a/web/src/router/index.ts b/web/src/router/index.ts
index 03ed97ee..0a4bd405 100644
--- a/web/src/router/index.ts
+++ b/web/src/router/index.ts
@@ -1,117 +1,125 @@
-import { createRouter, createWebHashHistory } from 'vue-router';
+import { createRouter, createWebHashHistory } from "vue-router";
const routes = [
- {
- path: '/',
- name: 'home',
- meta: {
- title: '广场',
- keepAlive: true,
- },
- component: () => import('@/views/Home.vue'),
+ {
+ path: "/",
+ name: "home",
+ meta: {
+ title: "广场",
+ keepAlive: true,
},
- {
- path: '/post',
- name: 'post',
- meta: {
- title: '话题详情',
- },
- component: () => import('@/views/Post.vue'),
+ component: () => import("@/views/Home.vue"),
+ },
+ {
+ path: "/post",
+ name: "post",
+ meta: {
+ title: "泡泡详情",
},
- {
- path: '/topic',
- name: 'topic',
- meta: {
- title: '话题',
- },
- component: () => import('@/views/Topic.vue'),
+ component: () => import("@/views/Post.vue"),
+ },
+ {
+ path: "/topic",
+ name: "topic",
+ meta: {
+ title: "话题",
},
- {
- path: '/anouncement',
- name: 'anouncement',
- meta: {
- title: '公告',
- },
- component: () => import('@/views/Anouncement.vue'),
+ component: () => import("@/views/Topic.vue"),
+ },
+ {
+ path: "/anouncement",
+ name: "anouncement",
+ meta: {
+ title: "公告",
},
- {
- path: '/profile',
- name: 'profile',
- meta: {
- title: '主页',
- },
- component: () => import('@/views/Profile.vue'),
+ component: () => import("@/views/Anouncement.vue"),
+ },
+ {
+ path: "/profile",
+ name: "profile",
+ meta: {
+ title: "主页",
},
- {
- path: '/user',
- name: 'user',
- meta: {
- title: '用户详情',
- },
- component: () => import('@/views/User.vue'),
+ component: () => import("@/views/Profile.vue"),
+ },
+ {
+ path: "/u",
+ name: "user",
+ meta: {
+ title: "用户详情",
},
- {
- path: '/messages',
- name: 'messages',
- meta: {
- title: '消息',
- },
- component: () => import('@/views/Messages.vue'),
+ component: () => import("@/views/User.vue"),
+ },
+ {
+ path: "/messages",
+ name: "messages",
+ meta: {
+ title: "消息",
},
- {
- path: '/collection',
- name: 'collection',
- meta: {
- title: '收藏',
- },
- component: () => import('@/views/Collection.vue'),
+ component: () => import("@/views/Messages.vue"),
+ },
+ {
+ path: "/collection",
+ name: "collection",
+ meta: {
+ title: "收藏",
},
- {
- path: '/contacts',
- name: 'contacts',
- meta: {
- title: '好友',
- },
- component: () => import('@/views/Contacts.vue'),
+ component: () => import("@/views/Collection.vue"),
+ },
+ {
+ path: "/contacts",
+ name: "contacts",
+ meta: {
+ title: "好友",
},
- {
- path: '/wallet',
- name: 'wallet',
- meta: {
- title: '钱包',
- },
- component: () => import('@/views/Wallet.vue'),
+ component: () => import("@/views/Contacts.vue"),
+ },
+ {
+ path: "/following",
+ name: "following",
+ meta: {
+ title: "关注",
},
- {
- path: '/setting',
- name: 'setting',
- meta: {
- title: '设置',
- },
- component: () => import('@/views/Setting.vue'),
+ component: () => import("@/views/Following.vue"),
+ },
+ {
+ path: "/wallet",
+ name: "wallet",
+ meta: {
+ title: "钱包",
},
- {
- path: '/404',
- name: '404',
- meta: {
- title: '404',
- },
- component: () => import('@/views/404.vue'),
+ component: () => import("@/views/Wallet.vue"),
+ },
+ {
+ path: "/setting",
+ name: "setting",
+ meta: {
+ title: "设置",
},
- {
- path: '/:pathMatch(.*)',
- redirect: '/404',
+ component: () => import("@/views/Setting.vue"),
+ },
+ {
+ path: "/404",
+ name: "404",
+ meta: {
+ title: "404",
},
+ component: () => import("@/views/404.vue"),
+ },
+ {
+ path: "/:pathMatch(.*)",
+ redirect: "/404",
+ },
];
const router = createRouter({
- history: createWebHashHistory(),
- routes,
+ history: createWebHashHistory(),
+ routes,
});
router.beforeEach((to, from, next) => {
- document.title = `${to.meta.title} | 泡泡 - 一个清新文艺的微社区`;
- next();
+ document.title = `${to.meta.title} | 泡泡 - 一个清新文艺的微社区`;
+ next();
});
export default router;
diff --git a/web/src/store/index.ts b/web/src/store/index.ts
index a80941f9..02ac21aa 100644
--- a/web/src/store/index.ts
+++ b/web/src/store/index.ts
@@ -17,6 +17,9 @@ export default createStore({
id: 0,
username: "",
nickname: "",
+ created_on: 0,
+ follows: 0,
+ followings: 0,
},
},
mutations: {
@@ -51,7 +54,14 @@ export default createStore({
},
userLogout(state) {
localStorage.removeItem("PAOPAO_TOKEN");
- state.userInfo = { id: 0, nickname: "", username: "" };
+ state.userInfo = {
+ id: 0,
+ nickname: "",
+ username: "",
+ created_on: 0,
+ follows: 0,
+ followings: 0,
+ };
state.userLogined = false;
},
},
diff --git a/web/src/types/Item.d.ts b/web/src/types/Item.d.ts
index ff8530ad..743c035f 100644
--- a/web/src/types/Item.d.ts
+++ b/web/src/types/Item.d.ts
@@ -16,6 +16,14 @@ declare module Item {
is_admin: boolean;
/** 是否好友 */
is_friend: boolean;
+ /** 是否关注 */
+ is_following: boolean;
+ /** 加入时间 */
+ created_on: number;
+ /** 关注数 */
+ follows: number;
+ /** 粉丝数 */
+ followings: number;
/** 用户余额(分) */
balance?: number;
/** 用户状态 */
@@ -260,6 +268,13 @@ declare module Item {
avatar: string;
}
+ interface FollowItemProps {
+ user_id: number;
+ name: string;
+ nickname: string;
+ avatar: string;
+ }
+
interface AttachmentProps {
id: number;
/** 类别:1为图片,2为视频,3为其他附件 */
diff --git a/web/src/types/NetParams.d.ts b/web/src/types/NetParams.d.ts
index 083d0588..0e2dc7eb 100644
--- a/web/src/types/NetParams.d.ts
+++ b/web/src/types/NetParams.d.ts
@@ -63,6 +63,14 @@ declare module NetParams {
status: number;
}
+ interface FollowUserReq {
+ user_id: number;
+ }
+
+ interface UnfollowUserReq {
+ user_id: number;
+ }
+
interface UserReqRecharge {
amount: number;
}
@@ -111,6 +119,18 @@ declare module NetParams {
page_size: number;
}
+ interface GetUserFollows {
+ username: string;
+ page: number;
+ page_size: number;
+ }
+
+ interface GetUserFollowings {
+ username: string;
+ page: number;
+ page_size: number;
+ }
+
interface UserChangePassword {
/** 新密码 */
password: string;
diff --git a/web/src/types/NetReq.d.ts b/web/src/types/NetReq.d.ts
index 671204a5..e4f964c8 100644
--- a/web/src/types/NetReq.d.ts
+++ b/web/src/types/NetReq.d.ts
@@ -86,15 +86,14 @@ declare module NetReq {
interface UserChangeStatus {}
+ interface FollowUserResp {}
+
+ interface UnfollowUserResp {}
+
interface AddFriend {}
interface DeleteFriend {}
- interface GetContacts {
- contacts: Item.ContactsItemProps;
- total: number;
- }
-
interface RejectFriend {}
interface RequestingFriend {}
diff --git a/web/src/utils/formatTime.ts b/web/src/utils/formatTime.ts
index 2e561ecc..da1499de 100644
--- a/web/src/utils/formatTime.ts
+++ b/web/src/utils/formatTime.ts
@@ -35,3 +35,7 @@ export const formatPrettyDate = (time: number) => {
}
return mt.fromNow();
};
+
+export const formatDate = (time: number) => {
+ return moment.unix(time).utc(true).format("YYYY年MM月");
+};
diff --git a/web/src/views/Following.vue b/web/src/views/Following.vue
new file mode 100644
index 00000000..8c8652af
--- /dev/null
+++ b/web/src/views/Following.vue
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/views/Profile.vue b/web/src/views/Profile.vue
index 5a4518fd..1d13c745 100644
--- a/web/src/views/Profile.vue
+++ b/web/src/views/Profile.vue
@@ -10,14 +10,54 @@
-
+
{{ store.state.userInfo.nickname }}
@{{ store.state.userInfo.username }}
+
+ 管理员
+
+
+
+ UID. {{ store.state.userInfo.id }}
+ {{ formatDate(store.state.userInfo.created_on) }} 加入
+
+
+
+
+ 关注 {{ store.state.userInfo.follows }}
+
+
+
+
+ 粉丝 {{ store.state.userInfo.followings }}
+
+
-
UID. {{ store.state.userInfo.id }}
@@ -64,6 +104,7 @@ import { ref, onMounted, watch } from 'vue';
import { useStore } from 'vuex';
import { useRoute } from 'vue-router';
import { getUserPosts } from '@/api/user';
+import { formatDate } from '@/utils/formatTime';
const store = useStore();
const route = useRoute();
@@ -276,23 +317,31 @@ watch(
display: flex;
padding: 16px;
.avatar {
- width: 55px;
+ width: 72px;
}
.base-info {
position: relative;
- width: calc(100% - 55px);
+ margin-left: 12px;
+ width: calc(100% - 84px);
.username {
line-height: 16px;
font-size: 16px;
}
- .uid {
+ .userinfo {
font-size: 14px;
line-height: 14px;
margin-top: 10px;
opacity: 0.75;
+ .info-item {
+ margin-right: 12px;
+ }
+ }
+
+ .top-tag {
+ transform: scale(0.75);
}
}
}
diff --git a/web/src/views/Topic.vue b/web/src/views/Topic.vue
index 474907ca..da5d0336 100644
--- a/web/src/views/Topic.vue
+++ b/web/src/views/Topic.vue
@@ -20,7 +20,7 @@
v-for="tag in tags"
:tag="tag"
:showAction="store.state.userLogined && tagsChecked"
- :checkFollowing="inFollwTab"
+ :checkFollowing="inFollowTab"
>
@@ -39,7 +39,7 @@ const tags = ref([]);
const tagType = ref<"hot" | "new" | "follow">('hot');
const loading = ref(false);
const tagsChecked = ref(false)
-const inFollwTab = ref(false)
+const inFollowTab = ref(false)
watch(tagsChecked, () => {
if (!tagsChecked.value) {
@@ -77,9 +77,9 @@ const loadTags = () => {
const changeTab = (tab: "hot" | "new" | "follow") => {
tagType.value = tab;
if (tab == "follow") {
- inFollwTab.value = true
+ inFollowTab.value = true
} else {
- inFollwTab.value = false
+ inFollowTab.value = false
}
loadTags();
};
diff --git a/web/src/views/User.vue b/web/src/views/User.vue
index c0d3c756..c4112a5d 100644
--- a/web/src/views/User.vue
+++ b/web/src/views/User.vue
@@ -7,7 +7,7 @@
-
+
@@ -22,7 +22,44 @@
管理员
-
UID. {{ user.id }}
+
+ UID. {{ user.id }}
+ {{ formatDate(user.created_on) }} 加入
+
+
+
+
+ 关注 {{ user.follows}}
+
+
+
+
+ 粉丝 {{ user.followings }}
+
+
+
-
@@ -89,16 +125,18 @@ import { NIcon } from 'naive-ui'
import type { Component } from 'vue'
import { useStore } from 'vuex';
import { useRoute } from 'vue-router';
-import { getUserProfile, getUserPosts, changeUserStatus, deleteFriend } from '@/api/user';
+import { getUserProfile, getUserPosts, changeUserStatus, deleteFriend, followUser, unfollowUser } from '@/api/user';
import { useDialog, DropdownOption } from 'naive-ui';
import WhisperAddFriend from '../components/whisper-add-friend.vue';
import { MoreHorizFilled } from '@vicons/material';
+import { formatDate } from '@/utils/formatTime';
import {
PaperPlaneOutline,
PersonAddOutline,
PersonRemoveOutline,
CubeOutline,
- TrashOutline,
+ BodyOutline,
+ WalkOutline
} from '@vicons/ionicons5';
const dialog = useDialog();
@@ -113,13 +151,17 @@ const user = reactive({
nickname: '',
is_admin: false,
is_friend: true,
+ is_following: false,
+ created_on: 0,
+ follows: 0,
+ followings: 0,
status: 1,
});
const userLoading = ref(false);
const showWhisper = ref(false);
const showAddFriendWhisper = ref(false);
const list = ref([]);
-const username = ref(route.query.username || '');
+const username = ref(route.query.s || '');
const page = ref(+(route.query.p as string) || 1);
const pageType = ref<"post" | "comment" | "highlight" | "media" | "star">('post');
const postPage = ref(+(route.query.p as string) || 1);
@@ -282,6 +324,10 @@ const loadUser = () => {
user.nickname = res.nickname;
user.is_admin = res.is_admin;
user.is_friend = res.is_friend;
+ user.created_on = res.created_on;
+ user.is_following = res.is_following;
+ user.follows = res.follows;
+ user.followings = res.followings;
user.status = res.status;
loadPage();
})
@@ -355,6 +401,19 @@ const userOptions = computed(() => {
});
}
}
+ if (user.is_following) {
+ options.push({
+ label: '取消关注',
+ key: 'unfollow',
+ icon: renderIcon(WalkOutline)
+ })
+ } else {
+ options.push({
+ label: '关注',
+ key: 'follow',
+ icon: renderIcon(BodyOutline)
+ })
+ }
if (user.is_friend) {
options.push({
label: '删除好友',
@@ -371,7 +430,7 @@ const userOptions = computed(() => {
return options;
});
const handleUserAction = (
- item: 'whisper' | 'delete' | 'requesting' | 'banned' | 'deblocking'
+ item: 'whisper' | 'follow' | 'unfollow' | 'delete' | 'requesting' | 'banned' | 'deblocking'
) => {
switch (item) {
case 'whisper':
@@ -383,6 +442,10 @@ const handleUserAction = (
case 'requesting':
openAddFriendWhisper();
break;
+ case 'follow':
+ case 'unfollow':
+ handleFollowUser();
+ break;
case 'banned':
case 'deblocking':
banUser();
@@ -414,6 +477,43 @@ const openDeleteFriend = () => {
},
});
};
+const handleFollowUser = () => {
+ dialog.success({
+ title: '提示',
+ content:
+ '确定' + (user.is_following ? '取消关注' : '关注') + '该用户吗?',
+ positiveText: '确定',
+ negativeText: '取消',
+ onPositiveClick: () => {
+ userLoading.value = true;
+ if (user.is_following) {
+ unfollowUser({
+ user_id: user.id,
+ }).then((_res) => {
+ userLoading.value = false;
+ window.$message.success('取消关注成功');
+ loadUser();
+ })
+ .catch((err) => {
+ userLoading.value = false;
+ console.log(err);
+ });
+ } else {
+ followUser({
+ user_id: user.id,
+ }).then((_res) => {
+ userLoading.value = false;
+ window.$message.success('关注成功');
+ loadUser();
+ })
+ .catch((err) => {
+ userLoading.value = false;
+ console.log(err);
+ });
+ }
+ },
+ });
+};
const banUser = () => {
dialog.warning({
title: '警告',
@@ -429,8 +529,13 @@ const banUser = () => {
id: user.id,
status: user.status === 1 ? 2 : 1,
})
- .then((res) => {
+ .then((_res) => {
userLoading.value = false;
+ if (user.status === 1) {
+ window.$message.success('禁言成功');
+ } else {
+ window.$message.success('解封成功');
+ }
loadUser();
})
.catch((err) => {
@@ -447,7 +552,7 @@ watch(
}),
(to, from) => {
if (from.path === '/user' && to.path === '/user') {
- username.value = route.query.username || '';
+ username.value = route.query.s || '';
loadUser();
}
}
@@ -467,23 +572,27 @@ onMounted(() => {
padding: 16px;
.avatar {
- width: 55px;
+ width: 72px;
}
.base-info {
position: relative;
- width: calc(100% - 55px);
+ margin-left: 12px;
+ width: calc(100% - 84px);
.username {
line-height: 16px;
font-size: 16px;
}
- .uid {
+ .userinfo {
font-size: 14px;
line-height: 14px;
margin-top: 10px;
opacity: 0.75;
+ .info-item {
+ margin-right: 12px;
+ }
}
.top-tag {