feat:重构模块

This commit is contained in:
2025-10-08 22:06:39 +08:00
parent f60d78fe8b
commit 2e81e57774
222 changed files with 2267 additions and 3059 deletions

View 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>

View 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>