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,50 @@
<template>
<div class="info-item card-item">
<div class="title-info">
<svg-icon name="home-basic"/>
<span class="sub-title">基础信息</span>
</div>
<div class="info travel-basic-info">
<div class="basic-item">
<span class="basic-title">景点地区</span>
<div class="basic-content">
<el-tag size="large">
<i class="fa-solid fa-location-dot"></i>
{{ travelStore.currentTravelSpot.area.join('-') }}
</el-tag>
</div>
</div>
<div class="basic-item">
<span class="basic-title">景点地址</span>
<div class="basic-content">
<el-tag v-if="travelStore.currentTravelSpot.address"
size="large">
<i class="fa-solid fa-map-location-dot"></i>
{{ travelStore.currentTravelSpot.address }}
</el-tag>
<el-tag v-else></el-tag>
</div>
</div>
<div class="basic-item">
<span class="basic-title">景点价格</span>
<div class="basic-content">
<el-tag size="large">
<i class="fa-solid fa-ticket"></i>
{{ formatAmount(travelStore.currentTravelSpot.price) }}
</el-tag>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { SvgIcon } from 'vue3-common'
import { useTravelStore } from '@/store/travel.ts'
import { formatAmount } from 'vue3-common/utils/numberUtil'
const travelStore = useTravelStore()
</script>

View File

@@ -0,0 +1,25 @@
<template>
<el-table :data="props.data" border>
<el-table-column type="index" align="center" width="50" />
<el-table-column prop="type" label="类型" align="center" min-width="20%"/>
<el-table-column prop="name" label="名称" align="center" min-width="40%"/>
<el-table-column prop="amount" label="金额" align="center" min-width="20%">
<template #default="scope">
{{ formatAmount(scope.row.amount) }}
</template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import { formatAmount } from 'vue3-common/utils/numberUtil'
import { defineProps, PropType } from 'vue'
import { ITravelConsume } from '@/types/travel.ts'
const props = defineProps({
data: {
required: true,
type: Array as PropType<ITravelConsume[]>
}
})
</script>

View File

@@ -0,0 +1,76 @@
<template>
<div class="info-item card-item">
<div class="title-info">
<svg-icon name="home-guideInfo"></svg-icon>
<span class="sub-title">攻略信息</span>
</div>
<div class="info guide-info">
<div class="guide-item">
<span class="guide-title">交通指南</span>
<div v-if="travelStore.currentTravelSpot.traffic" class="guide-content">
{{ travelStore.currentTravelSpot.traffic }}
</div>
<div v-else class="guide-content">
<el-tag></el-tag>
</div>
</div>
<div class="guide-item">
<span class="guide-title">游玩路线</span>
<div v-if="travelStore.currentTravelSpot.router" class="guide-content">
{{ travelStore.currentTravelSpot.router }}
</div>
<div v-else class="guide-content">
<el-tag></el-tag>
</div>
</div>
<div class="guide-item">
<span class="guide-title">美食推荐</span>
<div v-if="travelStore.currentTravelSpot.foods.length > 0"
class="guide-content">
<el-tag v-for="(item, index) in travelStore.currentTravelSpot.foods"
:key="index" size="large">
{{ item }}
</el-tag>
</div>
<div v-else class="guide-content">
<el-tag></el-tag>
</div>
</div>
<div class="guide-item">
<span class="guide-title">项目推荐</span>
<div v-if="travelStore.currentTravelSpot.projects.length > 0"
class="guide-content">
<el-tag v-for="(item, index) in travelStore.currentTravelSpot.projects"
:key="index" size="large">
{{ item }}
</el-tag>
</div>
<div v-else class="guide-content">
<el-tag></el-tag>
</div>
</div>
<div class="guide-item">
<span class="guide-title">其他</span>
<div class="guide-content">
<el-tag v-if="travelStore.currentTravelSpot.remark"
size="large">
{{ travelStore.currentTravelSpot.remark }}
</el-tag>
<el-tag v-else></el-tag>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { SvgIcon } from 'vue3-common'
import { useTravelStore } from '@/store/travel.ts'
const travelStore = useTravelStore()
</script>

View File

@@ -0,0 +1,47 @@
<template>
<div class="info-item">
<div class="title-info">
<svg-icon name="home-travel-record"></svg-icon>
<span class="sub-title">旅行记录</span>
</div>
<div v-if="travelStore.currentTravelSpot.recordList.length > 0" class="info record-info">
<el-timeline>
<el-timeline-item v-for="(item, index) in travelStore.currentTravelSpot.recordList"
:key="index" :timestamp="item.date" placement="top">
<el-card class="record-card">
<span class="record-content">旅行人{{ item.persons.join('-') }}</span>
<el-image v-for="(image, index) in item.imageList" :key="index"
:src="serviceFileRootPath + image"
:preview-src-list="[serviceFileRootPath + image]"
fit="contain" alt="暂无图片" class="record-image"/>
<video v-for="(video, index) in item.videoList"
:key="index" controls style="width: 60%;min-height: 200px;">
<source :src="serviceFileRootPath + video" type="video/mp4">
您的浏览器不支持 HTML5 视频
</video>
<!-- <el-carousel v-if="item.imageList.length > 0"-->
<!-- arrow="always">-->
<!-- <el-carousel-item v-for="(item, index) in item.imageList" :key="index">-->
<!-- -->
<!-- </el-carousel-item>-->
<!-- </el-carousel>-->
<!-- <el-empty v-else description="暂无记录"/>-->
</el-card>
</el-timeline-item>
</el-timeline>
</div>
<el-empty v-else description="暂无记录"/>
</div>
</template>
<script setup lang="ts">
import { SvgIcon } from 'vue3-common'
import { useTravelStore } from '@/store/travel.ts'
import { serviceFileRootPath } from '@/utils'
const travelStore = useTravelStore()
</script>

View File

@@ -0,0 +1,106 @@
<template>
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
center @open="openDialogEvent" class="travel-record-dialog">
<el-form ref="formRef" :model="dialogData.data"
:rules="rules" label-width="80">
<el-form-item label="景点名称" prop="name">
<el-select
v-model="travelStore.travelRecordDialog.data.name"
@focus="travelStore.queryTravelSpotNameList()"
:disabled="travelStore.travelRecordApiType === 'UPDATE'"
placeholder="请选择景点名称">
<el-option
v-for="(item, index) in travelStore.travelSpotNameList"
:key="index" :label="item" :value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="旅行日期" prop="date">
<el-date-picker
v-model="dialogData.data.date"
type="date" placeholder="请选择日期"
value-format="YYYY-MM-DD"/>
</el-form-item>
<el-form-item label="旅行人员" prop="persons">
<multi-input :data="dialogData.data.persons"
:size="5"/>
</el-form-item>
<el-form-item label="图片">
<upload-image v-model="dialogData.data.imageList"
:service-url="fileServiceUrl"
:image-limit="3"
:service-file-root-path="serviceFileRootPath"
:image-path="travelStore.travelImagePath"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
<el-button v-if="travelStore.travelRecordApiType === 'UPDATE'"
type="danger" @click="deleteClick">删除</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { UploadImage } from 'vue3-common'
import { useTravelStore } from '@/store/travel.ts'
import { ref, reactive, computed, nextTick } from 'vue'
import { FormRules } from 'element-plus'
import { travelRecordRules } from '@/utils/element/elRules.ts'
import { MultiInput } from 'vue3-common'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const rules = reactive<FormRules>(travelRecordRules)
const formRef = ref()
const travelStore = useTravelStore()
const dialogData = computed(() => travelStore.travelRecordDialog)
/**
* 打开对话框事件
*/
const openDialogEvent = async () => {
await nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(() => {
travelStore.handleTravelRecordApi(dialogData.value.data)
})
}
const cancelClick = () => {
dialogData.value.visible = false
}
const deleteClick = () => {
commonElMessageBox('是否确认删除该记录?').then(() => {
travelStore.travelRecordApiType = 'DELETE'
travelStore.handleTravelRecordApi(dialogData.value.data)
})
}
</script>
<style lang="scss">
.travel-record-dialog {
width: 40%;
.el-dialog__body {
height: 45vh;
}
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="travel-daily">
<v-calendar ref="calendarRef" :attributes='dailyInfo.attrs'
@dayclick="dateClick" @did-move="changeMonthClick"
:is-dark="appStore.isDark"
class="travel-calendar card-item"/>
<travel-daily-item :date="dailyInfo.calendarDate"/>
</div>
</template>
<script setup lang="ts">
import TravelDailyItem from '@/components/Travel/TravelDailyItem.vue'
import { reactive, watch, ref, onMounted } from 'vue'
import { useTravelStore } from '@/store/travel.ts'
import { formatDate } from 'vue3-common/utils/dateUtil'
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
import { useAppStore } from '@/store/app.ts'
const calendarRef = ref()
const travelStore = useTravelStore()
const appStore = useAppStore()
onMounted(() => {
const { month } = travelStore.queryRecord
calendarRef.value.move(new Date(month))
})
watch(() => travelStore.isRefresh, () => {
updateInfo()
})
const dailyInfo = reactive({
calendarDate: '',
attrs: [] as any[]
})
const updateInfo = () => {
getCalendarMoveDate()
getCalendarAttrDates()
}
const getCalendarMoveDate = () => {
const recordLength = travelStore.travelRecordList.length
if (recordLength > 0) {
const today = formatDate(new Date())
if (travelStore.travelRecordList.some((item) => item.date === today)) {
dailyInfo.calendarDate = today
} else {
dailyInfo.calendarDate = formatDate(travelStore.travelRecordList[recordLength - 1].date)
}
} else {
dailyInfo.calendarDate = ''
}
}
const getCalendarAttrDates = () => {
dailyInfo.attrs = []
dailyInfo.attrs.push({
key: 'today',
highlight: true,
dates: new Date()
})
const dateList = travelStore.travelRecordList.map((item) => item.date)
dailyInfo.attrs.push({
dot: 'red',
dates: [...new Set(dateList)]
})
}
/**
* 选择日期事件
* @param day 日期
*/
const dateClick = (day: CalendarDay) => {
dailyInfo.calendarDate = formatDate(day.date)
}
/**
* 选择月份事件
*/
const changeMonthClick = async (value) => {
travelStore.queryRecord.month = value[0].id
await travelStore.refreshInfo()
}
</script>
<style lang="scss">
.travel-daily {
height: 100%;
display: flex;
flex-direction: column;
gap: 1vh;
.travel-calendar {
width: 100%;
justify-self: center;
align-self: center;
}
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<div class="life-record-card card-item">
<el-carousel v-if="props.travelItem?.recordList.length > 0"
arrow="always" @change="changeTravelEvent">
<el-carousel-item v-for="(item, index) in props.travelItem?.recordList" :key="index">
<el-image :src="serviceFileRootPath + item.imageList[0]"
fit="contain" alt="暂无图片">
<template #error>
<div class="image-slot">
<span>暂无图片</span>
</div>
</template>
</el-image>
</el-carousel-item>
</el-carousel>
<el-empty v-else description="暂无记录"/>
<div class="record-info">
<div class="top-container">
<div class="info-list">
<div class="info-item">
<span class="title">{{ props.travelItem.name }}</span>
</div>
</div>
<div class="button-info">
<el-button type="primary" :icon="Edit" circle :size="'small'"
@click="editClick"/>
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
@click="viewClick()"/>
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
@click="deleteClick()"/>
</div>
</div>
<div class="bottom-container">
<div class="info-list">
<div class="info-item location-item">
<i class="fa-solid fa-location-dot"></i>
{{ props.travelItem.area.join('-') }}
</div>
</div>
<div v-if="props.travelItem?.recordList.length > 0" class="info-list">
<div class="info-item">
<i class="fa-solid fa-flag"></i>
{{ parsePerson(props.travelItem.recordList[cardInfo.currentIndex].persons) }}
</div>
<div class="info-item">
<i class="fa-regular fa-calendar-check"></i>
{{ formatDate(props.travelItem.recordList[cardInfo.currentIndex].date) }}
</div>
</div>
<div v-else>
<span>暂无记录</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
import { useTravelStore } from '@/store/travel.ts'
import { useAppStore } from '@/store/app.ts'
import { defineProps, PropType, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { formatDate } from 'vue3-common/utils/dateUtil'
import { ITravelSpot } from '@/types/travel.ts'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils'
import { parsePerson } from '@/utils/home/travelUtil.ts'
const router = useRouter()
const travelStore = useTravelStore()
const appStore = useAppStore()
const cardInfo = reactive({
currentIndex: 0
})
const props = defineProps({
travelItem: {
required: true,
type: Object as PropType<ITravelSpot>
}
})
const editClick = async () => {
const id = props.travelItem?.id
travelStore.travelSpotApiType = 'UPDATE'
await travelStore.queryTravelSpot(id as number)
appStore.isShowMobileBack = true
await router.push({ name: 'TravelSpotForm' })
}
const viewClick = async () => {
appStore.isShowMobileBack = true
await router.push({ name: 'TravelDetailId', params: { id: props.travelItem?.id } })
}
const deleteClick = () => {
travelStore.travelSpotApiType = 'DELETE'
commonElMessageBox(`请确认是否删除该旅行: ${props.travelItem?.name}?`).then(() => {
travelStore.handleTravelSpotApi(props.travelItem)
})
}
const changeTravelEvent = (current: number) => {
cardInfo.currentIndex = current
}
</script>
<style lang="scss">
@use "@/styles/home/life.record.module";
</style>

View File

@@ -0,0 +1,104 @@
<template>
<el-empty v-if="dailyItemInfo.itemList.length === 0" />
<div v-else class="travel-daily-item card-item">
<daily-summary :date="dailyItemInfo.date"/>
<div class="daily-list">
<daily-card v-for="(item, index) in dailyItemInfo.itemList"
:key="index" @click="selectTravelRecordClick(item)"
:title="item.name" :content="item.area.join('-')">
<template #icon>
<el-avatar>{{ item.area[0].charAt(0) }}</el-avatar>
</template>
<template #extra>
<span>{{ parsePerson(item.persons)}}</span>
<span>{{ item.date }}</span>
</template>
</daily-card>
</div>
</div>
</template>
<script setup lang="ts">
import DailySummary from '@/components/DailySummary.vue'
import DailyCard from '@/components/DailyCard.vue'
import { defineProps, onMounted, reactive, watch } from 'vue'
import { useTravelStore } from '@/store/travel.ts'
import { ITravelRecord } from '@/types/travel.ts'
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
import { useAppStore } from '@/store/app.ts'
import { useRouter } from 'vue-router'
import { parsePerson } from '@/utils/home/travelUtil.ts'
const travelStore = useTravelStore()
const appStore = useAppStore()
const router = useRouter()
const props = defineProps({
date: {
required: true,
type: String
}
})
watch(() => props.date, () => {
getTravelDailyByDate(props.date as string)
})
watch(() => travelStore.isRefresh, () => {
getTravelDailyByDate(props.date as string)
})
onMounted(() => {
getTravelDailyByDate(props.date as string)
})
const dailyItemInfo = reactive({
date: '',
itemList: [] as ITravelRecord[]
})
/**
* 根据日期获取当天的美食记录
* @param date
*/
const getTravelDailyByDate = (date: string) => {
if (date) {
dailyItemInfo.date = formatChineseDate(date, 'MM月DD日 dddd')
dailyItemInfo.itemList = []
travelStore.travelRecordList.forEach((item) => {
if (item.date === date) {
dailyItemInfo.itemList.push(item)
}
})
} else {
dailyItemInfo.itemList = []
}
}
const selectTravelRecordClick = async (travelRecord: ITravelRecord) => {
travelStore.travelRecordApiType = 'UPDATE'
appStore.isShowMobileBack = true
travelStore.currentTravelRecord = travelRecord
await router.push({ name: 'TravelRecordForm' })
}
</script>
<style lang="scss">
.travel-daily-item {
display: flex;
flex-direction: column;
gap: 1vh;
padding: 1vh 0.5vw;
.daily-list {
display: flex;
flex-direction: column;
gap: 1vh;
overflow: auto;
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<div class="travel-daily-list">
<el-date-picker v-model="travelStore.queryRecord.year"
type="year" :editable="false" style="width: 120px;"
@change="travelStore.refreshInfo()"/>
<el-empty v-if="travelDailyInfo.dateList.length === 0"/>
<el-scrollbar>
<travel-daily-item v-for="(item, index) in travelDailyInfo.dateList"
:key="index" :date="item"/>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import TravelDailyItem from '@/components/Travel/TravelDailyItem.vue'
import { reactive, watch } from 'vue'
import { useTravelStore } from '@/store/travel.ts'
const travelStore = useTravelStore()
watch(() => travelStore.isRefresh, () => {
updateInfo()
})
const travelDailyInfo = reactive({
dateList: [] as string[]
})
const updateInfo = () => {
const dateList = travelStore.travelRecordList.map((item) => item.date)
travelDailyInfo.dateList = [...new Set(dateList)] as string[]
}
</script>
<style lang="scss">
.travel-daily-list {
height: calc(100vh - 120px);
overflow: auto;
display: flex;
flex-direction: column;
gap: 1vh;
.el-date-editor {
align-self: center;
}
.el-scrollbar {
.el-scrollbar__view {
display: flex;
flex-direction: column;
gap: 2vh;
}
}
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
center @open="openDialogEvent" width="50%" class="travel-dialog">
<el-form ref="formRef" :model="dialogData.data" :rules=rules label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="景点名称" prop="name">
<el-input
v-model="dialogData.data.name"
placeholder="请输入景点名称" clearable
show-word-limit maxlength="20"
/>
</el-form-item>
<el-form-item label="景点地区" prop="area">
<el-cascader
v-model="dialogData.data.area"
placeholder="请选择景点地区" :options="pcTextArr">
</el-cascader>
</el-form-item>
<el-form-item label="景点地址" prop="address">
<el-input
v-model="dialogData.data.address"
placeholder="请输入景点地址" clearable
show-word-limit maxlength="50"
/>
</el-form-item>
<el-form-item label="门票价格" prop="price">
<el-input
v-model="dialogData.data.price"
placeholder="请输入门票价格" clearable
:formatter="(value: string) => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
:parser="(value: string) => value.replace(/\$\s?|(,*)/g, '')"
/>
</el-form-item>
<el-form-item label="推荐指数" prop="recommendRate">
<el-rate v-model="dialogData.data.recommendRate"
:colors="travelStore.rateColorList"/>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="dialogData.data.remark" type="textarea"
placeholder="请输入备注" clearable
:rows="5" show-word-limit maxlength="200">
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="交通指南">
<el-input v-model="dialogData.data.traffic" type="textarea"
placeholder="请输入交通指南" clearable
:rows="5" show-word-limit maxlength="200">
</el-input>
</el-form-item>
<el-form-item label="旅游路线">
<el-input v-model="dialogData.data.router" type="textarea"
placeholder="请输入旅游路线" clearable
:rows="5" show-word-limit maxlength="200">
</el-input>
</el-form-item>
<el-form-item label="美食推荐">
<multi-input :data="dialogData.data.foods" :size="5"/>
</el-form-item>
<el-form-item label="项目推荐">
<multi-input :data="dialogData.data.projects" :size="5"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { useTravelStore } from '@/store/travel.ts'
import { ref, computed, reactive, nextTick } from 'vue'
import { MultiInput } from 'vue3-common'
import { pcTextArr } from 'element-china-area-data'
import { FormRules } from 'element-plus'
import { travelRules } from '@/utils/element/elRules.ts'
const rules = reactive<FormRules>(travelRules)
const formRef = ref()
const travelStore = useTravelStore()
const dialogData = computed(() => travelStore.travelSpotDialog)
/**
* 打开对话框事件
*/
const openDialogEvent = async () => {
await nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(() => {
travelStore.handleTravelSpotApi(travelStore.travelSpotDialog.data)
})
}
const cancelClick = () => {
dialogData.value.visible = false
}
</script>

View File

@@ -0,0 +1,68 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="query-item">
<span>城市</span>
<el-segmented v-model="travelStore.querySpot.category"
:options="queryCategoryList"
@change="queryTravelSegmented"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="travelStore.queryDateType"
@change="travelStore.refreshInfo()"
style="width: 120px;align-self: center;">
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
:label="item.label" :value="item.value"/>
</el-select>
</div>
</template>
<script setup lang="ts">
import { useTravelStore } from '@/store/travel.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { ref, onMounted } from 'vue'
import { ICategory } from '@/types'
const travelStore = useTravelStore()
const queryCategoryList = ref([] as ICategory[])
const dateTypeOptions = [{
label: '按月选择',
value: 'month'
},
{
label: '按年选择',
value: 'year'
}]
onMounted(async () => {
await travelStore.queryTravelCategory()
initCategoryList()
})
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({
label: '全部',
value: ''
})
travelStore.travelCategoryList.forEach((item) => {
queryCategoryList.value.push({
label: item,
value: item
})
})
}
const queryTravelSegmented = async () => {
travelStore.pageInfo = {
currentPage: 1,
pageSize: 3,
totalSize: 1
}
travelStore.travelSpotList = []
await travelStore.queryMobileTravelSpotList()
}
</script>

View File

@@ -0,0 +1,39 @@
<template>
<div class="travel-spot-list card-item">
<el-empty v-if="travelStore.travelSpotList.length === 0" description="暂无旅行记录"/>
<div v-else class="list">
<travel-card v-for="(item, index) in travelStore.travelSpotList"
:key="index" :travel-item="item"/>
</div>
<el-pagination
v-model="travelStore.pageInfo.currentPage"
:page-size="travelStore.pageInfo.pageSize"
layout="total, prev, pager, next"
:total="travelStore.pageInfo.totalSize"
/>
<el-button v-if="isLargeScreen()"
type="primary" :icon="Plus" circle
@click="addTravelSpotClick" class="add-new" />
</div>
</template>
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import TravelCard from '@/components/Travel/TravelCard.vue'
import { useTravelStore } from '@/store/travel.ts'
import { isLargeScreen } from '@/utils'
const travelStore = useTravelStore()
const addTravelSpotClick = () => {
travelStore.travelSpotApiType = 'ADD'
travelStore.travelSpotDialog = {
title: '新增景点',
visible: true,
data: travelStore.getEmptyTravelSpot()
}
}
</script>

View File

@@ -0,0 +1,26 @@
<template>
<div class="common-statistics">
<stats-card
title="景点总数" color="#CA6C65" unit="个" icon="iconfont icon-caipu"
:value="travelStore.travelStats.spotCount" />
<stats-card
title="旅行城市" color="#6FBB69" unit="个" icon="iconfont icon-leibie"
:value="travelStore.travelStats.cityCount" />
<stats-card
title="旅行次数" color="#E4BF62" unit="次" icon="iconfont icon-cishu"
:value="travelStore.travelStats.recordCount" />
<stats-card
title="平均次数" color="#5470C6" unit="次/月" icon="iconfont icon-pinlv"
:value="1" />
</div>
</template>
<script setup lang="ts">
import StatsCard from '@/components/StatsCard.vue'
import { useTravelStore } from '@/store/travel.ts'
const travelStore = useTravelStore()
</script>