1. vuex -> pinia

2. 更新网络请求方式
pull/703/head
orzi 6 months ago
parent c97f143cac
commit bfffadf820

@ -24,12 +24,13 @@
"moment": "^2.30.1",
"naive-ui": "^2.41.0",
"paopao-video-player": "^1.0.0",
"pinia": "^3.0.4",
"qrcanvas-vue": "^3.0.0",
"qrcode": "^1.5.4",
"unplugin-vue-components": "^28.5.0",
"v3-infinite-loading": "^1.3.2",
"vfonts": "^0.0.3",
"vue": "^3.5.13",
"vue": "^3.5.30",
"vue-router": "4",
"vuex": "^4.1.0"
},
@ -38,11 +39,10 @@
"@tauri-apps/cli": "^1.5.10",
"@types/node": "^22.14.1",
"@types/qrcode": "^1.5.5",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue": "^6.0.5",
"@vue/compiler-sfc": "^3.5.13",
"rollup-plugin-visualizer": "^5.14.0",
"typescript": "^5.8.3",
"vite": "^6.2.6"
"vite": "^8.0.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

@ -1,14 +1,14 @@
<template>
<n-config-provider :theme="theme">
<n-config-provider :theme="iTheme">
<n-message-provider>
<n-dialog-provider>
<div
class="app-container"
:class="{ dark: theme?.name === 'dark', mobile: !store.state.desktopModelShow }"
:class="{ dark: iTheme?.name === 'dark', mobile: !desktopModelShow }"
>
<div has-sider class="main-wrap" position="static" >
<!-- -->
<div v-if="store.state.desktopModelShow">
<div v-if="desktopModelShow">
<sidebar />
</div>
@ -44,24 +44,28 @@
<script setup lang="ts">
import { onMounted, computed } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { darkTheme } from 'naive-ui';
import { getSiteProfile } from '@/api/site';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
const store = useStore();
const theme = computed(() => (store.state.theme === 'dark' ? darkTheme : null));
const storeMain = useStoreMain();
const storeProfile = useStoreProfile();
const { theme, desktopModelShow } = storeToRefs(storeMain);
const iTheme = computed(() => (theme.value === 'dark' ? darkTheme : null));
function loadSiteProfile() {
store.commit('loadDefaultSiteProfile');
if (import.meta.env.VITE_USE_WEB_PROFILE.toLowerCase() === 'true') {
getSiteProfile()
.then((res) => {
store.commit('updateSiteProfile', res);
})
.catch((err) => {
console.log(err);
});
}
storeProfile.loadDefaultSiteProfile();
if (import.meta.env.VITE_USE_WEB_PROFILE.toLowerCase() === 'true') {
getSiteProfile()
.then((res) => {
storeProfile.updateSiteProfile(res);
}).catch((err) => {
console.log(err);
});
}
}
onMounted(() => {

@ -1,47 +1,30 @@
import { request } from '@/utils/request';
/** 用户登录 */
export const userLogin = (
params: NetParams.AuthUserLogin,
): Promise<NetReq.AuthUserLogin> => {
return request({
method: 'post',
url: '/v1/auth/login',
data: params,
});
};
/**
* createApi
* API
*/
/** 注册用户 */
export const userRegister = (
params: NetParams.AuthUserRegister,
): Promise<NetReq.AuthUserRegister> => {
return request({
method: 'post',
url: '/v1/auth/register',
data: params,
});
};
import { request } from '@/utils/request';
/** 用户信息 */
export const userInfo = (
token: NetParams.AuthUserInfo = '',
token: NetParams.AuthUserInfo = '',
): Promise<NetReq.AuthUserInfo> => {
return request({
method: 'get',
url: '/v1/user/info',
headers: {
Authorization: `Bearer ${token}`,
},
});
return request({
method: 'get',
url: '/v1/user/info',
headers: {
Authorization: `Bearer ${token}`,
},
});
};
/** 修改用户密码,该接口暂时未使用 */
export const updateUserPassword = (
data: NetParams.AuthUpdateUserPassword,
data: NetParams.AuthUpdateUserPassword,
): Promise<NetReq.AuthUpdateUserPassword> => {
return request({
method: 'post',
url: '/v1/api/user/password',
data,
});
return request({
method: 'post',
url: '/v1/api/user/password',
data,
});
};

@ -1,3 +1,8 @@
/**
* createApi
* API
*/
import { request } from '@/utils/request';
/** 获取动态列表 */
@ -88,17 +93,6 @@ export const getPostComments = (
});
};
/** 获取联系人列表 */
export const getContacts = (
params: NetParams.GetContacts,
): Promise<NetReq.GetContacts> => {
return request({
method: 'get',
url: '/v1/user/contacts',
params,
});
};
/** 获取联系人列表 */
export const getIndexTrends = (
params: NetParams.IndexTrendsReq,

@ -1,9 +1,14 @@
/**
* createApi
* API
*/
import { request } from '@/utils/request';
/** 获取站点概要信息 */
export const getSiteProfile = (): Promise<NetReq.SiteProfile> => {
return request({
method: 'get',
url: '/v1/site/profile',
});
return request({
method: 'get',
url: '/v1/site/profile',
});
};

@ -1,440 +0,0 @@
import { request } from '@/utils/request';
/** 获取验证码 */
export const getCaptcha = (
params: NetParams.UserGetCaptcha = {},
): Promise<NetReq.UserGetCaptcha> => {
return request({
method: 'get',
url: '/v1/captcha',
params,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const sendCaptcha = (data: any) => {
return request({
method: 'post',
url: '/v1/captcha',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const sendUserWhisper = (
data: NetParams.UserWhisper,
): Promise<NetParams.UserWhisper> => {
return request({
method: 'post',
url: '/v1/user/whisper',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const requestingFriend = (
data: NetParams.RequestingFriend,
): Promise<NetReq.RequestingFriend> => {
return request({
method: 'post',
url: '/v1/friend/requesting',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const addFriend = (
data: NetParams.AddFriend,
): Promise<NetReq.AddFriend> => {
return request({
method: 'post',
url: '/v1/friend/add',
data,
});
};
// 关注 用户
export const followUser = (
data: NetParams.FollowUserReq,
): Promise<NetReq.FollowUserResp> => {
return request({
method: 'post',
url: '/v1/user/follow',
data,
});
};
// 取消关注 用户
export const unfollowUser = (
data: NetParams.UnfollowUserReq,
): Promise<NetReq.UnfollowUserResp> => {
return request({
method: 'post',
url: '/v1/user/unfollow',
data,
});
};
/**
*
* @param {Object} params
* @returns Promise
*/
export const getUserFollows = (
params: NetParams.GetUserFollows,
): Promise<NetReq.GetContacts> => {
return request({
method: 'get',
url: '/v1/user/follows',
params,
});
};
/**
*
* @param {Object} params
* @returns Promise
*/
export const getUserFollowings = (
params: NetParams.GetUserFollowings,
): Promise<NetReq.GetContacts> => {
return request({
method: 'get',
url: '/v1/user/followings',
params,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const rejectFriend = (
data: NetParams.RejectFriend,
): Promise<NetReq.RejectFriend> => {
return request({
method: 'post',
url: '/v1/friend/reject',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const deleteFriend = (
data: NetParams.DeleteFriend,
): Promise<NetReq.DeleteFriend> => {
return request({
method: 'post',
url: '/v1/friend/delete',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const getContacts = (
data: NetParams.GetContacts,
): Promise<NetReq.GetContacts> => {
return request({
method: 'post',
url: '/v1/user/contacts',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const bindUserPhone = (
data: NetParams.UserBindUserPhone,
): Promise<NetParams.UserBindUserPhone> => {
return request({
method: 'post',
url: '/v1/user/phone',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const activateUser = (
data: NetParams.UserActivation,
): Promise<NetParams.UserActivation> => {
return request({
method: 'post',
url: '/v1/user/activate',
data,
});
};
/** 更改密码 */
export const changePassword = (
data: NetParams.UserChangePassword,
): Promise<NetReq.UserChangePassword> => {
return request({
method: 'post',
url: '/v1/user/password',
data,
});
};
/** 更改昵称 */
export const changeNickname = (
data: NetParams.UserChangeNickname,
): Promise<NetReq.UserChangeNickname> => {
return request({
method: 'post',
url: '/v1/user/nickname',
data,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const changeAvatar = (data: any) => {
return request({
method: 'post',
url: '/v1/user/avatar',
data,
});
};
/** 获取未读消息数 */
export const getUnreadMsgCount = (
params: NetParams.UserGetUnreadMsgCount = {},
): Promise<NetReq.UserGetUnreadMsgCount> => {
return request({
method: 'get',
url: '/v1/user/msgcount/unread',
params,
});
};
/** 获取消息列表 */
export const getMessages = (
params: NetParams.UserGetMessages,
): Promise<NetReq.UserGetMessages> => {
return request({
method: 'get',
url: '/v1/user/messages',
params,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const readMessage = (
data: NetParams.ReadMessageReq,
): Promise<NetReq.ReadMessageResp> => {
return request({
method: 'post',
url: '/v1/user/message/read',
data,
});
};
/**
*
* @returns Promise
*/
export const readAllMessage = (): Promise<NetReq.ReadAllMessageResp> => {
return request({
method: 'post',
url: '/v1/user/message/readall',
});
};
/** 获取收藏列表 */
export const getCollections = (
params: NetParams.UserGetCollections,
): Promise<NetReq.UserGetCollections> => {
return request({
method: 'get',
url: '/v1/user/collections',
params,
});
};
/** 获取用户基础信息 */
export const getUserProfile = (
params: NetParams.UserGetUserProfile,
): Promise<NetReq.UserGetUserProfile> => {
return request({
method: 'get',
url: '/v1/user/profile',
params,
});
};
/** 获取用户帖子列表 */
export const getUserPosts = (
params: NetParams.UserGetUserPosts,
): Promise<NetReq.UserGetUserPosts> => {
return request({
method: 'get',
url: '/v1/user/posts',
params,
});
};
/** 获取账单列表 */
export const getBills = (
params: NetParams.UserGetBills,
): Promise<NetReq.UserGetBills> => {
return request({
method: 'get',
url: '/v1/user/wallet/bills',
params,
});
};
/**
*
* @param {Object} data
* @returns Promise
*/
export const reqRecharge = (
data: NetParams.UserReqRecharge,
): Promise<NetReq.UserReqRecharge> => {
return request({
method: 'post',
url: '/v1/user/recharge',
data,
});
};
/**
*
* @param {Object} params
* @returns Promise
*/
export const getRecharge = (
params: NetParams.UserGetRecharge,
): Promise<NetReq.UserGetRecharge> => {
return request({
method: 'get',
url: '/v1/user/recharge',
params,
});
};
/**
*
* @param {Object} params
* @returns Promise
*/
export const getSuggestUsers = (params: {
k: string;
}): Promise<NetReq.UserGetSuggestUsers> => {
return request({
method: 'get',
url: '/v1/suggest/users',
params,
});
};
/**
*
* @param {Object} params
* @returns Promise
*/
export const getSuggestTags = (params: {
k: string;
}): Promise<NetReq.UserGetSuggestTags> => {
return request({
method: 'get',
url: '/v1/suggest/tags',
params,
});
};
/**
*
* @param {Object} params
* @returns Promise
*/
export const precheckAttachment = (
params: NetParams.UserPrecheckAttachment,
): Promise<NetReq.UserPrecheckAttachment> => {
return request({
method: 'get',
url: '/v1/attachment/precheck',
params,
});
};
/**
*
* @param {Object} params
* @returns Promise
*/
export const getAttachment = (
params: NetParams.UserGetAttachment,
): Promise<NetReq.UserGetAttachment> => {
return request({
method: 'get',
url: '/v1/attachment',
params,
});
};
/**
* ·/
* @param {Object} data
* @returns Promise
*/
export const changeUserStatus = (
data: NetParams.UserStatusReq,
): Promise<NetReq.UserChangeStatus> => {
return request({
method: 'post',
url: '/v1/admin/user/status',
data,
});
};
/**
*
* @returns Promise
*/
export const getSiteInfo = (): Promise<NetReq.SiteInfoResp> => {
return request({
method: 'get',
url: '/v1/admin/site/status',
});
};

@ -1,6 +1,6 @@
<template>
<n-modal
v-model:show="store.state.authModalShow"
v-model:show="authModalShow"
class="auth-card"
preset="card"
size="small"
@ -12,7 +12,7 @@
>
<div class="auth-wrap">
<n-card :bordered="false">
<div v-if="!store.state.profile.allowUserRegister">
<div v-if="!profile.allowUserRegister">
<n-space justify="center"><n-h3><n-text type="success"></n-text></n-h3></n-space>
<n-form
ref="loginRef"
@ -57,8 +57,8 @@
</n-button>
</div>
<n-tabs
v-if="store.state.profile.allowUserRegister"
:default-value="store.state.authModelTab"
v-if="profile.allowUserRegister"
:default-value="authModelTab"
size="large"
justify-content="space-evenly"
>
@ -155,11 +155,19 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { useStore } from 'vuex';
import { userLogin, userRegister, userInfo } from '@/api/auth';
import { useStoreMain } from '@/store/main';
import { TOKEN_KEY, useStoreUser } from '@/store/user';
import { userInfo } from '@/api/auth';
import type { FormInst, FormItemRule } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
import { useStoreProfile } from '@/store/profile';
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const storeProfile = useStoreProfile();
const { authModalShow, authModelTab } = storeToRefs(storeMain);
const { profile } = storeToRefs(storeProfile);
const loading = ref(false);
const loginRef = ref<FormInst>();
@ -208,14 +216,14 @@ const handleLogin = (e: Event) => {
if (!errors) {
loading.value = true;
userLogin({
Api.v1.auth.post.login({
username: loginForm.username,
password: loginForm.password,
})
.then((res) => {
const token = res?.token || '';
// 写入用户信息
localStorage.setItem('PAOPAO_TOKEN', token);
localStorage.setItem(TOKEN_KEY, token);
return userInfo(token);
})
@ -223,9 +231,9 @@ const handleLogin = (e: Event) => {
window.$message.success('');
loading.value = false;
store.commit('updateUserinfo', res);
store.commit('triggerAuth', false);
store.commit('refresh');
storeUser.updateUserinfo(res);
storeMain.triggerAuth(false);
storeMain.doRefresh();
loginForm.username = '';
loginForm.password = '';
})
@ -244,12 +252,12 @@ const handleRegister = (e: Event) => {
if (!errors) {
loading.value = true;
userRegister({
Api.v1.auth.post.register({
username: registerForm.username,
password: registerForm.password,
})
.then((res) => {
return userLogin({
return Api.v1.auth.post.login({
username: registerForm.username,
password: registerForm.password,
});
@ -257,7 +265,7 @@ const handleRegister = (e: Event) => {
.then((res) => {
const token = res?.token || '';
// 写入用户信息
localStorage.setItem('PAOPAO_TOKEN', token);
localStorage.setItem(TOKEN_KEY, token);
return userInfo(token);
})
@ -265,8 +273,8 @@ const handleRegister = (e: Event) => {
window.$message.success('');
loading.value = false;
store.commit('updateUserinfo', res);
store.commit('triggerAuth', false);
storeUser.updateUserinfo(res);
storeMain.triggerAuth(false);
registerForm.username = '';
registerForm.password = '';
registerForm.repassword = '';
@ -279,18 +287,18 @@ const handleRegister = (e: Event) => {
};
onMounted(() => {
const token = localStorage.getItem('PAOPAO_TOKEN') || '';
const token = localStorage.getItem(TOKEN_KEY) || '';
if (token) {
userInfo(token)
.then((res) => {
store.commit('updateUserinfo', res);
store.commit('triggerAuth', false);
storeUser.updateUserinfo(res);
storeMain.triggerAuth(false);
})
.catch((err) => {
store.commit('userLogout');
storeUser.userLogout();
});
} else {
store.commit('userLogout');
storeUser.userLogout();
}
});
</script>

@ -36,7 +36,7 @@
{{ comment.ip_loc}}
</span>
<n-popconfirm
v-if="store.state.userInfo.id === postUserId"
v-if="userInfo.id === postUserId"
negative-text="取消"
positive-text="确认"
@positive-click="execHightlightAction"
@ -62,8 +62,8 @@
</n-popconfirm>
<n-popconfirm
v-if="
store.state.userInfo.is_admin ||
store.state.userInfo.id === comment.user.id
userInfo.is_admin ||
userInfo.id === comment.user.id
"
negative-text="取消"
positive-text="确认"
@ -128,19 +128,24 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useStoreUser } from '@/store/user';
import { useRouter } from 'vue-router';
import { parsePostTag } from '@/utils/content';
import { Trash, ArrowBarToUp, ArrowBarDown } from '@vicons/tabler';
import { deleteComment, highlightComment } from '@/api/post';
import { YesNoEnum } from '@/utils/IEnum';
import { storeToRefs } from 'pinia';
const store = useStore();
const router = useRouter();
const replyAtUserID = ref(0);
const replyAtUsername = ref('');
const replyComposeRef = ref();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { userInfo } = storeToRefs(storeUser);
const emit = defineEmits<{
(e: 'reload'): void;
}>();
@ -176,7 +181,7 @@ const doClickText = (e: MouseEvent, id: number | string) => {
if (_target.dataset.detail) {
const d = _target.dataset.detail.split(':');
if (d.length === 2) {
store.commit('refresh');
storeMain.doRefresh();
if (d[0] === 'tag') {
window.$message.warning('');
} else {

@ -1,12 +1,12 @@
<template>
<div>
<div class="compose-wrap" v-if="store.state.userInfo.id > 0">
<div class="compose-wrap" v-if="userInfo.id > 0">
<div class="compose-line">
<div class="compose-user">
<n-avatar
round
:size="30"
:src="store.state.userInfo.avatar"
:src="userInfo.avatar"
/>
</div>
<n-mention
@ -168,13 +168,15 @@
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { TOKEN_KEY, useStoreUser } from '@/store/user';
import { debounce } from 'lodash';
import { ImageOutline } from '@vicons/ionicons5';
import { createComment } from '@/api/post';
import { getSuggestUsers } from '@/api/user';
import { parsePostTag } from '@/utils/content';
import type { MentionOption, UploadFileInfo, UploadInst } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const emit = defineEmits<{
(e: 'post-success'): void;
@ -190,7 +192,9 @@ const props = withDefaults(
},
);
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { userInfo } = storeToRefs(storeUser);
const optionsRef = ref<MentionOption[]>([]);
const showBtn = ref(false);
@ -210,11 +214,11 @@ const defaultCommentMaxLength = Number(
const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment';
const uploadToken = computed(() => {
return 'Bearer ' + localStorage.getItem('PAOPAO_TOKEN');
return 'Bearer ' + localStorage.getItem(TOKEN_KEY);
});
// 加载at用户列表
const loadSuggestionUsers = debounce((k) => {
getSuggestUsers({
Api.v1.suggest.get.users({
k,
})
.then((res) => {
@ -387,8 +391,8 @@ const submitPost = () => {
});
};
const triggerAuth = (key: string) => {
store.commit('triggerAuth', true);
store.commit('triggerAuthKey', key);
storeMain.triggerAuth(true);
storeMain.triggerAuthKey(key);
};
</script>

@ -5,34 +5,34 @@
{{ formatPrettyTime(comment.created_on) }}
</span>
<div class="actions">
<div v-if="!store.state.userLogined" class="action-item">
<div v-if="!userLogined" class="action-item">
<n-icon size="medium">
<thumb-up-outlined />
</n-icon>
<span class="upvote-count">{{ thumbsUpCount }}</span>
</div>
<div v-if="store.state.userLogined" class="action-item hover" @click.stop="handleThumbsUp">
<div v-if="userLogined" class="action-item hover" @click.stop="handleThumbsUp">
<n-icon size="medium">
<thumb-up-outlined v-if="!hasThumbsUp" />
<thumb-up-twotone v-if="hasThumbsUp" class="show" />
</n-icon>
<span class="upvote-count">{{ thumbsUpCount>0 ? thumbsUpCount : "赞" }}</span>
</div>
<div v-if="!store.state.userLogined" class="action-item">
<div v-if="!userLogined" class="action-item">
<n-icon size="medium">
<thumb-down-outlined />
</n-icon>
</div>
<div v-if="store.state.userLogined" class="action-item hover" @click.stop="handleThumbsDown">
<div v-if="userLogined" class="action-item hover" @click.stop="handleThumbsDown">
<n-icon size="medium">
<thumb-down-outlined v-if="!hasThumbsDown" />
<thumb-down-twotone v-if="hasThumbsDown" class="show" />
</n-icon>
</div>
<span class="show reply-btn" v-if="store.state.userLogined && !showReply" @click="switchReply(true)">
<span class="show reply-btn" v-if="userLogined && !showReply" @click="switchReply(true)">
</span>
<span class="hide reply-btn" v-if="store.state.userLogined && showReply" @click="switchReply(false)">
<span class="hide reply-btn" v-if="userLogined && showReply" @click="switchReply(false)">
</span>
</div>
@ -55,7 +55,8 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useStoreUser } from '@/store/user';
import { formatPrettyTime } from '@/utils/formatTime';
import {
createCommentReply,
@ -70,6 +71,7 @@ import {
ThumbDownOutlined,
} from '@vicons/material';
import { YesNoEnum } from '@/utils/IEnum';
import { storeToRefs } from 'pinia';
const props = withDefaults(
defineProps<{
@ -82,7 +84,11 @@ const props = withDefaults(
atUsername: '',
},
);
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { userLogined } = storeToRefs(storeUser);
const emit = defineEmits<{
(e: 'reload'): void;
(e: 'reset'): void;

@ -1,12 +1,12 @@
<template>
<div>
<div class="compose-wrap" v-if="store.state.userInfo.id > 0">
<div class="compose-wrap" v-if="userInfo.id > 0">
<div class="compose-line">
<div class="compose-user">
<n-avatar
round
:size="30"
:src="store.state.userInfo.avatar"
:src="userInfo.avatar"
/>
</div>
<n-mention
@ -75,7 +75,7 @@
</n-upload-trigger>
<n-upload-trigger
v-if="store.state.profile.allowTweetVideo"
v-if="profile.allowTweetVideo"
#="{ handleClick }" abstract>
<n-button
:disabled="
@ -105,7 +105,7 @@
</n-upload-trigger>
<n-upload-trigger
v-if="store.state.profile.allowTweetAttachment"
v-if="profile.allowTweetAttachment"
#="{ handleClick }" abstract>
<n-button
:disabled="
@ -171,7 +171,7 @@
:show-indicator="false"
status="success"
:stroke-width="10"
:percentage="(content.length / store.state.profile.defaultTweetMaxLength) * 100"
:percentage="(content.length / profile.defaultTweetMaxLength) * 100"
/>
</template>
{{ content.length }}
@ -196,7 +196,7 @@
v-if="attachmentContents.length > 0"
>
<n-input-number
v-if="store.state.profile.allowTweetAttachmentPrice"
v-if="profile.allowTweetAttachmentPrice"
v-model:value="attachmentPrice"
:min="0"
:max="100000"
@ -239,7 +239,7 @@
<div class="login-wrap">
<span class="login-banner"> </span>
</div>
<div v-if="!store.state.profile.allowUserRegister" class="login-only-wrap">
<div v-if="!profile.allowUserRegister" class="login-only-wrap">
<n-button
strong
secondary
@ -250,7 +250,7 @@
</n-button>
</div>
<div v-if="store.state.profile.allowUserRegister" class="login-wrap">
<div v-if="profile.allowUserRegister" class="login-wrap">
<n-button
strong
secondary
@ -276,9 +276,10 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { TOKEN_KEY, useStoreUser } from '@/store/user';
import { useStoreProfile } from '@/store/profile';
import { debounce } from 'lodash';
import { getSuggestUsers, getSuggestTags } from '@/api/user';
import {
ImageOutline,
@ -292,12 +293,18 @@ import { parsePostTag } from '@/utils/content';
import { isZipFile } from '@/utils/isZipFile';
import type { MentionOption, UploadFileInfo, UploadInst } from 'naive-ui';
import { VisibilityEnum, PostItemTypeEnum } from '@/utils/IEnum';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const emit = defineEmits<{
(e: 'post-success', post: Item.PostProps): void;
}>();
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const storeProfile = useStoreProfile();
const { userInfo } = storeToRefs(storeUser);
const { profile } = storeToRefs(storeProfile);
const optionsRef = ref<MentionOption[]>([]);
const loading = ref(false);
@ -323,7 +330,7 @@ const allowTweetVisibility = ref(
const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment';
const uploadToken = computed(() => {
return 'Bearer ' + localStorage.getItem('PAOPAO_TOKEN');
return 'Bearer ' + localStorage.getItem(TOKEN_KEY);
});
const visibilities = computed(() => {
@ -332,7 +339,7 @@ const visibilities = computed(() => {
{ value: VisibilityEnum.PRIVATE, label: '' },
{ value: VisibilityEnum.Following, label: '' },
];
if (store.state.profile.useFriendship) {
if (profile.value.useFriendship) {
res.push({ value: VisibilityEnum.FRIEND, label: '' });
}
return res;
@ -354,7 +361,7 @@ const switchEye = () => {
// 加载at用户列表
const loadSuggestionUsers = debounce((k) => {
getSuggestUsers({
Api.v1.suggest.get.users({
k,
})
.then((res) => {
@ -375,7 +382,7 @@ const loadSuggestionUsers = debounce((k) => {
// 加载推荐tag列表
const loadSuggestionTags = debounce((k) => {
getSuggestTags({
Api.v1.suggest.get.tags({
k,
})
.then((res) => {
@ -406,8 +413,8 @@ const handleSearch = (k: string, prefix: string) => {
}
};
const changeContent = (v: string) => {
if (v.length > store.state.profile.defaultTweetMaxLength) {
content.value = v.substring(0, store.state.profile.defaultTweetMaxLength);
if (v.length > profile.value.defaultTweetMaxLength) {
content.value = v.substring(0, profile.value.defaultTweetMaxLength);
} else {
content.value = v;
}
@ -623,12 +630,12 @@ const submitPost = () => {
});
};
const triggerAuth = (key: string) => {
store.commit('triggerAuth', true);
store.commit('triggerAuthKey', key);
storeMain.triggerAuth(true);
storeMain.triggerAuthKey(key);
};
onMounted(() => {
const defaultVisibility = store.state.profile.defaultTweetVisibility;
if (store.state.profile.useFriendship && defaultVisibility === 'friend') {
const defaultVisibility = profile.value.defaultTweetVisibility;
if (profile.value.useFriendship && defaultVisibility === 'friend') {
defaultVisitType.value = VisibilityEnum.FRIEND;
} else if (defaultVisibility === 'following') {
defaultVisitType.value = VisibilityEnum.Following;

@ -57,9 +57,8 @@
<script setup lang="ts">
import { h, computed } from 'vue';
import { NIcon } from 'naive-ui';
import { NIcon, DropdownOption } from 'naive-ui';
import type { Component } from 'vue';
import { DropdownOption } from 'naive-ui';
import { formatDate } from '@/utils/formatTime';
import { MoreHorizFilled } from '@vicons/material';
import { PaperPlaneOutline } from '@vicons/ionicons5';

@ -57,13 +57,12 @@
<script setup lang="ts">
import { h, computed } from 'vue';
import { NIcon } from 'naive-ui';
import type { Component } from 'vue';
import { useDialog, DropdownOption } from 'naive-ui';
import { followUser, unfollowUser } from '@/api/user';
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';
const dialog = useDialog();
@ -91,7 +90,7 @@ const handleFollowUser = () => {
negativeText: '',
onPositiveClick: () => {
if (props.contact.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: props.contact.user_id,
})
.then((_res) => {
@ -102,7 +101,7 @@ const handleFollowUser = () => {
console.log(err);
});
} else {
followUser({
Api.v1.user.post.follow({
user_id: props.contact.user_id,
})
.then((_res) => {

@ -1,5 +1,5 @@
<template>
<div v-if="store.state.drawerModelShow">
<div v-if="drawerModelShow">
<n-drawer
v-model:show="activeDrawerRef"
:width=212
@ -16,7 +16,7 @@
<div class="navbar">
<n-button
class="drawer-btn"
v-if="store.state.drawerModelShow && !back"
v-if="drawerModelShow && !back"
@click="activeDrawer"
quaternary
circle
@ -43,7 +43,7 @@
<n-switch
v-if="props.theme"
:value="store.state.theme === 'dark'"
:value="theme === 'dark'"
@update:value="switchTheme"
size="small"
class="theme-switch-wrap"
@ -62,7 +62,7 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRouter } from 'vue-router';
import { useMessage, useOsTheme, DrawerPlacement } from 'naive-ui';
import {
@ -71,8 +71,11 @@ import {
ChevronLeftRound,
DehazeRound,
} from '@vicons/material';
import { storeToRefs } from 'pinia';
const storeMain = useStoreMain();
const { desktopModelShow, drawerModelShow, theme } = storeToRefs(storeMain);
const store = useStore();
const router = useRouter();
const activeDrawerRef = ref(false);
const placementRef = ref<DrawerPlacement>('left');
@ -92,10 +95,10 @@ const props = withDefaults(
const switchTheme = (theme: boolean) => {
if (theme) {
localStorage.setItem('PAOPAO_THEME', 'dark');
store.commit('triggerTheme', 'dark');
storeMain.triggerTheme('dark');
} else {
localStorage.setItem('PAOPAO_THEME', 'light');
store.commit('triggerTheme', 'light');
storeMain.triggerTheme('light');
}
};
const goBack = () => {
@ -116,8 +119,7 @@ onMounted(() => {
switchTheme((useOsTheme() as unknown as string) === 'dark');
}
// 移动端特殊处理
if (!store.state.desktopModelShow) {
window.$store = store;
if (!desktopModelShow.value) {
window.$message = useMessage();
}
});

@ -3,7 +3,7 @@
<n-thing content-indented>
<template #avatar>
<n-avatar round :size="30" :src="
message.type == 4 && message.sender_user_id == store.state.userInfo.id
message.type == 4 && message.sender_user_id == userInfo.id
? message.receiver_user.avatar
: ( message.sender_user.id > 0
? message.sender_user.avatar
@ -22,7 +22,7 @@
}">
{{ message.sender_user.nickname }}
</router-link>
<span v-if="store.state.desktopModelShow" class="username">
<span v-if="desktopModelShow" class="username">
@{{ message.sender_user.username }}
</span>
</span>
@ -35,7 +35,7 @@
}">
{{ message.receiver_user.nickname }}
</router-link>
<span v-if="store.state.desktopModelShow" class="username">
<span v-if="desktopModelShow" class="username">
@{{ message.receiver_user.username }}
</span>
</span>
@ -53,7 +53,7 @@
</template>
</n-tag>
<n-tag
v-if="message.type == 4 && message.receiver_user_id == store.state.userInfo.id"
v-if="message.type == 4 && message.receiver_user_id == userInfo.id"
class="top-tag"
type="warning"
size="small"
@ -139,37 +139,35 @@
<script setup lang="ts">
import { h, computed } from 'vue';
import type { Component } from 'vue';
import { NIcon, useDialog } from 'naive-ui';
import { useStore } from 'vuex';
import { NIcon, useDialog, DropdownOption } from 'naive-ui';
import { useStoreMain } from '@/store/main';
import { useStoreUser } from '@/store/user';
import { useRouter } from 'vue-router';
import { DropdownOption } from 'naive-ui';
import {
ShareOutline,
CheckmarkOutline,
CloseOutline,
CheckmarkDoneOutline,
} from '@vicons/ionicons5';
import {
readMessage,
addFriend,
rejectFriend,
followUser,
unfollowUser,
} from '@/api/user';
import { formatRelativeTime } from '@/utils/formatTime';
import { MoreHorizFilled } from '@vicons/material';
import {
PaperPlaneOutline,
CheckmarkCircle,
BodyOutline,
WalkOutline,
} from '@vicons/ionicons5';
import { formatRelativeTime } from '@/utils/formatTime';
import { MoreHorizFilled } from '@vicons/material';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const defaultavatar =
'https://assets.paopao.info/public/avatar/default/admin.png';
const router = useRouter();
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { desktopModelShow } = storeToRefs(storeMain);
const { userInfo } = storeToRefs(storeUser);
const dialog = useDialog();
const props = withDefaults(
defineProps<{
@ -189,7 +187,7 @@ const renderIcon = (icon: Component) => {
const actionOpts = computed(() => {
let user =
props.message.type == 4 &&
props.message.sender_user_id == store.state.userInfo.id
props.message.sender_user_id == userInfo.value.id
? props.message.receiver_user
: props.message.sender_user;
let options: DropdownOption[] = [
@ -199,7 +197,7 @@ const actionOpts = computed(() => {
icon: renderIcon(PaperPlaneOutline),
},
];
if (store.state.userInfo.id != user.id) {
if (userInfo.value.id != user.id) {
if (user.is_following) {
options.push({
label: ' @' + user.username,
@ -224,7 +222,7 @@ const emit = defineEmits<{
const onHandleFollowAction = (message: Item.MessageProps) => {
let user =
message.type == 4 && message.sender_user_id == store.state.userInfo.id
message.type == 4 && message.sender_user_id == userInfo.value.id
? message.receiver_user
: message.sender_user;
dialog.success({
@ -238,7 +236,7 @@ const onHandleFollowAction = (message: Item.MessageProps) => {
negativeText: '',
onPositiveClick: () => {
if (user.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: user.id,
})
.then((_res) => {
@ -251,7 +249,7 @@ const onHandleFollowAction = (message: Item.MessageProps) => {
})
.catch((_err) => {});
} else {
followUser({
Api.v1.user.post.follow({
user_id: user.id,
})
.then((_res) => {
@ -274,7 +272,7 @@ const handleAction = (item: 'whisper' | 'follow' | 'unfollow') => {
const message = props.message;
if (message.type != 99) {
let user =
message.type == 4 && message.sender_user_id == store.state.userInfo.id
message.type == 4 && message.sender_user_id == userInfo.value.id
? message.receiver_user
: message.sender_user;
emit('send-whisper', user);
@ -292,21 +290,21 @@ const handleAction = (item: 'whisper' | 'follow' | 'unfollow') => {
const isNotWhisperSender = computed(() => {
return (
props.message.type !== 4 ||
props.message.sender_user_id !== store.state.userInfo.id
props.message.sender_user_id !== userInfo.value.id
);
});
const isWhisperReceiver = computed(() => {
return (
props.message.type == 4 &&
props.message.receiver_user_id == store.state.userInfo.id
props.message.receiver_user_id == userInfo.value.id
);
});
const isWhisperSender = computed(() => {
return (
props.message.type == 4 &&
props.message.sender_user_id == store.state.userInfo.id
props.message.sender_user_id == userInfo.value.id
);
});
@ -328,7 +326,7 @@ const viewDetail = (message: Item.MessageProps) => {
const agreeAddFriend = (message: Item.MessageProps) => {
handleReadMessage(message);
addFriend({
Api.v1.friend.post.add({
user_id: message.sender_user_id,
})
.then((res) => {
@ -342,7 +340,7 @@ const agreeAddFriend = (message: Item.MessageProps) => {
const rejectAddFriend = (message: Item.MessageProps) => {
handleReadMessage(message);
rejectFriend({
Api.v1.friend.post.reject({
user_id: message.sender_user_id,
})
.then((res) => {
@ -355,11 +353,11 @@ const rejectAddFriend = (message: Item.MessageProps) => {
};
const handleReadMessage = (message: Item.MessageProps) => {
if (props.message.receiver_user_id != store.state.userInfo.id) {
if (props.message.receiver_user_id != userInfo.value.id) {
return;
}
if (message.is_read === 0) {
readMessage({
Api.v1.user.message.post.read({
id: message.id,
})
.then((_res) => {

@ -76,7 +76,7 @@
:key="content.id"
class="post-text"
@click.stop="doClickText($event, post.id)"
v-html="preparePost(content.content, '展开', '收起', store.state.profile.tweetMobileEllipsisSize, inFoldStyle)"
v-html="preparePost(content.content, '展开', '收起', profile.tweetMobileEllipsisSize, inFoldStyle)"
></span>
</div>
</template>
@ -129,9 +129,8 @@
<script setup lang="ts">
import { h, ref, computed } from 'vue';
import type { Component } from 'vue';
import { NIcon } from 'naive-ui';
import { useStore } from 'vuex';
import type { DropdownOption } from 'naive-ui';
import { NIcon, DropdownOption } from 'naive-ui';
import { useStoreMain } from '@/store/main';
import { useRouter } from 'vue-router';
import { formatPrettyDate } from '@/utils/formatTime';
import { preparePost } from '@/utils/content';
@ -149,9 +148,15 @@ import {
} from '@vicons/ionicons5';
import { MoreHorizFilled } from '@vicons/material';
import copy from 'copy-to-clipboard';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
const router = useRouter();
const store = useStore();
const storeMain = useStoreMain();
const storeProfile = useStoreProfile();
const { profile } = storeToRefs(storeProfile);
const inFoldStyle = ref<boolean>(true);
const props = withDefaults(
defineProps<{
@ -358,7 +363,7 @@ const doClickText = (e: MouseEvent, id: number) => {
if (detail && detail !== 'post') {
const d = detail.split(':');
if (d.length === 2) {
store.commit('refresh');
storeMain.doRefresh();
if (d[0] === 'tag') {
router.push({
name: 'home',

@ -38,7 +38,7 @@
<script setup lang="ts">
import { h, ref } from 'vue';
import { CloudDownloadOutline } from '@vicons/ionicons5';
import { precheckAttachment, getAttachment } from '@/api/user';
import { Api } from '@/utils/request';
const props = withDefaults(
defineProps<{
@ -71,9 +71,9 @@ const download = (attachment: Item.PostItemProps) => {
),
]);
precheckAttachment({
id: attachmentID.value,
})
Api.v1.attachment.get.precheck({
id: attachmentID.value,
})
.then((res) => {
if (res.paid) {
downloadTip.value = () =>
@ -88,7 +88,7 @@ const download = (attachment: Item.PostItemProps) => {
}
};
const execDownloadAction = () => {
getAttachment({
Api.v1.attachment.get._self({
id: attachmentID.value,
})
.then((res) => {

@ -163,7 +163,7 @@
<n-divider vertical />
{{ post.ip_loc }}
</span>
<span v-if="!store.state.collapsedLeft && post.created_on != post.latest_replied_on">
<span v-if="!collapsedLeft && post.created_on != post.latest_replied_on">
<n-divider vertical />
{{ formatPrettyTime(post.latest_replied_on) }}
</span>
@ -218,7 +218,7 @@
import { h, ref, onMounted, computed } from 'vue';
import type { Component } from 'vue';
import { NIcon, useDialog } from 'naive-ui';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRouter } from 'vue-router';
import { formatPrettyTime } from '@/utils/formatTime';
import { parsePostTag } from '@/utils/content';
@ -253,15 +253,21 @@ import {
highlightPost,
visibilityPost,
} from '@/api/post';
import { followUser, unfollowUser } from '@/api/user';
import type { DropdownOption } from 'naive-ui';
import { VisibilityEnum } from '@/utils/IEnum';
import copy from 'copy-to-clipboard';
import { storeToRefs } from 'pinia';
import { useStoreUser } from '@/store/user';
import { Api } from '@/utils/request';
const useFriendship =
import.meta.env.VITE_USE_FRIENDSHIP.toLowerCase() === 'true';
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { collapsedLeft } = storeToRefs(storeMain);
const { userInfo } = storeToRefs(storeUser);
const router = useRouter();
const dialog = useDialog();
const hasStarred = ref(false);
@ -361,8 +367,8 @@ const renderIcon = (icon: Component) => {
const adminOptions = computed(() => {
let options: DropdownOption[] = [];
if (
!store.state.userInfo.is_admin &&
store.state.userInfo.id != props.post.user.id
!userInfo.value.is_admin &&
userInfo.value.id != props.post.user.id
) {
options.push({
label: ' @' + props.post.user.username,
@ -402,7 +408,7 @@ const adminOptions = computed(() => {
icon: renderIcon(LockOpenOutline),
});
}
if (store.state.userInfo.is_admin) {
if (userInfo.value.is_admin) {
if (post.value.is_top === 0) {
options.push({
label: '',
@ -496,7 +502,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
negativeText: '',
onPositiveClick: () => {
if (post.user.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: post.user.id,
})
.then((_res) => {
@ -505,7 +511,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
})
.catch((_err) => {});
} else {
followUser({
Api.v1.user.post.follow({
user_id: post.user.id,
})
.then((_res) => {
@ -530,7 +536,7 @@ const doClickText = (e: MouseEvent, id: number) => {
if ((e.target as any).dataset.detail) {
const d = (e.target as any).dataset.detail.split(':');
if (d.length === 2) {
store.commit('refresh');
storeMain.doRefresh();
if (d[0] === 'tag') {
router.push({
name: 'home',
@ -621,7 +627,7 @@ const execDelAction = () => {
router.replace('/');
setTimeout(() => {
store.commit('refresh');
storeMain.doRefresh();
}, 50);
})
.catch((_err) => {
@ -744,7 +750,7 @@ const handlePostShare = () => {
};
onMounted(() => {
if (store.state.userInfo.id > 0) {
if (userInfo.value.id > 0) {
getPostStar({
id: post.value.id,
})

@ -75,7 +75,7 @@
:key="content.id"
class="post-text hover"
@click.stop="doClickText($event, post.id)"
v-html="preparePost(content.content, '展开', '收起', store.state.profile.tweetWebEllipsisSize, inFoldStyle)"
v-html="preparePost(content.content, '展开', '收起', profile.tweetWebEllipsisSize, inFoldStyle)"
></span>
</template>
@ -126,7 +126,7 @@
<script setup lang="ts">
import { h, ref, computed } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRouter } from 'vue-router';
import { NIcon } from 'naive-ui';
import type { Component } from 'vue';
@ -147,9 +147,15 @@ import {
} from '@vicons/ionicons5';
import { MoreHorizFilled } from '@vicons/material';
import copy from 'copy-to-clipboard';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
const router = useRouter();
const store = useStore();
const storeMain = useStoreMain();
const storeProfile = useStoreProfile();
const { profile } = storeToRefs(storeProfile);
const inFoldStyle = ref<boolean>(true);
const props = withDefaults(
defineProps<{
@ -353,7 +359,7 @@ const doClickText = (e: MouseEvent, id: number) => {
if (detail && detail !== 'post') {
const d = detail.split(':');
if (d.length === 2) {
store.commit('refresh');
storeMain.doRefresh();
if (d[0] === 'tag') {
router.push({
name: 'home',

@ -22,8 +22,8 @@
<div class="timestamp">
{{ props.reply.ip_loc }}
<n-popconfirm v-if="
store.state.userInfo.is_admin ||
store.state.userInfo.id === props.reply.user.id
userInfo.is_admin ||
userInfo.id === props.reply.user.id
" negative-text="" positive-text="" @positive-click="execDelAction">
<template #trigger>
<n-button quaternary circle size="tiny" class="del-btn">
@ -51,31 +51,31 @@
</span>
<div class="actions">
<div v-if="!store.state.userLogined" class="action-item" @click.stop="">
<div v-if="!userLogined" class="action-item" @click.stop="">
<n-icon size="medium">
<thumb-up-outlined />
</n-icon>
<span class="upvote-count">{{ thumbsUpCount }}</span>
</div>
<div v-if="store.state.userLogined" class="action-item hover" @click.stop="handleThumbsUp">
<div v-if="userLogined" class="action-item hover" @click.stop="handleThumbsUp">
<n-icon size="medium">
<thumb-up-outlined v-if="!hasThumbsUp" />
<thumb-up-twotone v-if="hasThumbsUp" class="show" />
</n-icon>
<span class="upvote-count">{{ thumbsUpCount>0 ? thumbsUpCount : "赞" }}</span>
</div>
<div v-if="!store.state.userLogined" class="action-item">
<div v-if="!userLogined" class="action-item">
<n-icon size="medium">
<thumb-down-outlined />
</n-icon>
</div>
<div v-if="store.state.userLogined" class="action-item hover" @click.stop="handleThumbsDown">
<div v-if="userLogined" class="action-item hover" @click.stop="handleThumbsDown">
<n-icon size="medium">
<thumb-down-outlined v-if="!hasThumbsDown" />
<thumb-down-twotone v-if="hasThumbsDown" class="show" />
</n-icon>
</div>
<span v-if="store.state.userLogined" class="show opacity-item reply-btn" @click="focusReply"> </span>
<span v-if="userLogined" class="show opacity-item reply-btn" @click="focusReply"> </span>
</div>
</div>
</div>
@ -84,7 +84,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { Trash } from '@vicons/tabler';
import { formatPrettyTime } from '@/utils/formatTime';
import {
@ -99,6 +99,8 @@ import {
ThumbDownOutlined,
} from '@vicons/material';
import { YesNoEnum } from '@/utils/IEnum';
import { useStoreUser } from '@/store/user';
import { storeToRefs } from 'pinia';
const props = withDefaults(
defineProps<{
@ -107,7 +109,11 @@ const props = withDefaults(
}>(),
{},
);
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { userInfo, userLogined } = storeToRefs(storeUser);
const emit = defineEmits<{
(e: 'focusReply', reply: Item.ReplyProps): void;
(e: 'reload'): void;

@ -1,5 +1,5 @@
<template>
<div class="rightbar-wrap" v-if="!store.state.collapsedRight">
<div class="rightbar-wrap" v-if="!collapsedRight">
<div class="search-wrap">
<n-input
round
@ -58,27 +58,27 @@
</n-spin>
</n-card>
<n-card class="copyright-wrap" embedded :bordered="false" size="small">
<div class="copyright">&copy; {{ store.state.profile.copyrightTop }}</div>
<div class="copyright">&copy; {{ profile.copyrightTop }}</div>
<div>
<n-space>
<a
:href="store.state.profile.copyrightLeftLink"
:href="profile.copyrightLeftLink"
target="_blank"
class="hash-link"
>
{{ store.state.profile.copyrightLeft }}
{{ profile.copyrightLeft }}
</a>
<a
:href="store.state.profile.copyrightRightLink"
:href="profile.copyrightRightLink"
target="_blank"
class="hash-link"
>
{{ store.state.profile.copyrightRight }}
{{ profile.copyrightRight }}
</a>
</n-space>
</div>
</n-card>
<div class="site-info" v-if="store.state.userInfo.is_admin" ref="userInfoElement">
<div class="site-info" v-if="userInfo.is_admin" ref="userInfoElement">
<span class="site-info-item">{{ registerUserCount }} {{ onlineUserCount }} 线线 {{ historyMaxOnline }} 线 {{ formatRelativeTime(serverUpTime) }}</span>
</div>
</div>
@ -86,18 +86,28 @@
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRouter } from 'vue-router';
import { getTags } from '@/api/post';
import { getSiteInfo } from '@/api/user';
import { Search } from '@vicons/ionicons5';
import { formatRelativeTime } from '@/utils/formatTime';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
import { useStoreUser } from '@/store/user';
const hotTags = ref<Item.TagProps[]>([]);
const followTags = ref<Item.TagProps[]>([]);
const loading = ref(false);
const keyword = ref('');
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const storeProfile = useStoreProfile();
const { collapsedRight, refreshTopicFollow } = storeToRefs(storeMain);
const { userInfo, userLogined } = storeToRefs(storeUser);
const { profile } = storeToRefs(storeProfile);
const router = useRouter();
const registerUserCount = ref(0);
const onlineUserCount = ref(0);
@ -112,7 +122,7 @@ const rightHotTopicMaxSize = Number(
);
const loadSiteInfo = () => {
getSiteInfo()
Api.v1.admin.get.site.status()
.then((res) => {
registerUserCount.value = res.register_user_count;
onlineUserCount.value = res.online_user_count;
@ -157,7 +167,7 @@ const handleSearch = () => {
};
const showFollowTopics = computed({
get: () => {
return store.state.userLogined && followTags.value.length !== 0;
return userLogined.value && followTags.value.length !== 0;
},
set: (newVal) => {
// do nothing
@ -165,14 +175,14 @@ const showFollowTopics = computed({
});
watch(
() => ({
refreshTopicFollow: store.state.refreshTopicFollow,
userLogined: store.state.userLogined,
refreshTopicFollow: refreshTopicFollow.value,
userLogined: userLogined.value,
}),
(to, from) => {
if (to.refreshTopicFollow !== from.refreshTopicFollow || to.userLogined) {
loadHotTags();
}
if (store.state.userInfo.is_admin) {
if (userInfo.value.is_admin) {
loadSiteInfo();
}
},
@ -192,7 +202,7 @@ const observer = new IntersectionObserver(
},
);
onMounted(() => {
// 不知道为什么 store.state.userInfo.is_admin 在这里就是不起作用f*k所以才用这么一种蹩脚的法子来凑合
// 不知道为什么 store.userInfo.is_admin 在这里就是不起作用f*k所以才用这么一种蹩脚的法子来凑合
if (userInfoElement.value) {
observer.observe(userInfoElement.value);
}

@ -6,13 +6,13 @@
<n-menu :accordion="true" :icon-size="24" :options="menuOptions" :render-label="renderMenuLabel"
:render-icon="renderMenuIcon" :value="selectedPath" @update:value="goRouter" />
<div class="user-wrap" v-if="store.state.userInfo.id > 0">
<n-avatar class="user-avatar" round :size="34" :src="store.state.userInfo.avatar" />
<div class="user-wrap" v-if="userInfo.id > 0">
<n-avatar class="user-avatar" round :size="34" :src="userInfo.avatar" />
<div class="user-info">
<div class="nickname">
<span class="nickname-txt">
{{ store.state.userInfo.nickname }}
{{ userInfo.nickname }}
</span>
<n-button class="logout" quaternary circle size="tiny" @click="handleLogout">
<template #icon>
@ -22,7 +22,7 @@
</template>
</n-button>
</div>
<div class="username">@{{ store.state.userInfo.username }}</div>
<div class="username">@{{ userInfo.username }}</div>
</div>
<div class="user-mini-wrap">
@ -36,12 +36,12 @@
</div>
</div>
<div class="user-wrap" v-else>
<div v-if="!store.state.profile.allowUserRegister" class="login-only-wrap">
<div v-if="!profile.allowUserRegister" class="login-only-wrap">
<n-button strong secondary round type="primary" @click="triggerAuth('signin')">
</n-button>
</div>
<div v-if="store.state.profile.allowUserRegister" class="login-wrap">
<div v-if="profile.allowUserRegister" class="login-wrap">
<n-button strong secondary round type="primary" @click="triggerAuth('signin')">
</n-button>
@ -56,7 +56,7 @@
<script setup lang="ts">
import { h, ref, watch, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { NIcon, NBadge, useMessage } from 'naive-ui';
import {
HomeOutline,
@ -70,10 +70,19 @@ import {
LogOutOutline,
} from '@vicons/ionicons5';
import { Hash } from '@vicons/tabler';
import { getUnreadMsgCount } from '@/api/user';
import LOGO from '@/assets/img/logo.png';
import { useStoreUser } from '@/store/user';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const storeProfile = useStoreProfile();
const { unreadMsgCount } = storeToRefs(storeMain);
const { userInfo } = storeToRefs(storeUser);
const { profile } = storeToRefs(storeProfile);
const store = useStore();
const route = useRoute();
const router = useRouter();
const hasUnreadMsg = ref(false);
@ -86,29 +95,29 @@ const enableAnnoucement =
watch(route, () => {
selectedPath.value = route.name;
});
watch(store.state, () => {
hasUnreadMsg.value = store.state.unreadMsgCount > 0;
if (store.state.userInfo.id > 0) {
watch(() => [unreadMsgCount, userInfo, profile], () => {
hasUnreadMsg.value = unreadMsgCount.value > 0;
if (userInfo.value.id > 0) {
if (!msgLoop.value) {
getUnreadMsgCount()
Api.v1.user.get.msgcount.unread({})
.then((res) => {
hasUnreadMsg.value = res.count > 0;
store.commit('updateUnreadMsgCount', res.count);
storeMain.updateUnreadMsgCount(res.count);
})
.catch((err) => {
console.log(err);
});
msgLoop.value = setInterval(() => {
getUnreadMsgCount()
Api.v1.user.get.msgcount.unread({})
.then((res) => {
hasUnreadMsg.value = res.count > 0;
store.commit('updateUnreadMsgCount', res.count);
storeMain.updateUnreadMsgCount(res.count);
})
.catch((err) => {
console.log(err);
});
}, store.state.profile.defaultMsgLoopInterval);
}, profile.value.defaultMsgLoopInterval);
}
} else {
if (msgLoop.value) {
@ -118,8 +127,8 @@ watch(store.state, () => {
});
onMounted(() => {
window.onresize = () => {
store.commit('triggerCollapsedLeft', document.body.clientWidth <= 821);
store.commit('triggerCollapsedRight', document.body.clientWidth <= 821);
storeMain.triggerCollapsedLeft(document.body.clientWidth <= 821);
storeMain.triggerCollapsedRight(document.body.clientWidth <= 821);
};
});
const menuOptions = computed(() => {
@ -163,7 +172,7 @@ const menuOptions = computed(() => {
icon: () => h(BookmarksOutline),
href: '/collection',
});
if (store.state.profile.useFriendship) {
if (profile.value.useFriendship) {
options.push({
label: '',
key: 'contacts',
@ -171,7 +180,7 @@ const menuOptions = computed(() => {
href: '/contacts',
});
}
if (store.state.profile.enableWallet) {
if (profile.value.enableWallet) {
options.push({
label: '',
key: 'wallet',
@ -186,7 +195,7 @@ const menuOptions = computed(() => {
href: '/setting',
});
return store.state.userInfo.id > 0
return userInfo.value.id > 0
? options
: [
{
@ -248,20 +257,19 @@ const goRouter = (name: string, item: any = {}) => {
};
const goHome = () => {
if (route.path === '/') {
store.commit('refresh');
storeMain.doRefresh();
}
goRouter('home');
};
const triggerAuth = (key: string) => {
store.commit('triggerAuth', true);
store.commit('triggerAuthKey', key);
storeMain.triggerAuth(true);
storeMain.triggerAuthKey(key);
};
const handleLogout = () => {
store.commit('userLogout');
store.commit('refresh');
storeUser.userLogout();
storeMain.doRefresh();
goHome();
};
window.$store = store;
window.$message = useMessage();
</script>

@ -50,8 +50,8 @@
</template>
<script setup lang="ts">
import { Api } from '@/utils/request';
import { ref } from 'vue';
import { requestingFriend } from '@/api/user';
const props = withDefaults(
defineProps<{
@ -73,7 +73,7 @@ const closeModal = () => {
};
const sendWhisper = () => {
loading.value = true;
requestingFriend({
Api.v1.friend.post.requesting({
user_id: props.user.id,
greetings: content.value,
})

@ -50,8 +50,8 @@
</template>
<script setup lang="ts">
import { Api } from '@/utils/request';
import { ref } from 'vue';
import { sendUserWhisper } from '@/api/user';
const props = withDefaults(
defineProps<{
@ -73,7 +73,7 @@ const closeModal = () => {
};
const sendWhisper = () => {
loading.value = true;
sendUserWhisper({
Api.v1.user.post.whisper({
user_id: props.user.id,
content: content.value,
})

@ -1,6 +1,6 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import router from './router';
import store from './store';
import App from './App.vue';
import type { MessageApiInjection } from 'naive-ui/lib/message/src/MessageProvider';
@ -10,11 +10,12 @@ import 'vfonts/Lato.css';
// 等宽字体
import 'vfonts/FiraCode.css';
createApp(App).use(router).use(store).mount('#app');
const pinia = createPinia();
createApp(App).use(router).use(pinia).mount('#app');
declare global {
interface Window {
$message: MessageApiInjection;
$store: any;
}
}

@ -1,201 +0,0 @@
import { createStore } from 'vuex';
export default createStore({
state: {
refresh: Date.now(),
refreshTopicFollow: Date.now(),
theme: localStorage.getItem('PAOPAO_THEME'),
collapsedLeft: document.body.clientWidth <= 821,
collapsedRight: document.body.clientWidth <= 821,
drawerModelShow: document.body.clientWidth <= 821,
desktopModelShow: document.body.clientWidth > 821,
authModalShow: false,
authModelTab: 'signin',
unreadMsgCount: 0,
userLogined: false,
userInfo: {
id: 0,
username: '',
nickname: '',
created_on: 0,
follows: 0,
followings: 0,
tweets_count: 0,
is_admin: false,
},
profile: {
useFriendship: true,
enableTrendsBar: true,
enableWallet: false,
allowTweetAttachment: true,
allowTweetAttachmentPrice: true,
allowTweetVideo: true,
allowUserRegister: true,
allowPhoneBind: true,
defaultTweetMaxLength: 2000,
tweetWebEllipsisSize: 400,
tweetMobileEllipsisSize: 300,
defaultTweetVisibility: 'friend',
defaultMsgLoopInterval: 5000,
copyrightTop: '2023 paopao.info',
copyrightLeft: "Roc's Me",
copyrightLeftLink: '',
copyrightRight: '(PaoPao)',
copyrightRightLink: 'https://www.paopao.info',
},
},
mutations: {
refresh(state, refresh) {
state.refresh = refresh || Date.now();
},
refreshTopicFollow(state) {
state.refreshTopicFollow = Date.now();
},
updateUnreadMsgCount(state, count) {
state.unreadMsgCount = count;
},
triggerTheme(state, theme) {
state.theme = theme;
},
triggerAuth(state, status) {
state.authModalShow = status;
},
triggerAuthKey(state, key) {
state.authModelTab = key;
},
triggerCollapsedLeft(state, status) {
state.collapsedLeft = status;
state.drawerModelShow = status;
state.desktopModelShow = !status;
},
triggerCollapsedRight(state, status) {
state.collapsedRight = status;
},
updateUserinfo(state, data) {
state.userInfo = data;
if (state.userInfo.id > 0) {
state.userLogined = true;
}
},
loadDefaultSiteProfile(state) {
state.profile.useFriendship =
import.meta.env.VITE_USE_FRIENDSHIP.toLowerCase() === 'true';
state.profile.enableTrendsBar =
import.meta.env.VITE_ENABLE_TRENDS_BAR.toLowerCase() === 'true';
state.profile.enableWallet =
import.meta.env.VITE_ENABLE_WALLET.toLocaleLowerCase() === 'true';
state.profile.allowTweetAttachment =
import.meta.env.VITE_ALLOW_TWEET_ATTACHMENT.toLowerCase() === 'true';
state.profile.allowTweetAttachmentPrice =
import.meta.env.VITE_ALLOW_TWEET_ATTACHMENT_PRICE.toLowerCase() ===
'true';
state.profile.allowTweetVideo =
import.meta.env.VITE_ALLOW_TWEET_VIDEO.toLowerCase() === 'true';
state.profile.allowUserRegister =
import.meta.env.VITE_ALLOW_USER_REGISTER.toLowerCase() === 'true';
state.profile.allowPhoneBind =
import.meta.env.VITE_ALLOW_PHONE_BIND.toLowerCase() === 'true';
state.profile.defaultTweetMaxLength = Number(
import.meta.env.VITE_DEFAULT_TWEET_MAX_LENGTH,
);
state.profile.tweetWebEllipsisSize = Number(
import.meta.env.VITE_TWEET_WEB_ELLIPSIS_SIZE,
);
state.profile.tweetMobileEllipsisSize = Number(
import.meta.env.VITE_TWEET_MOBILE_ELLIPSIS_SIZE,
);
state.profile.defaultTweetVisibility =
import.meta.env.VITE_DEFAULT_TWEET_VISIBILITY.toLowerCase();
state.profile.defaultMsgLoopInterval = Number(
import.meta.env.VITE_DEFAULT_MSG_LOOP_INTERVAL,
);
state.profile.copyrightTop = import.meta.env.VITE_COPYRIGHT_TOP;
state.profile.copyrightLeft = import.meta.env.VITE_COPYRIGHT_LEFT;
state.profile.copyrightLeftLink =
import.meta.env.VITE_COPYRIGHT_LEFT_LINK;
state.profile.copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT;
state.profile.copyrightRightLink =
import.meta.env.VITE_COPYRIGHT_RIGHT_LINK;
},
updateSiteProfile(state, data) {
const p = state.profile;
state.profile.useFriendship = data.use_friendship ?? p.useFriendship;
state.profile.enableTrendsBar =
data.enable_trends_bar ?? p.enableTrendsBar;
state.profile.enableWallet = data.enable_wallet ?? p.enableWallet;
state.profile.allowTweetAttachment =
data.allow_tweet_attachment ?? p.allowTweetAttachment;
state.profile.allowTweetAttachmentPrice =
data.allow_tweet_attachment_price ?? p.allowTweetAttachmentPrice;
state.profile.allowTweetVideo =
data.allow_tweet_video ?? p.allowTweetVideo;
state.profile.allowUserRegister =
data.allow_user_register ?? p.allowUserRegister;
state.profile.allowPhoneBind = data.allow_phone_bind ?? p.allowPhoneBind;
state.profile.defaultTweetMaxLength =
data.default_tweet_max_length ?? p.defaultTweetMaxLength;
state.profile.tweetWebEllipsisSize =
data.tweet_web_ellipsis_size ?? p.tweetWebEllipsisSize;
state.profile.tweetMobileEllipsisSize =
data.tweet_mobile_ellipsis_size ?? p.tweetMobileEllipsisSize;
state.profile.defaultTweetVisibility =
data.default_tweet_visibility ?? p.defaultTweetVisibility;
state.profile.defaultMsgLoopInterval =
data.default_msg_loop_interval ?? p.defaultMsgLoopInterval;
state.profile.copyrightTop = data.copyright_top ?? p.copyrightTop;
state.profile.copyrightLeft = data.copyright_left ?? p.copyrightLeft;
state.profile.copyrightLeftLink =
data.copyright_left_link ?? p.copyrightLeftLink;
state.profile.copyrightRight = data.copyright_right ?? p.copyrightRight;
state.profile.copyrightRightLink =
data.copyright_right_link ?? p.copyrightRightLink;
},
userLogout(state) {
localStorage.removeItem('PAOPAO_TOKEN');
state.userInfo = {
id: 0,
nickname: '',
username: '',
created_on: 0,
follows: 0,
followings: 0,
tweets_count: 0,
is_admin: false,
};
state.userLogined = false;
},
},
actions: {},
modules: {},
});

@ -0,0 +1,70 @@
import { defineStore } from 'pinia';
import { reactive, ref } from 'vue';
export const useStoreMain = defineStore('main', () => {
const refresh = ref(Date.now());
const refreshTopicFollow = ref(Date.now());
const theme = ref(localStorage.getItem('PAOPAO_THEME'));
const collapsedLeft = ref(document.body.clientWidth <= 821);
const collapsedRight = ref(document.body.clientWidth <= 821);
const drawerModelShow = ref(document.body.clientWidth <= 821);
const desktopModelShow = ref(document.body.clientWidth > 821);
const authModalShow = ref(false);
const authModelTab = ref('signin');
const unreadMsgCount = ref(0);
function doRefresh(val?: number) {
refresh.value = val || Date.now();
}
function doRefreshTopicFollow() {
refreshTopicFollow.value = Date.now();
}
function updateUnreadMsgCount(count: number) {
unreadMsgCount.value = count;
}
function triggerTheme(t: string) {
theme.value = t;
}
function triggerAuth(status: boolean) {
authModalShow.value = status;
}
function triggerAuthKey(key: string) {
authModelTab.value = key;
}
function triggerCollapsedLeft(status: boolean) {
collapsedLeft.value = status;
drawerModelShow.value = status;
desktopModelShow.value = !status;
}
function triggerCollapsedRight(status: boolean) {
collapsedRight.value = status;
}
return {
refresh,
refreshTopicFollow,
theme,
collapsedLeft,
collapsedRight,
drawerModelShow,
desktopModelShow,
authModalShow,
authModelTab,
unreadMsgCount,
doRefresh,
doRefreshTopicFollow,
updateUnreadMsgCount,
triggerTheme,
triggerAuth,
triggerAuthKey,
triggerCollapsedLeft,
triggerCollapsedRight,
};
});

@ -0,0 +1,131 @@
import { defineStore } from "pinia";
import { ref } from "vue";
export const useStoreProfile = defineStore("profile", () => {
const profile = ref({
useFriendship: true,
enableTrendsBar: true,
enableWallet: false,
allowTweetAttachment: true,
allowTweetAttachmentPrice: true,
allowTweetVideo: true,
allowUserRegister: true,
allowPhoneBind: true,
defaultTweetMaxLength: 2000,
tweetWebEllipsisSize: 400,
tweetMobileEllipsisSize: 300,
defaultTweetVisibility: 'friend',
defaultMsgLoopInterval: 5000,
copyrightTop: '2023 paopao.info',
copyrightLeft: "Roc's Me",
copyrightLeftLink: '',
copyrightRight: '(PaoPao)',
copyrightRightLink: 'https://www.paopao.info',
});
function loadDefaultSiteProfile() {
profile.value.useFriendship =
import.meta.env.VITE_USE_FRIENDSHIP.toLowerCase() === 'true';
profile.value.enableTrendsBar =
import.meta.env.VITE_ENABLE_TRENDS_BAR.toLowerCase() === 'true';
profile.value.enableWallet =
import.meta.env.VITE_ENABLE_WALLET.toLowerCase() === 'true';
profile.value.allowTweetAttachment =
import.meta.env.VITE_ALLOW_TWEET_ATTACHMENT.toLowerCase() === 'true';
profile.value.allowTweetAttachmentPrice =
import.meta.env.VITE_ALLOW_TWEET_ATTACHMENT_PRICE.toLowerCase() === 'true';
profile.value.allowTweetVideo =
import.meta.env.VITE_ALLOW_TWEET_VIDEO.toLowerCase() === 'true';
profile.value.allowUserRegister =
import.meta.env.VITE_ALLOW_USER_REGISTER.toLowerCase() === 'true';
profile.value.allowPhoneBind =
import.meta.env.VITE_ALLOW_PHONE_BIND.toLowerCase() === 'true';
profile.value.defaultTweetMaxLength = Number(
import.meta.env.VITE_DEFAULT_TWEET_MAX_LENGTH,
);
profile.value.tweetWebEllipsisSize = Number(
import.meta.env.VITE_TWEET_WEB_ELLIPSIS_SIZE,
);
profile.value.tweetMobileEllipsisSize = Number(
import.meta.env.VITE_TWEET_MOBILE_ELLIPSIS_SIZE,
);
profile.value.defaultTweetVisibility =
import.meta.env.VITE_DEFAULT_TWEET_VISIBILITY.toLowerCase();
profile.value.defaultMsgLoopInterval = Number(
import.meta.env.VITE_DEFAULT_MSG_LOOP_INTERVAL,
);
profile.value.copyrightTop = import.meta.env.VITE_COPYRIGHT_TOP;
profile.value.copyrightLeft = import.meta.env.VITE_COPYRIGHT_LEFT;
profile.value.copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK;
profile.value.copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT;
profile.value.copyrightRightLink = import.meta.env.VITE_COPYRIGHT_RIGHT_LINK;
}
function updateSiteProfile(data: Record<string, any>) {
profile.value.useFriendship = data.use_friendship ?? profile.value.useFriendship;
profile.value.enableTrendsBar = data.enable_trends_bar ?? profile.value.enableTrendsBar;
profile.value.enableWallet = data.enable_wallet ?? profile.value.enableWallet;
profile.value.allowTweetAttachment =
data.allow_tweet_attachment ?? profile.value.allowTweetAttachment;
profile.value.allowTweetAttachmentPrice =
data.allow_tweet_attachment_price ?? profile.value.allowTweetAttachmentPrice;
profile.value.allowTweetVideo = data.allow_tweet_video ?? profile.value.allowTweetVideo;
profile.value.allowUserRegister =
data.allow_user_register ?? profile.value.allowUserRegister;
profile.value.allowPhoneBind = data.allow_phone_bind ?? profile.value.allowPhoneBind;
profile.value.defaultTweetMaxLength =
data.default_tweet_max_length ?? profile.value.defaultTweetMaxLength;
profile.value.tweetWebEllipsisSize =
data.tweet_web_ellipsis_size ?? profile.value.tweetWebEllipsisSize;
profile.value.tweetMobileEllipsisSize =
data.tweet_mobile_ellipsis_size ?? profile.value.tweetMobileEllipsisSize;
profile.value.defaultTweetVisibility =
data.default_tweet_visibility ?? profile.value.defaultTweetVisibility;
profile.value.defaultMsgLoopInterval =
data.default_msg_loop_interval ?? profile.value.defaultMsgLoopInterval;
profile.value.copyrightTop = data.copyright_top ?? profile.value.copyrightTop;
profile.value.copyrightLeft = data.copyright_left ?? profile.value.copyrightLeft;
profile.value.copyrightLeftLink = data.copyright_left_link ?? profile.value.copyrightLeftLink;
profile.value.copyrightRight = data.copyright_right ?? profile.value.copyrightRight;
profile.value.copyrightRightLink =
data.copyright_right_link ?? profile.value.copyrightRightLink;
}
return {
profile,
loadDefaultSiteProfile, updateSiteProfile,
}
});

@ -0,0 +1,44 @@
import { defineStore } from "pinia";
import { computed, ref } from "vue";
/** 本地存储的用户令牌键名 */
export const TOKEN_KEY = 'PAOPAO_TOKEN';
export const useStoreUser = defineStore('user', () => {
const userInfo = ref<Record<string, any>>({
id: 0,
username: '',
nickname: '',
created_on: 0,
follows: 0,
followings: 0,
tweets_count: 0,
is_admin: false,
});
const userLogined = computed(() => userInfo.value.id > 0);
function updateUserinfo(data: Record<string, any>) {
userInfo.value = data;
}
function userLogout() {
localStorage.removeItem(TOKEN_KEY);
userInfo.value = {
id: 0,
nickname: '',
username: '',
created_on: 0,
follows: 0,
followings: 0,
tweets_count: 0,
is_admin: false,
};
}
return {
userInfo,
userLogined,
updateUserinfo, userLogout,
}
});

@ -0,0 +1,14 @@
interface Api {
v1: {
auth: Api.Auth.Api;
user: Api.User.Api;
friend: Api.Friend.Api;
captcha: Api.Captcha.Api;
attachment: Api.Attachment.Api;
suggest: Api.Suggest.Api;
admin: Api.Admin.Api;
}
}

@ -1,4 +1,4 @@
declare module Item {
declare namespace Item {
interface UserInfo {
/** 用户UID */
id: number;

@ -1,17 +1,4 @@
declare module NetParams {
interface AuthUserLogin {
/** 用户名 */
username: string;
/** 密码 */
password: string;
}
interface AuthUserRegister {
/** 用户名 */
username: string;
/** 密码 */
password: string;
}
declare namespace NetParams {
type AuthUserInfo = string;
@ -22,139 +9,13 @@ declare module NetParams {
old_password: string;
}
interface UserGetCollections {
page: number;
page_size: number;
}
interface UserPrecheckAttachment {
id: number;
}
interface UserGetAttachment {
id: number;
}
interface UserGetUnreadMsgCount {}
interface ReadMessageReq {
id: number;
}
interface UserGetMessages {
style: 'all' | 'system' | 'whisper' | 'requesting' | 'unread';
page: number;
page_size: number;
}
interface UserGetUserPosts {
username: string;
style: string;
page: number;
page_size: number;
}
interface UserGetUserProfile {
username: string;
}
interface UserGetBills {
page: number;
page_size: number;
}
interface UserStatusReq {
id: number;
status: number;
}
interface SiteInfoReq {}
interface FollowUserReq {
user_id: number;
}
interface UnfollowUserReq {
user_id: number;
}
interface UserReqRecharge {
amount: number;
}
interface UserGetRecharge {
id: number;
}
interface UserBindUserPhone {
phone: string;
captcha: string;
}
interface UserActivation {
activate_code: string;
captcha_id: string;
imgCaptcha: string;
}
interface UserGetCaptcha {}
interface UserWhisper {
user_id: number;
content: string;
}
interface RequestingFriend {
user_id: number;
greetings: string;
}
interface AddFriend {
user_id: number;
}
interface RejectFriend {
user_id: number;
}
interface DeleteFriend {
user_id: number;
}
interface GetContacts {
page: number;
page_size: number;
}
interface IndexTrendsReq {
page: number;
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;
/** 旧密码 */
old_password: string;
}
interface UserChangeNickname {
/** 昵称 */
nickname: string;
}
interface PostGetPost {
id: number;
}
@ -214,8 +75,6 @@ declare module NetParams {
page_size?: number;
}
interface GetContacts {}
interface PostCreatePost {
/** 帖子内容列表 */
contents: Partial<Item.PostItemProps>[];

@ -1,114 +1,13 @@
declare module NetReq {
interface AuthUserLogin {
token: string;
}
interface AuthUserRegister {
/** 用户UID */
id: number;
/** 用户名 */
username: string;
}
declare namespace NetReq {
type AuthUserInfo = Item.UserInfo;
interface AuthUpdateUserPassword {}
interface UserGetCollections {
/** 帖子列表 */
list: Item.PostProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface UserGetSuggestUsers {
suggest: string[];
}
interface UserGetSuggestTags {
suggest: string[];
}
interface UserPrecheckAttachment {
paid: number;
}
interface UserGetAttachment {
signed_url: string;
}
interface UserGetUnreadMsgCount {
count: number;
}
interface ReadMessageResp {}
interface ReadAllMessageResp {}
interface UserGetMessages {
/** 消息列表 */
list: Item.MessageProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface UserGetUserPosts {
/** 帖子列表 */
list: Item.PostProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
type UserGetUserProfile = Item.UserInfo;
interface UserGetBills {
list: Item.BillProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface UserReqRecharge {
id: number;
pay: string;
}
interface UserGetRecharge {
status: string;
}
interface UserBindUserPhone {}
interface UserGetCaptcha {
id: string;
/** 头像图片 base64 */
b64s: string;
}
interface UserChangeNickname {}
interface UserChangePassword {}
interface UserChangeStatus {}
interface SiteInfoResp {
register_user_count: number;
online_user_count: number;
history_max_online: number;
server_up_time: number;
}
interface FollowUserResp {}
interface UnfollowUserResp {}
interface AddFriend {}
interface DeleteFriend {}
interface RejectFriend {}
interface RequestingFriend {}
type PostGetPost = Item.PostProps;
interface PostGetPosts {
@ -186,13 +85,6 @@ declare module NetReq {
interface PostDeleteCommentReply {}
interface GetContacts {
/** 评论列表 */
list: Item.ContactItemProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface IndexTrendsResp {
list: Item.IndexTrendsItem[];
pager: Item.PagerProps;

@ -0,0 +1,42 @@
declare namespace Api {
namespace Admin {
interface Api {
post: {
user: {
/** 管理·用户禁言/解禁 */
status: (params: NetParams.UserStatusReq) => Promise<NetReq.UserChangeStatus>;
}
},
get: {
site: {
/** 获取站点状态信息 */
status: () => Promise<NetReq.SiteInfoResp>;
}
}
}
namespace NetParams {
interface UserStatusReq {
id: number;
status: number;
}
}
namespace NetReq {
interface UserChangeStatus {}
interface SiteInfoResp {
register_user_count: number;
online_user_count: number;
history_max_online: number;
server_up_time: number;
}
}
}
}

@ -0,0 +1,38 @@
declare namespace Api {
namespace Attachment {
interface Api {
get: {
/** 获取附件 */
_self: (params: NetParams.UserGetAttachment) => Promise<NetReq.UserGetAttachment>;
/** 获取附件 */
precheck: (params: NetParams.UserPrecheckAttachment) => Promise<NetReq.UserPrecheckAttachment>;
}
}
namespace NetParams {
interface UserGetAttachment {
id: number;
}
interface UserPrecheckAttachment {
id: number;
}
}
namespace NetReq {
interface UserGetAttachment {
signed_url: string;
}
interface UserPrecheckAttachment {
paid: number;
}
}
}
}

@ -0,0 +1,42 @@
declare namespace Api {
namespace Auth {
interface Api {
post: {
/** 用户登录 */
login: (params: NetParams.AuthUserLogin) => Promise<NetReq.AuthUserLogin>;
/** 用户注册 */
register: (params: NetParams.AuthUserRegister) => Promise<NetReq.AuthUserRegister>;
};
}
namespace NetParams {
interface AuthUserLogin {
/** 用户名 */
username: string;
/** 密码 */
password: string;
}
interface AuthUserRegister extends AuthUserLogin {
}
}
namespace NetReq {
interface AuthUserLogin {
token: string;
}
interface AuthUserRegister {
/** 用户UID */
id: number;
/** 用户名 */
username: string;
}
}
}
}

@ -0,0 +1,38 @@
declare namespace Api {
namespace Captcha {
interface Api {
get: {
/** 获取验证码 */
_self: (params: NetParams.UserGetCaptcha) => Promise<NetReq.UserGetCaptcha>;
},
post: {
/** 发送验证码 */
_self: (data: NetParams.UserPostCaptcha) => Promise<void>;
}
}
namespace NetParams {
interface UserGetCaptcha {}
interface UserPostCaptcha {
phone: string;
img_captcha: string;
img_captcha_id: string;
}
}
namespace NetReq {
interface UserGetCaptcha {
id: string;
/** 头像图片 base64 */
b64s: string;
}
}
}
}

@ -0,0 +1,44 @@
declare namespace Api {
namespace Friend {
interface Api {
post: {
/** 添加朋友申请 */
requesting: (data: NetParams.RequestingFriend) => Promise<NetReq.RequestingFriend>;
/** 添加好友 */
add: (data: NetParams.AddFriend) => Promise<NetReq.AddFriend>;
/** 拒绝朋友申请 */
reject: (data: NetParams.RejectFriend) => Promise<NetReq.RejectFriend>;
/** 删除好友 */
delete: (data: NetParams.DeleteFriend) => Promise<NetReq.DeleteFriend>;
}
}
namespace NetParams {
interface RequestingFriend {
user_id: number;
greetings: string;
}
interface AddFriend {
user_id: number;
}
interface RejectFriend extends AddFriend {}
interface DeleteFriend extends AddFriend {}
}
namespace NetReq {
interface RequestingFriend {}
interface AddFriend {}
interface RejectFriend {}
interface DeleteFriend {}
}
}
}

@ -0,0 +1,34 @@
declare namespace Api {
namespace Suggest {
interface Api {
get: {
/** 获取推荐用户 */
users: (params: NetParams.GetSuggestUsers) => Promise<NetReq.UserGetSuggestUsers>;
/** 获取推荐用户 */
tags: (params: NetParams.GetSuggestTags) => Promise<NetReq.UserGetSuggestTags>;
}
}
namespace NetParams {
interface GetSuggestUsers {
k: string
}
interface GetSuggestTags extends GetSuggestUsers {}
}
namespace NetReq {
interface UserGetSuggestUsers {
suggest: string[];
}
interface UserGetSuggestTags {
suggest: string[];
}
}
}
}

@ -0,0 +1,224 @@
declare namespace Api {
namespace User {
interface Api {
get: {
/** 获取用户基础信息 */
profile: (params: NetParams.UserGetUserProfile) => Promise<NetReq.UserGetUserProfile>;
/** 获取关注列表 */
follows: (params: NetParams.GetUserFollows) => Promise<NetReq.GetContacts>;
/** 获取粉丝列表 */
followings: (params: NetParams.GetUserFollowings) => Promise<NetReq.GetContacts>;
msgcount: {
/** 获取未读消息数 */
unread: (params: NetParams.UserGetUnreadMsgCount) => Promise<NetReq.UserGetUnreadMsgCount>;
};
/** 获取消息列表 */
messages: (params: NetParams.UserGetMessages) => Promise<NetReq.UserGetMessages>;
/** 获取收藏列表 */
collections: (params: NetParams.UserGetCollections) => Promise<NetReq.UserGetCollections>;
/** 获取用户帖子列表 */
posts: (params: NetParams.UserGetUserPosts) => Promise<NetReq.UserGetUserPosts>;
wallet: {
/** 获取账单列表 */
bills: (params: NetParams.UserGetBills) => Promise<NetReq.UserGetBills>;
}
/** 获取充值状态 */
recharge: (params: NetParams.UserGetRecharge) => Promise<NetReq.UserGetRecharge>;
},
post: {
/** 发送用户私信 */
whisper: (data: NetParams.UserWhisper) => Promise<NetParams.UserWhisper>;
/** 关注用户 */
follow: (data: NetParams.FollowUserReq) => Promise<NetReq.FollowUserResp>;
/** 取消关注用户 */
unfollow: (data: NetParams.UnfollowUserReq) => Promise<NetReq.UnfollowUserResp>;
/** 获取联系人列表 */
contacts: (params: NetParams.GetContacts) => Promise<NetReq.GetContacts>;
/** 绑定用户手机 */
phone: (data: NetParams.UserBindUserPhone) => Promise<NetParams.UserBindUserPhone>;
/** 激活码激活 */
activate: (data: NetParams.UserActivation) => Promise<NetParams.UserActivation>;
/** 更改密码 */
password: (data: NetParams.UserChangePassword) => Promise<NetReq.UserChangePassword>;
/** 更改昵称 */
nickname: (data: NetParams.UserChangeNickname) => Promise<NetReq.UserChangeNickname>;
/** 更改头像 */
avatar: (data: NetParams.UserChangeAvatar) => Promise<void>;
/** 发起充值请求 */
recharge: (data: NetParams.UserReqRecharge) => Promise<NetReq.UserReqRecharge>;
},
/** 信息相关 */
message: {
post: {
/** 阅读消息 */
read: (data: NetParams.ReadMessageReq) => Promise<NetReq.ReadMessageResp>;
/** 阅读所有消息 */
readall: () => Promise<NetReq.ReadAllMessageResp>;
}
}
}
namespace NetParams {
interface UserGetUserProfile {
username: string;
}
interface UserWhisper {
user_id: number;
content: string;
}
interface FollowUserReq {
user_id: number;
}
interface UnfollowUserReq extends FollowUserReq {}
interface GetUserFollows {
username: string;
page: number;
page_size: number;
}
interface GetUserFollowings extends GetUserFollows {}
interface GetContacts {
page: number;
page_size: number;
}
interface UserBindUserPhone {
phone: string;
captcha: string;
}
interface UserActivation {
activate_code: string;
captcha_id: string;
imgCaptcha: string;
}
interface UserChangePassword {
/** 新密码 */
password: string;
/** 旧密码 */
old_password: string;
}
interface UserChangeNickname {
/** 昵称 */
nickname: string;
}
interface UserChangeAvatar {
/** 头像图片 */
avatar: any;
}
interface UserGetUnreadMsgCount {}
interface UserGetMessages {
style: 'all' | 'system' | 'whisper' | 'requesting' | 'unread';
page: number;
page_size: number;
}
interface ReadMessageReq {
id: number;
}
interface UserGetCollections {
page: number;
page_size: number;
}
interface UserGetUserPosts {
username: string;
style: string;
page: number;
page_size: number;
}
interface UserGetBills {
page: number;
page_size: number;
}
interface UserReqRecharge {
amount: number;
}
interface UserGetRecharge {
id: number;
}
}
namespace NetReq {
type UserGetUserProfile = Item.UserInfo;
interface FollowUserResp {}
interface UnfollowUserResp {}
interface GetContacts {
/** 评论列表 */
list: Item.ContactItemProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface UserChangePassword {}
interface UserChangeNickname {}
interface UserGetUnreadMsgCount {
count: number;
}
interface UserGetMessages {
/** 消息列表 */
list: Item.MessageProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface ReadMessageResp {}
interface ReadAllMessageResp {}
interface UserGetCollections {
/** 帖子列表 */
list: Item.PostProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface UserGetUserPosts {
/** 帖子列表 */
list: Item.PostProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface UserGetBills {
list: Item.BillProps[];
/** 页码信息 */
pager: Item.PagerProps;
}
interface UserReqRecharge {
id: number;
pay: string;
}
interface UserGetRecharge {
status: string;
}
}
}
}

@ -1,4 +1,6 @@
import axios, { AxiosRequestConfig, AxiosRequestHeaders, Method } from 'axios';
import { useStoreMain } from '@/store/main';
import { TOKEN_KEY } from '@/store/user';
const service = axios.create({
baseURL: import.meta.env.VITE_HOST,
@ -6,50 +8,89 @@ const service = axios.create({
});
service.interceptors.request.use(
(config) => {
// 鉴权Header
if (localStorage.getItem('PAOPAO_TOKEN')) {
(config.headers as any)['Authorization'] =
'Bearer ' + localStorage.getItem('PAOPAO_TOKEN');
}
return config;
},
(error) => {
return Promise.reject(error);
},
(config) => {
// 鉴权Header
if (localStorage.getItem(TOKEN_KEY)) {
(config.headers as any)['Authorization'] =
'Bearer ' + localStorage.getItem(TOKEN_KEY);
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
service.interceptors.response.use(
(response) => {
const { data = {}, code = 0 } = response?.data || {};
if (+code === 0) {
return data || {};
} else {
Promise.reject(response?.data || {});
}
},
(error = {}) => {
const { response = {} } = error || {};
// 重定向
if (+response?.status === 401) {
localStorage.removeItem('PAOPAO_TOKEN');
if (response?.data.code !== 10005) {
window.$message.warning(response?.data.msg || '');
} else {
// 打开登录弹窗
window.$store.commit('triggerAuth', true);
}
} else {
window.$message.error(response?.data?.msg || '');
}
return Promise.reject(response?.data || {});
},
(response) => {
const { data = {}, code = 0 } = response?.data || {};
if (+code === 0) {
return data || {};
} else {
Promise.reject(response?.data || {});
}
},
(error = {}) => {
const { response = {} } = error || {};
// 重定向
if (+response?.status === 401) {
localStorage.removeItem(TOKEN_KEY);
if (response?.data.code !== 10005) {
window.$message.warning(response?.data.msg || '');
} else {
// 打开登录弹窗
useStoreMain().triggerAuth(true);
}
} else {
window.$message.error(response?.data?.msg || '');
}
return Promise.reject(response?.data || {});
},
);
export default service;
export function request<T, R>(config: AxiosRequestConfig<T>): Promise<R> {
return service(config) as unknown as Promise<R>;
return service(config) as unknown as Promise<R>;
}
/**
* API
*/
export function createApi<T>(): Readonly<T> {
const createProxy = (...names: string[]) => new Proxy((...args: any) => {
let _path: string[] = [];
let method: 'get' | 'post' | '' = '';
let methods = ['get', 'post'];
for (const name of names) {
const lowerName = name.toLowerCase();
if (methods.includes(lowerName) && !method) {
method = lowerName as any;
} else {
_path.push(name);
}
}
if (!method) method = 'get';
// 如果最后一条路径是 _self 则代表不需要它,直接去掉它
if (_path[_path.length - 1] === "_self") _path.pop();
return request({
method,
url: _path.join('/'),
data: args[0],
});
}, {
get(target: any, p: string) {
if (p === 'then') return undefined;
if (!target[p]) target[p] = createProxy(...names, p);
return target[p];
},
})
return createProxy();
}
export const Api = createApi<Api>();

@ -7,7 +7,7 @@
<template #footer>
<div class="pagination-wrap" v-if="totalPage > 1">
<n-pagination :page="page" @update:page="updatePage"
:page-slot="!store.state.collapsedRight ? 8 : 5" :page-count="totalPage" />
:page-slot="!collapsedRight ? 8 : 5" :page-count="totalPage" />
</div>
</template>
@ -44,11 +44,14 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRoute } from 'vue-router';
import { formatRelativeTime } from '@/utils/formatTime';
import { storeToRefs } from 'pinia';
const storeMain = useStoreMain();
const { collapsedRight } = storeToRefs(storeMain);
const store = useStore();
const route = useRoute();
const loading = ref(false);

@ -11,10 +11,10 @@
<n-empty size="large" description="暂无数据" />
</div>
<div v-if="store.state.desktopModelShow">
<div v-if="desktopModelShow">
<n-list-item v-for="post in list" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -23,7 +23,7 @@
<div v-else>
<n-list-item v-for="post in list" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -48,13 +48,19 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useStoreUser } from '@/store/user';
import { useRoute } from 'vue-router';
import { useDialog } from 'naive-ui';
import InfiniteLoading from 'v3-infinite-loading';
import { getCollections, followUser, unfollowUser } from '@/api/user';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { collapsedRight, desktopModelShow } = storeToRefs(storeMain);
const { userInfo } = storeToRefs(storeUser);
const store = useStore();
const route = useRoute();
const dialog = useDialog();
@ -97,7 +103,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
negativeText: '',
onPositiveClick: () => {
if (post.user.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: post.user.id,
})
.then((_res) => {
@ -106,7 +112,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
})
.catch((_err) => {});
} else {
followUser({
Api.v1.user.post.follow({
user_id: post.user.id,
})
.then((_res) => {
@ -129,7 +135,7 @@ function postFollowAction(userId: number, isFollowing: boolean) {
const loadPosts = () => {
loading.value = true;
getCollections({
Api.v1.user.get.collections({
page: page.value,
page_size: pageSize.value,
})

@ -33,9 +33,9 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getContacts } from '@/api/post';
import InfiniteLoading from 'v3-infinite-loading';
import { useRoute } from 'vue-router';
import { Api } from '@/utils/request';
const route = useRoute();
const loading = ref(false);
@ -86,7 +86,7 @@ const loadContacts = (scrollToBottom: boolean = false) => {
if (list.value.length === 0) {
loading.value = true;
}
getContacts({
Api.v1.user.post.contacts({
page: page.value,
page_size: pageSize.value,
})

@ -37,9 +37,9 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { getUserFollows, getUserFollowings } from '@/api/user';
import InfiniteLoading from 'v3-infinite-loading';
import { useRoute } from 'vue-router';
import { Api } from '@/utils/request';
const route = useRoute();
const loading = ref(false);
@ -119,7 +119,7 @@ const loadFollows = (username: string, scrollToBottom: boolean = false) => {
if (list.value.length === 0) {
loading.value = true;
}
getUserFollows({
Api.v1.user.get.follows({
username: username,
page: page.value,
page_size: pageSize.value,
@ -153,7 +153,7 @@ const loadFollowings = (username: string, scrollToBottom: boolean = false) => {
if (list.value.length === 0) {
loading.value = true;
}
getUserFollowings({
Api.v1.user.get.followings({
username: username,
page: page.value,
page_size: pageSize.value,

@ -59,10 +59,10 @@
<div class="empty-wrap" v-if="list.length === 0">
<n-empty size="large" description="暂无数据" />
</div>
<div v-if="store.state.desktopModelShow">
<div v-if="desktopModelShow">
<n-list-item v-for="post in list" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction"
@ -72,7 +72,7 @@
<div v-else>
<n-list-item v-for="post in list" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction"
@ -101,23 +101,27 @@
<script setup lang="ts">
import { ref, onMounted, reactive, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRoute, useRouter } from 'vue-router';
import { useDialog } from 'naive-ui';
import InfiniteLoading from 'v3-infinite-loading';
import { getPosts, getIndexTrends } from '@/api/post';
import {
getUserPosts,
deleteFriend,
followUser,
unfollowUser,
} from '@/api/user';
import SlideBar from '@opentiny/vue-slide-bar';
import allTweets from '@/assets/img/fresh-tweets.png';
import discoverTweets from '@/assets/img/discover-tweets.jpeg';
import followingTweets from '@/assets/img/following-tweets.jpeg';
import { useStoreUser } from '@/store/user';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const storeProfile = useStoreProfile();
const { desktopModelShow, refresh } = storeToRefs(storeMain);
const { userInfo } = storeToRefs(storeUser);
const { profile } = storeToRefs(storeProfile);
const store = useStore();
const route = useRoute();
const router = useRouter();
const dialog = useDialog();
@ -229,7 +233,7 @@ const openDeleteFriend = (post: Item.PostProps) => {
positiveText: '',
negativeText: '',
onPositiveClick: () => {
deleteFriend({
Api.v1.friend.post.delete({
user_id: user.id,
})
.then((res) => {
@ -270,7 +274,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
negativeText: '',
onPositiveClick: () => {
if (post.user.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: post.user.id,
})
.then((_res) => {
@ -279,7 +283,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
})
.catch((_err) => {});
} else {
followUser({
Api.v1.user.post.follow({
user_id: post.user.id,
})
.then((_res) => {
@ -313,17 +317,17 @@ const updateTitle = () => {
const showTrendsTag = computed(() => {
return (
store.state.userInfo.id > 0 &&
!store.state.profile.enableTrendsBar &&
store.state.desktopModelShow
userInfo.value.id > 0 &&
!profile.value.enableTrendsBar &&
desktopModelShow.value
);
});
const showTrendsBar = computed(() => {
return (
store.state.profile.useFriendship &&
store.state.profile.enableTrendsBar &&
store.state.desktopModelShow &&
store.state.userInfo.id > 0
profile.value.useFriendship &&
profile.value.enableTrendsBar &&
desktopModelShow.value &&
userInfo.value.id > 0
);
});
@ -366,9 +370,9 @@ const handleBarClick = (data: Item.SlideBarItem, index: number) => {
const loadContacts = () => {
slideBarList.value = slideBarList.value.slice(0, 3);
if (
!store.state.profile.useFriendship ||
!store.state.profile.enableTrendsBar ||
store.state.userInfo.id === 0
!profile.value.useFriendship ||
!profile.value.enableTrendsBar ||
userInfo.value.id === 0
) {
return;
}
@ -433,7 +437,7 @@ const loadPosts = (style: 'newest' | 'hots' | 'following' | 'search') => {
const loadUserPosts = () => {
loading.value = true;
getUserPosts({
Api.v1.user.get.posts({
username: targetUsername.value,
style: 'post',
page: page.value,
@ -544,7 +548,7 @@ watch(
() => ({
path: route.path,
query: route.query,
refresh: store.state.refresh,
refresh: refresh.value,
}),
(to, from) => {
updateTitle();

@ -13,7 +13,7 @@
<UnreadIcon />
</n-icon>
</template>
{{ store.state.unreadMsgCount }}
{{ unreadMsgCount }}
</n-button>
<n-divider vertical />
<n-button text size="small" :focusable="false" @click="handleReadAll"></n-button>
@ -67,10 +67,9 @@
import { h, ref, onMounted, computed } from 'vue';
import type { Component } from 'vue';
import { NIcon, DropdownOption } from 'naive-ui';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRoute } from 'vue-router';
import InfiniteLoading from 'v3-infinite-loading';
import { getMessages, readAllMessage } from '@/api/user';
import {
LayersOutline as AllIcon,
AtOutline as SystemIcon,
@ -79,8 +78,14 @@ import {
ChatbubbleEllipsesOutline as UnreadIcon,
OptionsOutline as OptionsIcon,
} from '@vicons/ionicons5';
import { useStoreUser } from '@/store/user';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { unreadMsgCount } = storeToRefs(storeMain);
const store = useStore();
const route = useRoute();
const loading = ref(false);
const noMore = ref(false);
@ -284,8 +289,8 @@ const handleUnreadMessage = () => {
};
const handleReadAll = () => {
if (store.state.unreadMsgCount > 0 && list.value.length > 0) {
readAllMessage()
if (unreadMsgCount.value > 0 && list.value.length > 0) {
Api.v1.user.message.post.readall()
.then((_res) => {
if (messageStyleVal.value != 'unread') {
for (let idx in list.value) {
@ -294,7 +299,7 @@ const handleReadAll = () => {
} else {
list.value = [];
}
store.commit('updateUnreadMsgCount', 0);
storeMain.updateUnreadMsgCount(0);
})
.catch((err) => {
console.log(err);
@ -318,7 +323,7 @@ const reloadMessages = () => {
const loadMessages = () => {
loading.value = true;
getMessages({
Api.v1.user.get.messages({
style: messageStyleVal.value,
page: page.value,
page_size: pageSize.value,

@ -5,25 +5,25 @@
<n-list
class="main-content-wrap profile-wrap"
bordered
v-if="store.state.userInfo.id > 0"
v-if="userInfo.id > 0"
>
<!-- -->
<!-- <n-spin :show="false" > -->
<div class="profile-baseinfo">
<div class="avatar">
<n-avatar :size="72" :src="store.state.userInfo.avatar" />
<n-avatar :size="72" :src="userInfo.avatar" />
</div>
<div class="base-info">
<div class="username">
<strong>{{ store.state.userInfo.nickname }}</strong>
<span> @{{ store.state.userInfo.username }} </span>
<n-tag v-if="store.state.userInfo.is_admin" class="top-tag" type="error" size="small" round>
<strong>{{ userInfo.nickname }}</strong>
<span> @{{ userInfo.username }} </span>
<n-tag v-if="userInfo.is_admin" class="top-tag" type="error" size="small" round>
</n-tag>
</div>
<div class="userinfo">
<span class="info-item">UID. {{ store.state.userInfo.id }} </span>
<span class="info-item">{{ formatDate(store.state.userInfo.created_on) }}&nbsp;</span>
<span class="info-item">UID. {{ userInfo.id }} </span>
<span class="info-item">{{ formatDate(userInfo.created_on) }}&nbsp;</span>
</div>
<div class="userinfo">
<span class="info-item">
@ -33,13 +33,13 @@
:to="{
name: 'following',
query: {
s: store.state.userInfo.username,
n: store.state.userInfo.nickname,
s: userInfo.username,
n: userInfo.nickname,
t: 'follows',
},
}"
>
&nbsp;&nbsp;{{ prettyQuoteNum(store.state.userInfo.follows) }}
&nbsp;&nbsp;{{ prettyQuoteNum(userInfo.follows) }}
</router-link>
</span>
<span class="info-item">
@ -49,17 +49,17 @@
:to="{
name: 'following',
query: {
s: store.state.userInfo.username,
n: store.state.userInfo.nickname,
s: userInfo.username,
n: userInfo.nickname,
t: 'followings',
},
}"
>
&nbsp;&nbsp;{{ prettyQuoteNum(store.state.userInfo.followings) }}
&nbsp;&nbsp;{{ prettyQuoteNum(userInfo.followings) }}
</router-link>
</span>
<span class="info-item">
&nbsp;&nbsp;{{ prettyQuoteNum(store.state.userInfo.tweets_count) }}
&nbsp;&nbsp;{{ prettyQuoteNum(userInfo.tweets_count) }}
</span>
</div>
</div>
@ -92,11 +92,11 @@
<div class="empty-wrap" v-if="list.length === 0">
<n-empty size="large" description="暂无数据" />
</div>
<div v-if="store.state.desktopModelShow">
<div v-if="desktopModelShow">
<div v-if="pageType === 'post'">
<n-list-item v-for="post in postList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -105,7 +105,7 @@
<div v-if="pageType === 'comment'">
<n-list-item v-for="post in commentList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -114,7 +114,7 @@
<div v-if="pageType === 'highlight'">
<n-list-item v-for="post in highlightList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -123,7 +123,7 @@
<div v-if="pageType === 'media'">
<n-list-item v-for="post in mediaList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -132,7 +132,7 @@
<div v-if="pageType === 'star'">
<n-list-item v-for="post in starList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -143,7 +143,7 @@
<div v-if="pageType === 'post'">
<n-list-item v-for="post in postList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -152,7 +152,7 @@
<div v-if="pageType === 'comment'">
<n-list-item v-for="post in commentList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -161,7 +161,7 @@
<div v-if="pageType === 'highlight'">
<n-list-item v-for="post in highlightList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -170,7 +170,7 @@
<div v-if="pageType === 'media'">
<n-list-item v-for="post in mediaList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -179,7 +179,7 @@
<div v-if="pageType === 'star'">
<n-list-item v-for="post in starList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -207,17 +207,23 @@
<script setup lang="ts">
import { h, ref, Component, onMounted, computed, watch } from 'vue';
import { NIcon } from 'naive-ui';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRoute, useRouter } from 'vue-router';
import { useDialog, DropdownOption } from 'naive-ui';
import { getUserPosts, followUser, unfollowUser } from '@/api/user';
import { formatDate } from '@/utils/formatTime';
import { prettyQuoteNum } from '@/utils/count';
import InfiniteLoading from 'v3-infinite-loading';
import { SettingsOutline } from '@vicons/ionicons5';
import { MoreHorizFilled } from '@vicons/material';
import { useStoreUser } from '@/store/user';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { refresh, desktopModelShow } = storeToRefs(storeMain);
const { userInfo } = storeToRefs(storeUser);
const store = useStore();
const route = useRoute();
const router = useRouter();
const dialog = useDialog();
@ -315,7 +321,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
negativeText: '',
onPositiveClick: () => {
if (post.user.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: post.user.id,
})
.then((_res) => {
@ -324,7 +330,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
})
.catch((_err) => {});
} else {
followUser({
Api.v1.user.post.follow({
user_id: post.user.id,
})
.then((_res) => {
@ -380,8 +386,8 @@ const loadPage = () => {
};
const loadPosts = () => {
loading.value = true;
getUserPosts({
username: store.state.userInfo.username,
Api.v1.user.get.posts({
username: userInfo.value.username,
style: 'post',
page: page.value,
page_size: pageSize.value,
@ -411,8 +417,8 @@ const loadPosts = () => {
};
const loadCommentPosts = () => {
loading.value = true;
getUserPosts({
username: store.state.userInfo.username,
Api.v1.user.get.posts({
username: userInfo.value.username,
style: 'comment',
page: page.value,
page_size: pageSize.value,
@ -442,8 +448,8 @@ const loadCommentPosts = () => {
};
const loadHighlightPosts = () => {
loading.value = true;
getUserPosts({
username: store.state.userInfo.username,
Api.v1.user.get.posts({
username: userInfo.value.username,
style: 'highlight',
page: page.value,
page_size: pageSize.value,
@ -473,8 +479,8 @@ const loadHighlightPosts = () => {
};
const loadMediaPosts = () => {
loading.value = true;
getUserPosts({
username: store.state.userInfo.username,
Api.v1.user.get.posts({
username: userInfo.value.username,
style: 'media',
page: page.value,
page_size: pageSize.value,
@ -504,8 +510,8 @@ const loadMediaPosts = () => {
};
const loadStarPosts = () => {
loading.value = true;
getUserPosts({
username: store.state.userInfo.username,
Api.v1.user.get.posts({
username: userInfo.value.username,
style: 'star',
page: page.value,
page_size: pageSize.value,
@ -610,7 +616,7 @@ watch(
() => ({
path: route.path,
query: route.query,
refresh: store.state.refresh,
refresh: refresh.value,
}),
(to, from) => {
if (to.refresh !== from.refresh) {

@ -6,13 +6,13 @@
<n-avatar
class="avatar-img"
:size="80"
:src="store.state.userInfo.avatar"
:src="userInfo.avatar"
/>
<n-upload
v-if="!store.state.profile.allowPhoneBind || (
store.state.profile.allowPhoneBind &&
store.state.userInfo.phone &&
store.state.userInfo.phone.length > 0)
v-if="!profile.allowPhoneBind || (
profile.allowPhoneBind &&
userInfo.phone &&
userInfo.phone.length > 0)
"
ref="avatarRef"
:action="uploadGateway"
@ -31,13 +31,13 @@
<div class="base-line">
<span class="base-label"></span>
<div v-if="!showNicknameEdit">
{{ store.state.userInfo.nickname }}
{{ userInfo.nickname }}
</div>
<n-input
ref="inputInstRef"
v-show="showNicknameEdit"
class="nickname-input"
v-model:value="store.state.userInfo.nickname"
v-model:value="userInfo.nickname"
type="text"
size="small"
placeholder="请输入昵称"
@ -49,11 +49,11 @@
round
type="success"
size="small"
v-if="!showNicknameEdit && (!store.state.profile.allowPhoneBind || (
store.state.profile.allowPhoneBind &&
store.state.userInfo.phone &&
store.state.userInfo.phone.length > 0 &&
store.state.userInfo.status == 1)
v-if="!showNicknameEdit && (!profile.allowPhoneBind || (
profile.allowPhoneBind &&
userInfo.phone &&
userInfo.phone.length > 0 &&
userInfo.status == 1)
)
"
@click="handleNicknameShow"
@ -67,25 +67,25 @@
</div>
<div class="base-line">
<span class="base-label"></span> @{{
store.state.userInfo.username
userInfo.username
}}
</div>
</n-card>
<n-card v-if="store.state.profile.allowPhoneBind" title="手机号" size="small" class="setting-card">
<n-card v-if="profile.allowPhoneBind" title="手机号" size="small" class="setting-card">
<div
v-if="
store.state.userInfo.phone &&
store.state.userInfo.phone.length > 0
userInfo.phone &&
userInfo.phone.length > 0
"
>
{{ store.state.userInfo.phone }}
{{ userInfo.phone }}
<n-button
quaternary
round
type="success"
v-if="!showPhoneBind && store.state.userInfo.status == 1"
v-if="!showPhoneBind && userInfo.status == 1"
@click="showPhoneBind = true"
>
@ -183,11 +183,11 @@
<n-card v-if="allowActivation" title="激活码" size="small" class="setting-card">
<div
v-if="
store.state.userInfo.activation &&
store.state.userInfo.activation.length > 0
userInfo.activation &&
userInfo.activation.length > 0
"
>
{{ store.state.userInfo.activation }}
{{ userInfo.activation }}
<n-button
quaternary
@ -341,17 +341,8 @@
<script setup lang="ts">
import { onMounted, ref, reactive } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { Edit } from '@vicons/tabler';
import {
getCaptcha,
sendCaptcha,
bindUserPhone,
activateUser,
changePassword,
changeNickname,
changeAvatar,
} from '@/api/user';
import type {
UploadInst,
FormItemRule,
@ -359,13 +350,23 @@ import type {
FormInst,
InputInst,
} from 'naive-ui';
import { TOKEN_KEY, useStoreUser } from '@/store/user';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment';
const uploadToken = 'Bearer ' + localStorage.getItem('PAOPAO_TOKEN');
const uploadToken = 'Bearer ' + localStorage.getItem(TOKEN_KEY);
const uploadType = ref('public/avatar');
const allowActivation =
import.meta.env.VITE_ALLOW_ACTIVATION.toLowerCase() === 'true';
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const storeProfile = useStoreProfile();
const { userInfo } = storeToRefs(storeUser);
const { profile } = storeToRefs(storeProfile);
const sending = ref(false);
const binding = ref(false);
const activating = ref(false);
@ -424,15 +425,15 @@ const finishUpload = ({ file, event }: any): any => {
if (data.code === 0) {
if (uploadType.value === 'public/avatar') {
changeAvatar({
Api.v1.user.post.avatar({
avatar: data.data.content,
})
.then((res) => {
window.$message.success('');
avatarRef.value?.clear();
store.commit('updateUserinfo', {
...store.state.userInfo,
storeUser.updateUserinfo({
...userInfo.value,
avatar: data.data.content,
});
})
@ -469,7 +470,7 @@ const handleValidateButtonClick = (e: MouseEvent) => {
formRef.value?.validate((errors) => {
if (!errors) {
passwordSetting.value = true;
changePassword({
Api.v1.user.post.password({
password: modelData.password,
old_password: modelData.old_password,
})
@ -479,9 +480,9 @@ const handleValidateButtonClick = (e: MouseEvent) => {
window.$message.success('');
// 用户退出登录
store.commit('userLogout');
store.commit('triggerAuth', true);
store.commit('triggerAuthKey', 'signin');
storeUser.userLogout();
storeMain.triggerAuth(true);
storeMain.triggerAuthKey('signin');
})
.catch((err) => {
passwordSetting.value = false;
@ -495,7 +496,7 @@ const handlePhoneBind = (e: MouseEvent) => {
phoneFormRef.value?.validate((errors) => {
if (!errors) {
binding.value = true;
bindUserPhone({
Api.v1.user.post.phone({
phone: modelData.phone,
captcha: modelData.phone_captcha,
})
@ -504,8 +505,8 @@ const handlePhoneBind = (e: MouseEvent) => {
showPhoneBind.value = false;
window.$message.success('');
store.commit('updateUserinfo', {
...store.state.userInfo,
storeUser.updateUserinfo({
...userInfo.value,
phone: modelData.phone,
});
@ -532,7 +533,7 @@ const handleActivation = (e: MouseEvent) => {
sending.value = true;
if (!errors) {
activating.value = true;
activateUser({
Api.v1.user.post.activate({
activate_code: activateData.activate_code,
captcha_id: activateData.id,
imgCaptcha: activateData.imgCaptcha,
@ -542,8 +543,8 @@ const handleActivation = (e: MouseEvent) => {
showActivation.value = false;
window.$message.success('');
store.commit('updateUserinfo', {
...store.state.userInfo,
storeUser.updateUserinfo({
...userInfo.value,
activation: activateData.activate_code,
});
@ -563,7 +564,7 @@ const handleActivation = (e: MouseEvent) => {
};
const loadCaptcha = () => {
getCaptcha()
Api.v1.captcha.get._self({})
.then((res) => {
modelData.id = res.id;
modelData.b64s = res.b64s;
@ -574,7 +575,7 @@ const loadCaptcha = () => {
};
const loadCaptcha4Activate = () => {
getCaptcha()
Api.v1.captcha.get._self({})
.then((res) => {
activateData.id = res.id;
activateData.b64s = res.b64s;
@ -585,8 +586,8 @@ const loadCaptcha4Activate = () => {
};
const handleNicknameChange = () => {
changeNickname({
nickname: store.state.userInfo.nickname || '',
Api.v1.user.post.nickname({
nickname: userInfo.value.nickname || '',
})
.then((res) => {
showNicknameEdit.value = false;
@ -606,7 +607,7 @@ const sendPhoneCaptcha = () => {
return;
}
sending.value = true;
sendCaptcha({
Api.v1.captcha.post._self({
phone: modelData.phone,
img_captcha: modelData.imgCaptcha,
img_captcha_id: modelData.id,
@ -702,9 +703,9 @@ const handleNicknameShow = () => {
}, 30);
};
onMounted(() => {
if (store.state.userInfo.id === 0) {
store.commit('triggerAuth', true);
store.commit('triggerAuthKey', 'signin');
if (userInfo.value.id === 0) {
storeMain.triggerAuth(true);
storeMain.triggerAuthKey('signin');
}
loadCaptcha();
loadCaptcha4Activate();

@ -6,9 +6,9 @@
<n-tabs type="line" animated @update:value="changeTab">
<n-tab-pane name="hot" tab="热门" />
<n-tab-pane name="new" tab="最新" />
<n-tab-pane name="follow" tab="关注" v-if="store.state.userLogined" />
<n-tab-pane name="pin" tab="钉住" v-if="store.state.userLogined" />
<template v-if="store.state.userLogined" #suffix>
<n-tab-pane name="follow" tab="关注" v-if="userLogined" />
<n-tab-pane name="pin" tab="钉住" v-if="userLogined" />
<template v-if="userLogined" #suffix>
<n-tag v-model:checked="tagsChecked" checkable>
{{tagsEditText}}
</n-tag>
@ -19,7 +19,7 @@
<tag-item
v-for="tag in tags"
:tag="tag"
:showAction="store.state.userLogined && tagsChecked"
:showAction="userLogined && tagsChecked"
:checkFollowing="inFollowTab"
:checkPin="inPinTab"
>
@ -36,9 +36,14 @@
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue';
import { getTags } from '@/api/post';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useStoreUser } from '@/store/user';
import { storeToRefs } from 'pinia';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { userLogined } = storeToRefs(storeUser);
const store = useStore();
const tags = ref<Item.TagProps[]>([]);
const tagType = ref<'hot' | 'new' | 'follow' | 'pin'>('hot');
const loading = ref(false);
@ -49,7 +54,7 @@ const inPinTab = ref(false);
watch(tagsChecked, () => {
if (!tagsChecked.value) {
window.$message.success('');
store.commit('refreshTopicFollow');
storeMain.doRefreshTopicFollow();
}
});
const tagsEditText = computed({

@ -14,12 +14,12 @@
<strong>{{ user.nickname }}</strong>
<span> @{{ user.username }} </span>
<n-tag
v-if="store.state.profile.useFriendship && store.state.userInfo.id > 0 && store.state.userInfo.username != user.username && user.is_friend"
v-if="profile.useFriendship && userInfo.id > 0 && userInfo.username != user.username && user.is_friend"
class="top-tag" type="info" size="small" round>
</n-tag>
<n-tag
v-if="store.state.userInfo.id > 0 && store.state.userInfo.username != user.username && user.is_following"
v-if="userInfo.id > 0 && userInfo.username != user.username && user.is_following"
class="top-tag" type="success" size="small" round>
</n-tag>
@ -70,7 +70,7 @@
</div>
</div>
<div class="user-opts" v-if="store.state.userInfo.id > 0">
<div class="user-opts" v-if="userInfo.id > 0">
<n-dropdown placement="bottom-end" trigger="click" size="small" :options="userOptions"
@select="handleUserAction">
<n-button quaternary circle>
@ -104,11 +104,11 @@
<div class="empty-wrap" v-if="list.length === 0">
<n-empty size="large" description="暂无数据" />
</div>
<div v-if="store.state.desktopModelShow">
<div v-if="desktopModelShow">
<div v-if="pageType === 'post'">
<n-list-item v-for="post in postList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -117,7 +117,7 @@
<div v-if="pageType === 'comment'">
<n-list-item v-for="post in commentList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -126,7 +126,7 @@
<div v-if="pageType === 'highlight'">
<n-list-item v-for="post in highlightList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -135,7 +135,7 @@
<div v-if="pageType === 'media'">
<n-list-item v-for="post in mediaList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -144,7 +144,7 @@
<div v-if="pageType === 'star'">
<n-list-item v-for="post in starList" :key="post.id">
<post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -155,7 +155,7 @@
<div v-if="pageType === 'post'">
<n-list-item v-for="post in postList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -164,7 +164,7 @@
<div v-if="pageType === 'comment'">
<n-list-item v-for="post in commentList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -173,7 +173,7 @@
<div v-if="pageType === 'highlight'">
<n-list-item v-for="post in highlightList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -182,7 +182,7 @@
<div v-if="pageType === 'media'">
<n-list-item v-for="post in mediaList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -191,7 +191,7 @@
<div v-if="pageType === 'star'">
<n-list-item v-for="post in starList" :key="post.id">
<mobile-post-item :post="post"
:isOwner="store.state.userInfo.id == post.user_id"
:isOwner="userInfo.id == post.user_id"
:addFollowAction="true"
@send-whisper="onSendWhisper"
@handle-follow-action="onHandleFollowAction" />
@ -218,16 +218,8 @@
import { h, ref, reactive, watch, onMounted, computed } from 'vue';
import { NIcon } from 'naive-ui';
import type { Component, Ref } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRoute, useRouter } from 'vue-router';
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';
@ -243,9 +235,20 @@ import {
WalkOutline,
} from '@vicons/ionicons5';
import InfiniteLoading from 'v3-infinite-loading';
import { useStoreUser } from '@/store/user';
import { useStoreProfile } from '@/store/profile';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const dialog = useDialog();
const store = useStore();
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const storeProfile = useStoreProfile();
const { desktopModelShow } = storeToRefs(storeMain);
const { userLogined, userInfo } = storeToRefs(storeUser);
const { profile } = storeToRefs(storeProfile);
const route = useRoute();
const router = useRouter();
@ -312,7 +315,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
negativeText: '',
onPositiveClick: () => {
if (post.user.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: post.user.id,
})
.then((_res) => {
@ -321,7 +324,7 @@ const onHandleFollowAction = (post: Item.PostProps) => {
})
.catch((_err) => {});
} else {
followUser({
Api.v1.user.post.follow({
user_id: post.user.id,
})
.then((_res) => {
@ -399,7 +402,7 @@ const loadPage = () => {
};
const loadPosts = () => {
loading.value = true;
getUserPosts({
Api.v1.user.get.posts({
username: username.value as string,
style: 'post',
page: page.value,
@ -430,7 +433,7 @@ const loadPosts = () => {
};
const loadCommentPosts = () => {
loading.value = true;
getUserPosts({
Api.v1.user.get.posts({
username: username.value as string,
style: 'comment',
page: page.value,
@ -461,7 +464,7 @@ const loadCommentPosts = () => {
};
const loadHighlightPosts = () => {
loading.value = true;
getUserPosts({
Api.v1.user.get.posts({
username: username.value as string,
style: 'highlight',
page: page.value,
@ -492,7 +495,7 @@ const loadHighlightPosts = () => {
};
const loadMediaPosts = () => {
loading.value = true;
getUserPosts({
Api.v1.user.get.posts({
username: username.value as string,
style: 'media',
page: page.value,
@ -523,7 +526,7 @@ const loadMediaPosts = () => {
};
const loadStarPosts = () => {
loading.value = true;
getUserPosts({
Api.v1.user.get.posts({
username: username.value as string,
style: 'star',
page: page.value,
@ -591,7 +594,7 @@ const changeTab = (
};
const loadUser = () => {
userLoading.value = true;
getUserProfile({
Api.v1.user.get.profile({
username: username.value as string,
})
.then((res) => {
@ -661,7 +664,7 @@ const renderIcon = (icon: Component) => {
};
};
const userOptions = computed(() => {
if (store.state.userInfo.username == user.username) {
if (userInfo.value.username == user.username) {
return [
{
label: '',
@ -677,7 +680,7 @@ const userOptions = computed(() => {
icon: renderIcon(PaperPlaneOutline),
},
];
if (store.state.userInfo.is_admin) {
if (userInfo.value.is_admin) {
if (user.status === 1) {
options.push({
label: '',
@ -705,7 +708,7 @@ const userOptions = computed(() => {
icon: renderIcon(BodyOutline),
});
}
if (store.state.profile.useFriendship) {
if (profile.value.useFriendship) {
if (user.is_friend) {
options.push({
label: '',
@ -774,7 +777,7 @@ const openDeleteFriend = () => {
negativeText: '',
onPositiveClick: () => {
userLoading.value = true;
deleteFriend({
Api.v1.friend.post.delete({
user_id: user.id,
})
.then((res) => {
@ -802,7 +805,7 @@ const handleFollowUser = () => {
onPositiveClick: () => {
userLoading.value = true;
if (user.is_following) {
unfollowUser({
Api.v1.user.post.unfollow({
user_id: user.id,
})
.then((_res) => {
@ -815,7 +818,7 @@ const handleFollowUser = () => {
console.log(err);
});
} else {
followUser({
Api.v1.user.post.follow({
user_id: user.id,
})
.then((_res) => {
@ -840,7 +843,7 @@ const banUser = () => {
negativeText: '',
onPositiveClick: () => {
userLoading.value = true;
changeUserStatus({
Api.v1.admin.post.user.status({
id: user.id,
status: user.status === 1 ? 2 : 1,
})

@ -8,7 +8,7 @@
<n-statistic label="账户余额 (元)"
><n-number-animation
:from="0.0"
:to="(store.state.userInfo.balance || 0) / 100"
:to="(userInfo.balance || 0) / 100"
:duration="500"
:precision="2"
/>
@ -41,7 +41,7 @@
<n-pagination
:page="page"
@update:page="updatePage"
:page-slot="!store.state.collapsedRight ? 8 : 5"
:page-slot="!collapsedRight ? 8 : 5"
:page-count="totalPage"
/>
</div>
@ -145,15 +145,21 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useStoreMain } from '@/store/main';
import { useRoute } from 'vue-router';
import QRCode from 'qrcode';
import { LogoAlipay } from '@vicons/ionicons5';
import { userInfo } from '@/api/auth';
import { reqRecharge, getRecharge, getBills } from '@/api/user';
import { userInfo as fetchUserInfo } from '@/api/auth';
import { formatRelativeTime } from '@/utils/formatTime';
import { TOKEN_KEY, useStoreUser } from '@/store/user';
import { storeToRefs } from 'pinia';
import { Api } from '@/utils/request';
const storeMain = useStoreMain();
const storeUser = useStoreUser();
const { collapsedRight } = storeToRefs(storeMain);
const { userInfo } = storeToRefs(storeUser);
const store = useStore();
const route = useRoute();
const showRecharge = ref(false);
@ -171,7 +177,7 @@ const openAmounts = ref([100, 200, 300, 500, 1000, 3000, 5000, 10000, 50000]);
const loadPosts = () => {
loading.value = true;
getBills({
Api.v1.user.get.wallet.bills({
page: page.value,
page_size: pageSize.value,
})
@ -191,30 +197,30 @@ const updatePage = (p: number) => {
loadPosts();
};
const loadWallet = () => {
// 获取最新
const token = localStorage.getItem('PAOPAO_TOKEN') || '';
if (token) {
userInfo(token)
.then((res) => {
store.commit('updateUserinfo', res);
store.commit('triggerAuth', false);
loadPosts();
})
.catch((err) => {
store.commit('triggerAuth', true);
store.commit('userLogout');
});
} else {
store.commit('triggerAuth', true);
store.commit('userLogout');
}
// 获取最新
const token = localStorage.getItem(TOKEN_KEY) || '';
if (token) {
fetchUserInfo(token)
.then((res) => {
storeUser.updateUserinfo(res);
storeMain.triggerAuth(false);
loadPosts();
})
.catch((err) => {
storeMain.triggerAuth(true);
storeUser.userLogout();
});
} else {
storeMain.triggerAuth(true);
storeUser.userLogout();
}
};
const doRecharge = () => {
showRecharge.value = true;
};
const handleRecharge = (amount: any) => {
recharging.value = true;
reqRecharge({
Api.v1.user.post.recharge({
amount: selectedRechargeAmount.value,
})
.then((res) => {
@ -228,7 +234,7 @@ const handleRecharge = (amount: any) => {
});
const s = setInterval(() => {
getRecharge({
Api.v1.user.get.recharge({
id: res.id,
})
.then((res) => {
@ -252,7 +258,7 @@ const handleRecharge = (amount: any) => {
});
};
const doWithdraw = () => {
if (store.state.userInfo.balance == 0) {
if (userInfo.value.balance == 0) {
window.$message.warning('');
} else {
window.$message.warning('');

@ -1,5 +1,4 @@
/// <reference types="vite/client" />
/// <reference types="vue/ref-macros" />
/// <reference types="naive-ui/volar" />
/// <reference types="@types/qrcode" />

14
web/src/vuex.d.ts vendored

@ -1,14 +0,0 @@
import { ComponentCustomProperties } from 'vue';
import { Store } from 'vuex';
declare module '@vue/runtime-core' {
// 声明自己的 store state
interface State {
[key: string]: any;
}
// 为 `this.$store` 提供类型声明
interface ComponentCustomProperties {
$store: Store<State>;
}
}

@ -1,9 +1,8 @@
import { defineConfig, type PluginOption } from 'vite';
import { defineConfig, esmExternalRequirePlugin } from 'vite';
import path from 'path';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { visualizer } from 'rollup-plugin-visualizer';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
@ -11,13 +10,13 @@ export default defineConfig({
host: '0.0.0.0',
},
plugins: [
vue({
reactivityTransform: [/src/],
}),
vue(),
Components({
resolvers: [NaiveUiResolver()],
}),
visualizer() as PluginOption,
esmExternalRequirePlugin({
external: ['vue', /^node:/]
}),
],
resolve: {
alias: {

Loading…
Cancel
Save