feat:重构模块
This commit is contained in:
100
src/components/Journal/JournalCalendar.vue
Normal file
100
src/components/Journal/JournalCalendar.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="journal-daily">
|
||||
<v-calendar ref="calendarRef" :attributes='dailyInfo.attrs'
|
||||
@dayclick="dateClick" @did-move="changeMonthClick"
|
||||
:is-dark="appStore.isDark"
|
||||
class="journal-calendar card-item"/>
|
||||
|
||||
<journal-card v-if="journalStore.currentJournal.id !== 0"
|
||||
:item="journalStore.currentJournal"/>
|
||||
|
||||
<el-empty v-else description="今日暂无日记" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import JournalCard from '@/components/Journal/JournalCard.vue'
|
||||
import { reactive, watch, ref } from 'vue'
|
||||
import { formatDate, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
||||
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { useJournalStore } from '@/store/journal.ts'
|
||||
import { IJournal } from '@/types/journal.ts'
|
||||
|
||||
const calendarRef = ref()
|
||||
const appStore = useAppStore()
|
||||
const journalStore = useJournalStore()
|
||||
|
||||
watch(() => journalStore.isRefresh, () => {
|
||||
updateInfo()
|
||||
})
|
||||
|
||||
const dailyInfo = reactive({
|
||||
calendarDate: '',
|
||||
attrs: [] as any[]
|
||||
})
|
||||
|
||||
const updateInfo = () => {
|
||||
getCalendarMoveDate()
|
||||
getCalendarAttrDates()
|
||||
}
|
||||
|
||||
const getCalendarMoveDate = () => {
|
||||
const recordLength = journalStore.journalList.length
|
||||
if (recordLength > 0) {
|
||||
journalStore.currentJournal = getJournalDay(formatDate(new Date()))
|
||||
}
|
||||
}
|
||||
|
||||
const getCalendarAttrDates = () => {
|
||||
dailyInfo.attrs = []
|
||||
dailyInfo.attrs.push({
|
||||
key: 'today',
|
||||
highlight: true,
|
||||
dates: new Date()
|
||||
})
|
||||
|
||||
const dateList = journalStore.journalList.map((item) => item.date)
|
||||
dailyInfo.attrs.push({
|
||||
dot: 'red',
|
||||
dates: [...new Set(dateList)]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择日期事件
|
||||
* @param day 日期
|
||||
*/
|
||||
const dateClick = (day: CalendarDay) => {
|
||||
journalStore.currentJournal = getJournalDay(formatDate(day.date))
|
||||
}
|
||||
|
||||
const getJournalDay = (date: string): IJournal => {
|
||||
return journalStore.journalList.find((item) => formatDate(item.date) === date)
|
||||
|| journalStore.getEmptyJournal()
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择月份事件
|
||||
*/
|
||||
const changeMonthClick = async (value) => {
|
||||
const month = value[0].id
|
||||
journalStore.queryInfo.startDate = getStartEndDate(month, 'month')[0]
|
||||
journalStore.queryInfo.endDate = getStartEndDate(month, 'month')[1]
|
||||
await journalStore.refreshInfo()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.journal-daily {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1vh;
|
||||
|
||||
.journal-calendar {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
180
src/components/Journal/JournalCard.vue
Normal file
180
src/components/Journal/JournalCard.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div class="journal-note card-item">
|
||||
<div class="journal-head" @click="editClick">
|
||||
<div class="head-left">
|
||||
<span class="time">
|
||||
{{ formatDate(props.item.date, 'HH:mm') }}
|
||||
</span>
|
||||
|
||||
<span v-if="props.item?.location" class="location">
|
||||
<i class="fa-solid fa-location-dot"></i>
|
||||
{{ props.item.location }}
|
||||
</span>
|
||||
|
||||
<el-tag v-if="props.item?.category" type="primary">
|
||||
{{ props.item.category }}
|
||||
</el-tag>
|
||||
|
||||
<span v-if="props.item?.mood" class="emoji">
|
||||
{{ props.item.mood }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<svgIcon v-if="props.item?.weather" :name="`weather-${props.item?.weather}`"/>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="journal-body">
|
||||
<!-- 可折叠文本 -->
|
||||
<div
|
||||
ref="textContent"
|
||||
class="text-content"
|
||||
:class="{ 'collapsed': !expanded }"
|
||||
@click="toggleExpand">
|
||||
{{ props.item.content }}
|
||||
</div>
|
||||
|
||||
<!-- 单媒体内容(图片优先于视频) -->
|
||||
<el-image
|
||||
v-if="props.item?.imageUrl"
|
||||
:src="serviceFileRootPath + props.item?.imageUrl"
|
||||
:preview-src-list="[serviceFileRootPath + props.item?.imageUrl]"
|
||||
fit="contain"
|
||||
class="media-content"/>
|
||||
|
||||
<video
|
||||
v-if="props.item?.videoUrl"
|
||||
controls
|
||||
class="media-content">
|
||||
<source :src="serviceFileRootPath + props.item?.videoUrl" type="video/mp4">
|
||||
</video>
|
||||
|
||||
<!-- 展开/收起按钮 -->
|
||||
<el-button
|
||||
v-if="showToggle"
|
||||
text class="toggle-btn"
|
||||
@click.stop="toggleExpand">
|
||||
<el-icon>
|
||||
<ArrowUp v-if="expanded" />
|
||||
<ArrowDown v-else />
|
||||
</el-icon>
|
||||
{{ expanded ? '收起内容' : `展开全文(${overflowLines}行)` }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, PropType, defineProps } from 'vue'
|
||||
import { ElTag, ElButton, ElImage } from 'element-plus'
|
||||
import { SvgIcon } from 'vue3-common'
|
||||
import { ArrowUp, ArrowDown } from '@element-plus/icons-vue'
|
||||
import { IJournal } from '@/types/journal.ts'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { useJournalStore } from '@/store/journal.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { serviceFileRootPath } from '@/utils'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
required: true,
|
||||
type: Object as PropType<IJournal>
|
||||
}
|
||||
})
|
||||
|
||||
const journalStore = useJournalStore()
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(() => {
|
||||
if (overflowLines.value <= 0) expanded.value = true
|
||||
})
|
||||
|
||||
// 文本展开逻辑
|
||||
const expanded = ref(false)
|
||||
const textContent = ref<HTMLElement>()
|
||||
const maxLines = 3
|
||||
|
||||
const overflowLines = computed(() => {
|
||||
if (!textContent.value) return 0
|
||||
const lineHeight = parseInt(getComputedStyle(textContent.value).lineHeight, 10)
|
||||
return Math.round((textContent.value.scrollHeight - textContent.value.clientHeight) / lineHeight)
|
||||
})
|
||||
|
||||
const showToggle = computed(() => overflowLines.value > 0)
|
||||
|
||||
const toggleExpand = () => expanded.value = !expanded.value
|
||||
|
||||
const editClick = async () => {
|
||||
const id = props.item?.id
|
||||
journalStore.journalApiType = 'UPDATE'
|
||||
journalStore.currentJournal = props.item
|
||||
|
||||
if (isMobile()) {
|
||||
appStore.isShowMobileBack = true
|
||||
await router.push({ name: 'JournalDetailId', params: { id } })
|
||||
} else {
|
||||
journalStore.journalDialog = {
|
||||
title: '编辑日记',
|
||||
visible: true,
|
||||
data: deepCopyObject(journalStore.currentJournal)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.journal-note {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.journal-head {
|
||||
padding: 10px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.head-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: darkgrey;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.journal-body {
|
||||
padding: 10px;
|
||||
|
||||
.text-content {
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
line-height: 2;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
&.collapsed {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: v-bind(maxLines);
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
.media-content {
|
||||
width: 100%;
|
||||
// height: 300px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
padding: 0;
|
||||
color: #999;
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user