feat:更新个人中心UI

This commit is contained in:
2025-06-30 22:52:28 +08:00
parent 50991bb49a
commit be8e105a55
13 changed files with 302 additions and 200 deletions

View File

@@ -21,12 +21,18 @@ export const deleteMomentApi = (id: number): Promise<boolean> =>
method: 'delete'
})
export const queryMomentApi = (): Promise<IMoment[]> =>
export const queryMomentListApi = (): Promise<IMoment[]> =>
cloudService({
url: '/food-service/moment',
method: 'get'
})
export const queryMomentByIdApi = (id: number): Promise<IMoment[]> =>
cloudService({
url: `/food-service/moment/${id}`,
method: 'get'
})
export const addCommentApi = (id: number, content: string): Promise<boolean> =>
cloudService({
url: `/food-service/moment/${id}/comment`,

View File

@@ -0,0 +1,49 @@
<template>
<div class="search-container">
<el-input v-if="expanded" ref="inputRef"
v-model="searchText" placeholder="搜索菜友食谱"
style="width: 200px;"
show-word-limit :maxlength="10" clearable
@keyup.enter="handleSearch" @blur="handleBlur"
/>
<el-button v-else :icon="Search"
circle size="small" @click="expandSearch"/>
</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
import { Search } from '@element-plus/icons-vue'
const expanded = ref(false)
const searchText = ref('')
const inputRef = ref(null)
// 展开搜索框
const expandSearch = () => {
expanded.value = true
// 等待DOM更新后自动聚焦输入框
nextTick(() => {
inputRef.value?.focus()
})
}
// 处理搜索
const handleSearch = () => {
if (searchText.value.trim()) {
console.log('搜索内容:', searchText.value)
// 这里可以触发搜索逻辑
}
}
// 输入框失去焦点时的处理
const handleBlur = () => {
if (!searchText.value.trim()) {
expanded.value = false
}
}
</script>
<style lang="scss">
</style>

View File

@@ -24,7 +24,7 @@
import FoodCard from '@/components/Food/FoodCard.vue'
import ArriveBottom from '@/components/ArriveButton.vue'
import { useFoodStore } from '@/store/food.ts'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import type { ICategory } from '@/types'
const foodRecipeRef = ref()
@@ -33,10 +33,7 @@ const foodStore = useFoodStore()
const queryCategoryList = ref([] as ICategory[])
onMounted(async () => {
await foodStore.queryCategory()
initCategoryList()
// foodStore.foodRecipeList = []
await foodStore.refreshInfo()
// foodRecipeRef.value.addEventListener('scroll', handleScroll)
})
@@ -44,6 +41,10 @@ onBeforeUnmount(() => {
// foodRecipeRef.value.removeEventListener('scroll', handleScroll)
})
watch(() => foodStore.isRefresh, () => {
initCategoryList()
})
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({

View File

@@ -43,29 +43,26 @@
<script setup lang="ts">
import { CaretLeft, CaretRight } from '@element-plus/icons-vue'
import { useFoodStore } from '@/store/food.ts'
import { onMounted, ref } from 'vue'
import { formatDate, getLastDate, getNextDate } from 'vue3-common/utils/dateUtil'
import { serviceFileRootPath } from '@/utils'
const currentYear = ref(new Date())
const foodStore = useFoodStore()
const changeDateEvent = async (value: Date) => {
foodStore.queryRecord.year = String(value.getFullYear())
await foodStore.refreshInfo()
await foodStore.queryRecordList()
}
const previousYearClick = async () => {
const lastYear = getLastDate(foodStore.queryRecord.year, 1, 'years')
foodStore.queryRecord.year = String(new Date(lastYear).getFullYear())
await foodStore.refreshInfo()
await foodStore.queryRecordList()
}
const nextYearClick = async () => {
const lastYear = getNextDate(foodStore.queryRecord.year, 1, 'years')
foodStore.queryRecord.year = String(new Date(lastYear).getFullYear())
await foodStore.refreshInfo()
await foodStore.queryRecordList()
}
</script>

View File

@@ -150,61 +150,3 @@ const viewProfileClick = async () => {
}
}
</script>
<style lang="scss">
.moment-card {
padding: 10px;
display: grid;
grid-template-columns: 50px 1fr;
gap: 10px;
.moment-content {
display: flex;
flex-direction: column;
gap: 10px;
p {
margin: 0;
}
.username {
font-weight: bold;
font-size: larger;
}
.moment-image-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.button-container {
display: flex;
gap: 20px;
}
.moment-comment {
display: grid;
grid-template-columns: 1fr 40px 60px;
justify-items: center;
gap: 5px;
}
.moment-comment-list {
padding: 10px;
background-color: var(--color-comment-bg);
display: flex;
flex-direction: column;
gap: 5px;
.moment-comment-item {
.name {
font-weight: bold;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,23 @@
<template>
<div class="food-moment"
v-loading="foodStore.isLoading" element-loading-text="加载中">
<el-empty v-if="foodStore.momentList.length === 0" description="暂无朋友圈记录"/>
<div v-else class="moment-list">
<moment-card v-for="(item, index) in foodStore.momentList"
:key="index" :item="item"/>
</div>
</div>
</template>
<script setup lang="ts">
import MomentCard from '@/components/Moment/MomentCard.vue'
import { useFoodStore } from '@/store/food'
const foodStore = useFoodStore()
</script>
<style lang="scss">
@use "@/styles/moment.module.scss";
</style>

View File

@@ -16,6 +16,7 @@
<div class="right-container">
<svg-icon v-if="appStore.isWebSocket"
name="home-websocket" style="color: #FFFFFF;"></svg-icon>
<food-query />
<el-button type="info" :icon="Setting" size="small"
circle color="#FFFFFF" @click="openSettingClick"/>
@@ -30,6 +31,7 @@
</template>
<script lang="ts" setup>
import FoodQuery from '@/components/Food/FoodQuery.vue'
import { SvgIcon } from 'vue3-common'
import MobileSetting from '@/components/MobileSetting.vue'
import { Back } from '@element-plus/icons-vue'

View File

@@ -26,8 +26,8 @@ import {
addLikeApi,
addMomentApi,
deleteLikeApi,
deleteMomentApi,
queryMomentApi,
deleteMomentApi, queryMomentByIdApi,
queryMomentListApi,
updateMomentApi
} from '@/apis/moment'
@@ -105,7 +105,8 @@ export const useFoodStore = defineStore('food', {
recordStats: [] as IStatsChart[],
categoryStats: [] as IStatsChart[],
rankStats: [] as IStatsChart[],
isRefresh: false
isRefresh: false,
isLoading: false
}),
actions: {
async queryRecipeList() {
@@ -113,16 +114,24 @@ export const useFoodStore = defineStore('food', {
// const result = await queryFoodRecipeByPageApi(currentPage, pageSize, this.queryRecipe)
// this.foodRecipeList.push(...result.records)
// this.pageInfo.totalSize = result.total
this.isLoading = true
this.recipeList = await queryRecipeApi(this.queryRecipeSegment)
this.isRefresh = !this.isRefresh
this.isLoading = false
},
async queryRecordList() {
const { year, month } = this.queryRecord
const date = this.queryDateType === 'year' ? year : month
this.queryDateList = getStartEndDate(date, this.queryDateType)
this.isLoading = true
this.recordList = await queryRecordApi(this.queryDateList[0], this.queryDateList[1])
this.isRefresh = !this.isRefresh
this.isLoading = false
},
async queryMomentList() {
this.momentList = await queryMomentApi()
this.isLoading = true
this.momentList = await queryMomentListApi()
this.isLoading = false
},
async queryFoodNameList() {
this.foodNameList = await queryFoodNameListApi()
@@ -130,6 +139,11 @@ export const useFoodStore = defineStore('food', {
async queryRecipe(id: number) {
this.currentRecipe = await queryRecipeByIdApi(id)
},
async queryMoment(id: number) {
this.isLoading = true
this.momentList = await queryMomentByIdApi(id)
this.isLoading = false
},
async queryStats() {
this.stats = await queryStatsApi()
this.recordStats = await queryRecordStatsApi()
@@ -157,7 +171,7 @@ export const useFoodStore = defineStore('food', {
default:
break
}
await this.refreshInfo()
await this.queryRecipeList()
},
async handleRecordApi(record: IRecord) {
switch (this.recordApiType) {
@@ -176,7 +190,7 @@ export const useFoodStore = defineStore('food', {
default:
break
}
await this.refreshInfo()
await this.queryRecordList()
},
async handleMomentApi(moment: IMoment) {
switch (this.momentApiType) {
@@ -195,7 +209,7 @@ export const useFoodStore = defineStore('food', {
default:
break
}
await queryMomentApi()
await this.queryMomentList()
},
async addComment(id: number, content: string) {
await addCommentApi(id, content)
@@ -209,11 +223,6 @@ export const useFoodStore = defineStore('food', {
await deleteLikeApi(id)
await this.queryMomentList()
},
async refreshInfo() {
await this.queryRecipeList()
await this.queryRecordList()
this.isRefresh = !this.isRefresh
},
getEmptyRecipe(): IRecipe {
return {
category: '',

View File

@@ -0,0 +1,67 @@
.food-moment {
display: flex;
flex-direction: column;
gap: 10px;
.moment-list {
display: flex;
flex-direction: column;
gap: 10px;
.moment-card {
padding: 10px;
display: grid;
grid-template-columns: 50px 1fr;
gap: 10px;
.moment-content {
display: flex;
flex-direction: column;
gap: 10px;
p {
margin: 0;
}
.username {
font-weight: bold;
font-size: larger;
}
.moment-image-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.button-container {
display: flex;
gap: 20px;
}
.moment-comment {
display: grid;
grid-template-columns: 1fr 40px 60px;
justify-items: center;
gap: 5px;
}
.moment-comment-list {
padding: 10px;
background-color: var(--color-comment-bg);
display: flex;
flex-direction: column;
gap: 5px;
.moment-comment-item {
.name {
font-weight: bold;
}
}
}
}
}
}
}

View File

@@ -1,20 +1,15 @@
<template>
<div class="common-mobile-view food-moment">
<el-empty v-if="foodStore.momentList.length === 0" description="暂无朋友圈记录"/>
<div class="common-mobile-view">
<moment-list />
<div v-else class="moment-list">
<moment-card v-for="(item, index) in foodStore.momentList"
:key="index" :item="item"/>
<el-button type="primary" circle :icon="Refresh"
class="refresh-button" @click="foodStore.queryMomentList()"/>
</div>
<el-button type="primary" circle :icon="Refresh"
class="refresh-button" @click="foodStore.queryMomentList()"/>
</div>
</template>
<script setup lang="ts">
import MomentList from '@/components/Moment/MomentList.vue'
import { Refresh } from '@element-plus/icons-vue'
import MomentCard from '@/components/Moment/MomentCard.vue'
import { useFoodStore } from '@/store/food'
import { onMounted } from 'vue'
@@ -26,21 +21,9 @@ onMounted(async () => {
</script>
<style lang="scss">
.food-moment {
display: flex;
flex-direction: column;
gap: 10px;
.moment-list {
display: flex;
flex-direction: column;
gap: 10px;
.refresh-button {
position: fixed;
top: 60px;
right: 20px;
}
}
.refresh-button {
position: fixed;
top: 60px;
right: 20px;
}
</style>

View File

@@ -1,22 +1,44 @@
<template>
<div class="moment-profile-view common-mobile-view">
<profile-card />
<el-tabs v-model="activeName"
stretch class="profile-tabs"
@tab-click="handleClick">
<el-tab-pane label="朋友圈" name="moment">
<moment-list />
</el-tab-pane>
<el-tab-pane label="菜谱记录" name="recipe">
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" setup>
import ProfileCard from '@/components/Profile/ProfileCard.vue'
import { useUserStore } from '@/store/user.ts'
import { useAppStore } from '@/store/app.ts'
import { useRouter } from 'vue-router'
import { onMounted } from 'vue'
import { useFoodStore } from '@/store/food.ts'
import { onMounted, ref } from 'vue'
import MomentList from '@/components/Moment/MomentList.vue'
import type { TabsPaneContext } from 'element-plus'
const appStore = useAppStore()
const router = useRouter()
const foodStore = useFoodStore()
onMounted(() => {
const activeName = ref('moment')
onMounted(async () => {
await foodStore.queryMoment(foodStore.currentMomentUserId)
})
const handleClick = async (tab: TabsPaneContext) => {
switch (tab.paneName) {
case 'moment':
await foodStore.queryMoment(foodStore.currentMomentUserId)
break
default:
break
}
}
</script>
<style lang="scss">

View File

@@ -2,61 +2,74 @@
<div class="profile-view common-mobile-view">
<profile-card />
<div class="menu-card card-item">
<div class="menu-item" @click="infoClick">
<i class="fas fa-user"></i>
<span>基本资料</span>
<i class="fas fa-chevron-right"></i>
</div>
<el-tabs v-model="activeName"
stretch class="profile-tabs"
@tab-click="handleClick">
<el-tab-pane label="基本资料" name="basic">
<div class="menu-card card-item">
<div class="menu-item" @click="infoClick">
<i class="fas fa-user"></i>
<span>基本资料</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-divider"></div>
<div class="menu-item">
<i class="fas fa-cog"></i>
<span>账户设置</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-item">
<i class="fas fa-cog"></i>
<span>账户设置</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-divider"></div>
<div class="menu-item">
<i class="fa-solid fa-comment-dots"></i>
<span>反馈意见</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-item" @click="passwordClick">
<i class="fas fa-key"></i>
<span>更改密码</span>
<i class="fas fa-chevron-right"></i>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="朋友圈" name="moment">
<moment-list />
</el-tab-pane>
<el-tab-pane label="菜谱收藏" name="recipe">
<div class="menu-divider"></div>
</el-tab-pane>
<el-tab-pane label="系统设置" name="system">
<div class="menu-card card-item">
<div class="menu-item">
<i class="fa-solid fa-comment-dots"></i>
<span>反馈意见</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-item" @click="passwordClick">
<i class="fas fa-key"></i>
<span>更改密码</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-divider"></div>
<div class="menu-item">
<i class="fa-solid fa-file-arrow-down"></i>
<span>软件下载</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-item">
<i class="fa-solid fa-file-arrow-down"></i>
<span>软件下载</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-divider"></div>
<div class="menu-item" @click="refreshClick">
<i class="fa-solid fa-arrows-rotate"></i>
<span>检查更新</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-item" @click="refreshClick">
<i class="fa-solid fa-arrows-rotate"></i>
<span>检查更新</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-divider"></div>
<div class="menu-item" @click="logoutClick">
<i class="fas fa-sign-out-alt"></i>
<span>退出登录</span>
<i class="fas fa-chevron-right"></i>
</div>
</div>
<div class="menu-item" @click="logoutClick">
<i class="fas fa-sign-out-alt"></i>
<span>退出登录</span>
<i class="fas fa-chevron-right"></i>
</div>
</div>
</el-tab-pane>
</el-tabs>
<div class="version">版本号 {{ appStore.version }}</div>
</div>
@@ -64,18 +77,24 @@
<script lang="ts" setup>
import ProfileCard from '@/components/Profile/ProfileCard.vue'
import MomentList from '@/components/Moment/MomentList.vue'
import { useUserStore } from '@/store/user.ts'
import { useAppStore } from '@/store/app.ts'
import { useAuthStore } from '@/store/auth'
import { useFoodStore } from '@/store/food.ts'
import { useRouter } from 'vue-router'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { onMounted } from 'vue'
import { onMounted, ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
const userStore = useUserStore()
const authStore = useAuthStore()
const appStore = useAppStore()
const foodStore = useFoodStore()
const router = useRouter()
const activeName = ref('basic')
onMounted(() => {
userStore.queryUser(userStore.getUserId() as number)
})
@@ -89,6 +108,16 @@ const passwordClick = () => {
// router.push('/mobile-home/profile/password')
}
const handleClick = async (tab: TabsPaneContext) => {
switch (tab.paneName) {
case 'moment':
await foodStore.queryMoment(userStore.getUserId())
break
default:
break
}
}
const refreshClick = async () => {
await router.push('/')
location.reload()

View File

@@ -1,26 +1,16 @@
<template>
<div class="common-mobile-view food-record">
<div class="food-query">
<el-input
v-model="foodInfo.query"
placeholder="搜索菜友食谱" :prefix-icon="Search" clearable
:maxlength="20" show-word-limit
/>
</div>
<hr style="width: 100%;"/>
<el-tabs v-model="foodStore.recordSegment"
stretch class="record-tabs"
@tab-click="handleClick">
<el-tab-pane label="菜谱" name="recipe">
<food-recipe />
<food-recipe v-loading="foodStore.isLoading" element-loading-text="加载中"/>
</el-tab-pane>
<el-tab-pane label="日历" name="daily">
<food-daily />
<food-daily v-loading="foodStore.isLoading" element-loading-text="加载中"/>
</el-tab-pane>
<el-tab-pane label="时间轴" name="timeline">
<food-timeline />
<food-timeline v-loading="foodStore.isLoading" element-loading-text="加载中"/>
</el-tab-pane>
</el-tabs>
</div>
@@ -30,41 +20,30 @@
import FoodRecipe from '@/components/Food/FoodRecipe.vue'
import FoodDaily from '@/components/Food/FoodDaily.vue'
import FoodTimeline from '@/components/Food/FoodTimeline.vue'
import { Search } from '@element-plus/icons-vue'
import { reactive } from 'vue'
import { useFoodStore } from '@/store/food'
import { TabsPaneContext } from 'element-plus'
import type { TabsPaneContext } from 'element-plus'
import { onMounted } from 'vue'
const foodStore = useFoodStore()
const foodInfo = reactive({
query: ''
onMounted(async () => {
await foodStore.queryCategory()
await foodStore.queryRecipeList()
})
const recordSegmentList = [{
label: '菜谱',
value: 'recipe'
},
{
label: '日历',
value: 'daily'
},
{
label: '时间轴',
value: 'timeline'
}]
const handleClick = async (tab: TabsPaneContext, event: Event) => {
const handleClick = async (tab: TabsPaneContext) => {
switch (tab.paneName) {
case 'recipe':
await foodStore.queryCategory()
await foodStore.queryRecipeList()
break
case 'daily':
foodStore.queryDateType = 'month'
await foodStore.refreshInfo()
await foodStore.queryRecordList()
break
case 'timeline':
foodStore.queryDateType = 'year'
await foodStore.refreshInfo()
await foodStore.queryRecordList()
break
default:
break
@@ -77,12 +56,5 @@ const handleClick = async (tab: TabsPaneContext, event: Event) => {
display: flex;
flex-direction: column;
gap: 5px;
.food-query {
}
.record-tabs {
}
}
</style>