feat:更新朋友圈功能

This commit is contained in:
2025-06-28 15:54:02 +08:00
parent c77c3ff512
commit 0969af5ade
22 changed files with 598 additions and 70 deletions

View File

@@ -28,7 +28,7 @@
<div class="button-info">
<el-button type="primary" :icon="Edit" circle :size="'small'"
@click="editClick"/>
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
<el-button type="primary" :icon="Collection" circle :size="'small'"
@click="viewClick()" />
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
@click="deleteClick()" />
@@ -59,7 +59,7 @@
</template>
<script setup lang="ts">
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
import { Edit, Collection, DeleteFilled } from '@element-plus/icons-vue'
import { useFoodStore } from '@/store/food'
import { defineProps, onMounted, reactive } from 'vue'
import type { PropType } from 'vue'

View File

@@ -44,7 +44,7 @@
<script lang="ts" setup>
import { useFoodStore } from '@/store/food'
import { nextTick, reactive, ref } from 'vue'
import { FormRules } from 'element-plus'
import type { FormRules } from 'element-plus'
import { foodMaterialRules } from '@/utils/element/elRules'
const rules = reactive<FormRules>(foodMaterialRules)

View File

@@ -1,13 +1,13 @@
<template>
<el-dialog v-model="foodStore.stepDialog.visible"
:title="foodStore.stepDialog.title"
:title="`${foodStore.stepDialog.title}${foodStore.editStepIndex + 1}`"
center @open="openDialogEvent"
class="food-step-dialog">
<el-form ref="formRef" :model="foodStore.stepDialog.data"
:rules="rules" label-width="60px">
<el-form-item label="内容" prop="content">
<el-input v-model="foodStore.stepDialog.data.content" autocomplete="off"
placeholder="请输入内容" clearable
:placeholder="`请输入步骤${foodStore.editStepIndex + 1}`" clearable
type="textarea" :rows="3"
show-word-limit maxlength="50">
</el-input>
@@ -36,7 +36,7 @@
import { UploadImage } from 'vue3-common'
import { useFoodStore } from '@/store/food'
import { ref, reactive, nextTick } from 'vue'
import { FormRules } from 'element-plus'
import type { FormRules } from 'element-plus'
import { foodStepRules } from '@/utils/element/elRules'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
@@ -60,11 +60,13 @@ const openDialogEvent = () => {
const confirmClick = () => {
formRef.value.validate()
.then(async () => {
const { editStepIndex: index } = foodStore
if (foodStore.stepApiType === 'UPDATE') {
const { editStepIndex: index } = foodStore
foodStore.currentRecipe.stepList[index] = foodStore.stepDialog.data
} else {
foodStore.stepDialog.data.sort = foodStore.editStepIndex
foodStore.currentRecipe.stepList.push(foodStore.stepDialog.data)
foodStore.editStepIndex++
}
foodStore.stepDialog.visible = false
})

View File

@@ -91,10 +91,14 @@ const userStore = useUserStore()
const loginFormRef = ref()
onMounted(() => {
onMounted(async () => {
const storageLoginForm = getLocalStorage('loginForm')
if (storageLoginForm) {
authStore.loginForm = JSON.parse(storageLoginForm)
if (!authStore.isLogout) {
await doLogin('common')
}
}
})
@@ -109,15 +113,7 @@ const handleLogin = async (type: ILoginType) => {
authStore.loginLoading = true
try {
await authStore.handleLogin(type)
userStore.handleSession(authStore.session)
// 2. 存储账户密码到localStorage中
if (authStore.loginForm.isRemember) {
setLocalStorage('loginForm', JSON.stringify(authStore.loginForm))
}
await router.push('/record')
await doLogin(type)
} finally {
authStore.loginLoading = false
}
@@ -127,4 +123,16 @@ const handleLogin = async (type: ILoginType) => {
}
})
}
const doLogin = async (type: ILoginType) => {
await authStore.handleLogin(type)
userStore.handleSession(authStore.session)
// 2. 存储账户密码到localStorage中
if (authStore.loginForm.isRemember) {
setLocalStorage('loginForm', JSON.stringify(authStore.loginForm))
}
await router.push('/record')
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div class="moment-card card-item">
<el-avatar :src="serviceFileRootPath + props.item.avatar" fit="contain"/>
<el-avatar :src="avatarUrl" fit="contain" @click="viewProfileClick"/>
<div class="moment-content">
<span class="username">{{ props.item.username }}</span>
@@ -9,7 +9,7 @@
<div class="moment-image-list">
<el-image v-for="(item, index) in props.item.imageList" :key="index"
:src="serviceFileRootPath + item" fit="cover"
:preview-src-list="[serviceFileRootPath + item]"/>
:preview-src-list=previewList />
</div>
<p>{{ formatTimeAgo(props.item.date) }}</p>
@@ -67,17 +67,19 @@
import EmojiPicker from 'vue3-emoji-picker'
import 'vue3-emoji-picker/css'
import { Edit } from '@element-plus/icons-vue'
import { defineProps, reactive } from 'vue'
import { computed, defineProps, reactive } from 'vue'
import type { PropType } from 'vue'
import type { IMoment } from '@/types/food'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
import { serviceFileRootPath } from '@/utils'
import { formatTimeAgo } from '@/utils/dateUtils'
import { useFoodStore } from '@/store/food'
import { useUserStore } from '@/store/user'
import { useAppStore } from '@/store/app'
import { useRouter } from 'vue-router'
const router = useRouter()
const userStore = useUserStore()
const appStore = useAppStore()
const props = defineProps({
item: {
@@ -93,10 +95,22 @@ const momentInfo = reactive({
comment: ''
})
const avatarUrl = computed(() => {
if (props.item?.avatar) {
return serviceFileRootPath + props.item.avatar
} else {
return 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
}
})
const previewList = computed(() => {
return props.item?.imageList.map((item) => serviceFileRootPath + item)
})
const editClick = () => {
foodStore.momentApiType = 'UPDATE'
foodStore.currentMoment = props.item as IMoment
router.push('/moment/momentForm')
router.push('/moment/form')
}
const onSelectEmoji = (emoji) => {
@@ -109,6 +123,15 @@ const sendCommentClick = async () => {
momentInfo.comment = ''
momentInfo.isShowComment = false
}
const viewProfileClick = async () => {
if (props.item?.userId !== userStore.userInfo.id) {
appStore.isShowMobileBack = true
foodStore.currentMomentUserId = props.item?.userId as number
await userStore.queryUser(foodStore.currentMomentUserId)
await router.push('/moment/profile')
}
}
</script>
<style lang="scss">

View File

@@ -0,0 +1,120 @@
<template>
<div class="profile-card">
<div class="avatar-wrapper">
<el-avatar :src="avatarUrl" alt="用户头像" :size="80"
fit="contain" @click="previewVisible = true"
/>
<el-image-viewer
v-if="previewVisible"
:url-list="[serviceFileRootPath + userStore.userInfo.avatar]"
@close="previewVisible = false"
/>
<i v-if="userStore.userInfo.gender"
class="fas fa-mars gender-icon" style="color: #409EFF"></i>
<i v-else class="fas fa fa-venus gender-icon" style="color: #FFB6C1"></i>
</div>
<span class="user-name">{{ userStore.userInfo.username }}</span>
<div class="user-info">
<div v-if="userStore.userInfo.area" class="info-item">
<i class="fas fa-map-marker-alt"></i>
<span>{{ userStore.userInfo.area.join('-') }}</span>
</div>
<div v-if="userStore.userInfo.birthDate" class="info-item">
<i class="fas fa-birthday-cake"></i>
<span>{{ userStore.userInfo.birthDate }}</span>
</div>
<div v-if="userStore.userInfo.job" class="info-item">
<i class="fas fa-user-tie"></i>
<span>{{ userStore.userInfo.job }}</span>
</div>
</div>
<div class="user-info">
<div v-if="userStore.userInfo.phoneNumber" class="info-item">
<i class="fas fa-mobile-alt"></i>
<span>{{ userStore.userInfo.phoneNumber }}</span>
</div>
<div v-if="userStore.userInfo.email" class="info-item">
<i class="fas fa-envelope"></i>
<span>{{ userStore.userInfo.email }}</span>
</div>
</div>
<div v-if="userStore.userInfo.tags" class="user-info">
<el-tag v-for="(item, index) in userStore.userInfo.tags"
:key="index">{{ item }}</el-tag>
</div>
<span v-if="userStore.userInfo.description" class="user-bio">
{{ userStore.userInfo.description }}
</span>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { useUserStore } from '@/store/user.ts'
import { serviceFileRootPath } from '@/utils'
const userStore = useUserStore()
const previewVisible = ref(false)
const avatarUrl = computed(() => {
if (userStore.userInfo.avatar) {
return serviceFileRootPath + userStore.userInfo.avatar
} else {
return 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
}
})
</script>
<style lang="scss">
.profile-card {
background: linear-gradient(135deg, #6e8efb, #a777e3);
border-radius: 10px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
text-align: center;
color: white;
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
.avatar-wrapper {
position: relative;
display: inline-block;
.gender-icon {
position: absolute;
right: 2px;
bottom: 0;
background: white;
border-radius: 50%;
padding: 3px;
}
}
.user-name {
font-size: 1.5rem;
}
.user-info {
display: flex;
justify-content: center;
gap: 10px;
font-size: 1.1rem;
.info-item {
display: flex;
align-items: center;
gap: 5px;
}
}
}
</style>