feat:重构模块
This commit is contained in:
@@ -29,7 +29,7 @@ import { defineProps, PropType } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAnniversaryStore } from '@/store/anniversary.ts'
|
||||
import { IAnniversary } from '@/types/anniversary.ts'
|
||||
import { getAnniversaryRemainDays } from '@/utils/home/anniversaryUtil'
|
||||
import { getAnniversaryRemainDays } from '@/utils/home/anniversaryUtil.ts'
|
||||
|
||||
const router = useRouter()
|
||||
const anniversaryStore = useAnniversaryStore()
|
||||
@@ -47,7 +47,7 @@ const selectDateCardClick = async () => {
|
||||
if (props.dateItem?.category !== '节假日') {
|
||||
anniversaryStore.currentAnniversary = props.dateItem as IAnniversary
|
||||
const { id } = anniversaryStore.currentAnniversary
|
||||
await router.push({ name: 'MobileHomeAnniversaryDetailId', params: { id } })
|
||||
await router.push({ name: 'AnniversaryDetailId', params: { id } })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, PropType } from 'vue'
|
||||
import { IAnniversaryCategory } from '@/types/anniversary'
|
||||
import { useAnniversaryStore } from '@/store/anniversary'
|
||||
import { IAnniversaryCategory } from '@/types/anniversary.ts'
|
||||
import { useAnniversaryStore } from '@/store/anniversary.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -29,7 +29,7 @@ const anniversaryStore = useAnniversaryStore()
|
||||
|
||||
const selectCategoryClick = () => {
|
||||
anniversaryStore.queryInfo.category = props.categoryItem?.name as string
|
||||
router.push({ name: 'MobileHomeAnniversaryList' })
|
||||
router.push({ name: 'AnniversaryList' })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,11 +33,9 @@
|
||||
<script setup lang="ts">
|
||||
import { SvgIcon } from 'vue3-common'
|
||||
import { defineProps, PropType } from 'vue'
|
||||
import { IAsset, IAssetItem } from '@/types/asset'
|
||||
import { IAsset, IAssetItem } from '@/types/asset.ts'
|
||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { useAssetStore } from '@/store/asset'
|
||||
import { useAssetStore } from '@/store/asset.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -62,15 +60,7 @@ const editClick = async (asset: IAssetItem) => {
|
||||
assetStore.assetApiType = 'UPDATE'
|
||||
assetStore.currentAssetRecord = assetStore.assetRecordList.find((item) => item.id === asset.id)
|
||||
|
||||
if (isMobile()) {
|
||||
await router.push({ name: 'MobileHomeAssetDetailId', params: { id: asset.id } })
|
||||
} else {
|
||||
assetStore.assetDialog = {
|
||||
title: '编辑资产',
|
||||
visible: true,
|
||||
data: deepCopyObject(assetStore.currentAssetRecord)
|
||||
}
|
||||
}
|
||||
await router.push({ name: 'AssetDetailId', params: { id: asset.id } })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -62,13 +62,13 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useBillStore } from '@/store/bill'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { IBillBook } from '@/types/bill'
|
||||
import { billBookRules } from '@/utils/element/elRules'
|
||||
import { IBillBook } from '@/types/bill.ts'
|
||||
import { billBookRules } from '@/utils/element/elRules.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import type { IDialog, IHandleApi } from 'vue3-common/types'
|
||||
import { addBillBookApi, updateBillBookApi } from '@/apis/bill'
|
||||
import { addBillBookApi, updateBillBookApi } from '@/apis/bill.ts'
|
||||
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
|
||||
|
||||
const formRef = ref()
|
||||
@@ -12,13 +12,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BillDailyItem from '@/components/Home/Bill/BillDailyItem.vue'
|
||||
import BillDailyItem from '@/components/Bill/BillDailyItem.vue'
|
||||
import { reactive, watch, ref, onMounted } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
import { CalendarDay, Page } from 'v-calendar/dist/types/src/utils/page'
|
||||
import { getCalendarDateList } from '@/utils/home/billUtil.ts'
|
||||
import { useAppStore } from '@/store/app'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
|
||||
const calendarRef = ref()
|
||||
const billStore = useBillStore()
|
||||
@@ -96,11 +96,11 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useBillStore } from '@/store/bill'
|
||||
import { IBillBook, IBillCategory } from '@/types/bill'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { IBillBook, IBillCategory } from '@/types/bill.ts'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { billCategoryRules } from '@/utils/element/elRules'
|
||||
import { addBillCategoryApi, updateBillCategoryApi } from '@/apis/bill'
|
||||
import { billCategoryRules } from '@/utils/element/elRules.ts'
|
||||
import { addBillCategoryApi, updateBillCategoryApi } from '@/apis/bill.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { tableHeaderStyle, tableRowStyle, tableCellStyle } from 'vue3-common/utils/elUtil'
|
||||
import type { IDialog, IHandleApi } from 'vue3-common/types'
|
||||
@@ -38,11 +38,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BillRank from '@/components/Home/Bill/BillRank.vue'
|
||||
import BillRank from '@/components/Bill/BillRank.vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
||||
import { useBillStore } from '@/store/bill'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import type { IStatsChart } from 'vue3-common/types'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
@@ -36,8 +36,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DailySummary from '@/components/Home/DailySummary.vue'
|
||||
import DailyCard from '@/components/Home/DailyCard.vue'
|
||||
import DailySummary from '@/components/DailySummary.vue'
|
||||
import DailyCard from '@/components/DailyCard.vue'
|
||||
import { defineProps, onMounted, reactive, watch } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -104,7 +104,7 @@ const selectBillRecordClick = async (billRecord: IBillRecord) => {
|
||||
|
||||
if (isMobile()) {
|
||||
await router.push({
|
||||
name: 'MobileHomeBillDetailId',
|
||||
name: 'BillDetailId',
|
||||
params: { id: billStore.currentBillRecord.id }
|
||||
})
|
||||
} else {
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BillDailyItem from '@/components/Home/Bill/BillDailyItem.vue'
|
||||
import BillDailyItem from '@/components/Bill/BillDailyItem.vue'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
|
||||
@@ -118,12 +118,12 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UploadImage, AmountInput } from 'vue3-common'
|
||||
import { useBillStore } from '@/store/bill'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { Location, Document } from '@element-plus/icons-vue'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { billRecordRules } from '@/utils/element/elRules'
|
||||
import { billRecordRules } from '@/utils/element/elRules.ts'
|
||||
import { nextTick, reactive, ref } from 'vue'
|
||||
import { IBillCategory } from '@/types/bill'
|
||||
import { IBillCategory } from '@/types/bill.ts'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
||||
|
||||
@@ -62,12 +62,12 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useBillStore } from '@/store/bill'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { IBillPay } from '@/types/bill'
|
||||
import { billPayRules } from '@/utils/element/elRules'
|
||||
import { IBillPay } from '@/types/bill.ts'
|
||||
import { billPayRules } from '@/utils/element/elRules.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { addBillPayApi, updateBillPayApi } from '@/apis/bill'
|
||||
import { addBillPayApi, updateBillPayApi } from '@/apis/bill.ts'
|
||||
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
|
||||
import type { IDialog, IHandleApi } from 'vue3-common/types'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBillStore } from '@/store/bill'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
|
||||
import { computed } from 'vue'
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StatsCard from '@/components/Home/StatsCard.vue'
|
||||
import { useBillStore } from '@/store/bill'
|
||||
import StatsCard from '@/components/StatsCard.vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { ref, watch } from 'vue'
|
||||
import { getBillSummaryStats } from '@/utils/home/billUtil'
|
||||
import { IBillSummaryStats } from '@/types/bill'
|
||||
import { getBillSummaryStats } from '@/utils/home/billUtil.ts'
|
||||
import { IBillSummaryStats } from '@/types/bill.ts'
|
||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||
|
||||
const billStore = useBillStore()
|
||||
62
src/components/Blog/Archive/BlogArchive.vue
Normal file
62
src/components/Blog/Archive/BlogArchive.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="blog-archive blog-section card-item">
|
||||
<h4 class="module-title">文章归档</h4>
|
||||
|
||||
<el-date-picker
|
||||
v-model="currentYear"
|
||||
type="year" placeholder="请选择年份"
|
||||
style="width: 100px"
|
||||
@change="changeDateEvent"
|
||||
/>
|
||||
|
||||
<span class="total">共计{{ blogStore.blogList.length }}篇文章</span>
|
||||
|
||||
<el-timeline class="time-line">
|
||||
<el-timeline-item v-for="(value, index) in blogStore.blogList" :key="index">
|
||||
<div class="item">
|
||||
<span class="date">{{ value.createTime }}</span>
|
||||
<span class="title" @click="viewBlogClick(index)">{{ value.title }}</span>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
const router = useRouter()
|
||||
const currentYear = ref(new Date())
|
||||
|
||||
onMounted(() => {
|
||||
blogStore.queryBlogByCondition({
|
||||
year: (new Date()).getFullYear()
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 选择年份事件
|
||||
* @param value 年份
|
||||
*/
|
||||
const changeDateEvent = async (value: Date) => {
|
||||
await blogStore.queryBlogByCondition({
|
||||
year: value.getFullYear()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击查看博客按钮
|
||||
* @param index 索引
|
||||
*/
|
||||
const viewBlogClick = async (index: number) => {
|
||||
const blogId = blogStore.blogList[index].id
|
||||
await router.push({ name: 'DetailId', params: { id: blogId } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/blog/blog.archive.module";
|
||||
</style>
|
||||
40
src/components/Blog/Category/BlogCategory.vue
Normal file
40
src/components/Blog/Category/BlogCategory.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="blog-category blog-section card-item">
|
||||
<h4 class="module-title">文章分类</h4>
|
||||
|
||||
<span class="total">目前共计{{ blogStore.blogCategoryList.length }}个分类</span>
|
||||
|
||||
<div class="category-list">
|
||||
<div v-for="(item, index) in blogStore.blogCategoryList"
|
||||
:key="index" class="item" @click="viewBlogCategoryClick(item.name)">
|
||||
<span class="label">{{ item.name }}</span>
|
||||
<span class="value">{{ `(${item.count})` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(() => {
|
||||
blogStore.queryBlogCategory()
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击查看博客分类信息
|
||||
* @param name 类别名称
|
||||
*/
|
||||
const viewBlogCategoryClick = async (name: string) => {
|
||||
await router.push({ name: 'CategoryName', params: { name } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/blog/blog.category.module";
|
||||
</style>
|
||||
41
src/components/Blog/Category/BlogCategoryDetail.vue
Normal file
41
src/components/Blog/Category/BlogCategoryDetail.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="blog-category-detail blog-section card-item">
|
||||
<span class="name">{{ route.params?.name }}</span>
|
||||
|
||||
<div class="category-detail-list">
|
||||
<div v-for="(item, index) in blogStore.blogList"
|
||||
:key="index" class="item" @click="viewBlogClick(index)">
|
||||
<span class="number">{{ index+1 }}</span>
|
||||
<span class="date">{{ item?.createTime }}</span>
|
||||
<span class="title">{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
onMounted(async () => {
|
||||
const categoryName = route.params.name as string
|
||||
await blogStore.queryBlogByCondition({
|
||||
category: categoryName
|
||||
})
|
||||
})
|
||||
|
||||
const viewBlogClick = async (index: number) => {
|
||||
const blogId = blogStore.blogList[index].id
|
||||
await router.push({ name: 'DetailId', params: { id: blogId } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/blog/blog.category.detail.module";
|
||||
</style>
|
||||
30
src/components/Blog/Comment/BlogComment.vue
Normal file
30
src/components/Blog/Comment/BlogComment.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="blog-comment card-item">
|
||||
<div class="comment-total">
|
||||
<span>共 {{ blogStore.blogNestedCommentList.length }} 条评论</span>
|
||||
</div>
|
||||
|
||||
<div class="comment-list">
|
||||
<blog-comment-item v-for="(item, index) in blogStore.blogNestedCommentList" :key="index"
|
||||
:comment="item.comment" :replyList="item.replies"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BlogCommentItem from '@/components/Comment/BlogCommentItem.vue'
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.blog-comment {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
121
src/components/Blog/Comment/BlogCommentEdit.vue
Normal file
121
src/components/Blog/Comment/BlogCommentEdit.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="blog-comment-edit card-item">
|
||||
<div class="comment-title">
|
||||
<el-input v-model="blogComment.name"
|
||||
:maxlength="10" show-word-limit
|
||||
placeholder="请输入昵称"/>
|
||||
<el-input v-model="blogComment.website"
|
||||
:maxlength="50" show-word-limit
|
||||
placeholder="请输入个人博客网站"/>
|
||||
</div>
|
||||
|
||||
<el-input v-model="blogComment.content"
|
||||
placeholder="有什么想和我说的呢" :maxlength="500"
|
||||
:rows="7" show-word-limit type="textarea"
|
||||
/>
|
||||
|
||||
<div class="comment-button">
|
||||
<el-popover ref="emojiPopoverRef" placement="top" :width="300" trigger="hover">
|
||||
<template #default>
|
||||
<div class="emoji-panel">
|
||||
<EmojiPicker :native="true" @select="onSelectEmoji" />
|
||||
</div>
|
||||
</template>
|
||||
<template #reference>
|
||||
<el-button circle>
|
||||
<i class="fa-regular fa-face-smile"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
<el-button type="primary"
|
||||
:disabled="blogComment.content.length === 0"
|
||||
@click="sendCommentClick"
|
||||
class="send-button">发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import EmojiPicker from 'vue3-emoji-picker'
|
||||
import 'vue3-emoji-picker/css'
|
||||
import { defineProps, ref, onMounted } from 'vue'
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { IBlogComment } from '@/types/blog.ts'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
const emojiPopoverRef = ref()
|
||||
|
||||
const props = defineProps({
|
||||
parentId: {
|
||||
required: true,
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const blogComment = ref<IBlogComment>({
|
||||
id: 0,
|
||||
blogId: 0,
|
||||
name: '',
|
||||
website: '',
|
||||
content: '',
|
||||
ipAddress: '',
|
||||
userAgent: '',
|
||||
parentId: 0,
|
||||
isApproved: true,
|
||||
createTime: ''
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
blogComment.value = deepCopyObject(blogStore.getEmptyBlogComment())
|
||||
})
|
||||
|
||||
const onSelectEmoji = (emoji) => {
|
||||
if (blogComment.value.content.length <= 498) {
|
||||
blogComment.value.content += emoji.i
|
||||
}
|
||||
|
||||
emojiPopoverRef.value.hide()
|
||||
}
|
||||
|
||||
const sendCommentClick = async () => {
|
||||
blogComment.value.parentId = props.parentId
|
||||
blogComment.value.blogId = blogStore.currentBlogId
|
||||
|
||||
if (!blogComment.value.name) {
|
||||
blogComment.value.name = 'Anonymous'
|
||||
}
|
||||
|
||||
await blogStore.addBlogComment(blogComment.value)
|
||||
blogComment.value = deepCopyObject(blogStore.getEmptyBlogComment())
|
||||
blogStore.isShowBlogCommentEdit = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.blog-comment-edit {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.comment-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.comment-button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.send-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
127
src/components/Blog/Comment/BlogCommentItem.vue
Normal file
127
src/components/Blog/Comment/BlogCommentItem.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="blog-comment-card">
|
||||
<el-avatar :src="`https://api.dicebear.com/6.x/adventurer/svg?seed=${props.comment.name}`"
|
||||
:size="50" alt="未加载"/>
|
||||
|
||||
<div class="blog-comment-content">
|
||||
<div class="basic-info">
|
||||
<span>{{ props.comment.name }}</span>
|
||||
<span class="content">{{ getBrowser(props.comment.userAgent) }}</span>
|
||||
<span class="content">{{ getOsInfo(props.comment.userAgent) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="sub-info">
|
||||
<span class="content">{{ props.comment.createTime }}</span>
|
||||
<el-button v-if="!blogComment.isShowEdit"
|
||||
type="primary" text @click="replyClick">回复</el-button>
|
||||
<el-button v-else type="primary"
|
||||
text @click="cancelClick">取消</el-button>
|
||||
</div>
|
||||
|
||||
<p>{{ props.comment.content }}</p>
|
||||
|
||||
<blog-comment-edit v-if="blogComment.isShowEdit"
|
||||
:parent-id="props.comment.id"/>
|
||||
|
||||
<div class="reply-list">
|
||||
<div class="blog-comment-card" v-for="(item, index) in props.replyList" :key="index">
|
||||
<el-avatar :src="`https://api.dicebear.com/6.x/adventurer/svg?seed=${item.name}`"
|
||||
:size="50" alt="未加载"/>
|
||||
|
||||
<div class="blog-comment-content">
|
||||
<div class="basic-info">
|
||||
<span>{{ item.name }}</span>
|
||||
<span class="content">{{ getBrowser(props.comment.userAgent) }}</span>
|
||||
<span class="content">{{ getOsInfo(props.comment.userAgent) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="sub-info">
|
||||
<span class="content">{{ item.createTime }}</span>
|
||||
</div>
|
||||
<p>{{ item.content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BlogCommentEdit from '@/components/Comment/BlogCommentEdit.vue'
|
||||
import 'vue3-emoji-picker/css'
|
||||
import { defineProps, PropType, reactive } from 'vue'
|
||||
import { IBlogComment } from '@/types/blog.ts'
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
|
||||
const props = defineProps({
|
||||
comment: {
|
||||
required: true,
|
||||
type: Object as PropType<IBlogComment>
|
||||
},
|
||||
replyList: {
|
||||
required: true,
|
||||
type: Object as PropType<IBlogComment[]>
|
||||
}
|
||||
})
|
||||
|
||||
const blogComment = reactive({
|
||||
isShowEdit: false
|
||||
})
|
||||
|
||||
const replyClick = () => {
|
||||
blogStore.currentBlogComment = blogStore.getEmptyBlogComment()
|
||||
blogComment.isShowEdit = true
|
||||
}
|
||||
|
||||
const cancelClick = () => {
|
||||
blogComment.isShowEdit = false
|
||||
}
|
||||
|
||||
const getOsInfo = (userAgent: string) => {
|
||||
const uaParse = new UAParser(userAgent)
|
||||
return `${uaParse.getOS().name} ${uaParse.getOS().version}`
|
||||
}
|
||||
|
||||
const getBrowser = (userAgent: string) => {
|
||||
const uaParse = new UAParser(userAgent)
|
||||
return `${uaParse.getBrowser().name} ${uaParse.getBrowser().version}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.blog-comment-card {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 60px 1fr;
|
||||
gap: 5px;
|
||||
|
||||
.blog-comment-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
.basic-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.sub-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: small;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
67
src/components/Blog/Content/BlogDetail.vue
Normal file
67
src/components/Blog/Content/BlogDetail.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="blog-detail blog-section card-item" id="blog-section">
|
||||
<div class="blog-title">
|
||||
<span class="title">{{ blogStore.currentBlog.title }}</span>
|
||||
<blog-label :basic-blog="getBasicBlog(blogStore.currentBlog)"/>
|
||||
</div>
|
||||
|
||||
<div class="blog-md card-item">
|
||||
<MdPreview editorId="blog-id"
|
||||
v-model="blogStore.currentBlog.content"
|
||||
:theme="appStore.isDark ? 'dark' : 'light'"/>
|
||||
</div>
|
||||
|
||||
<div class="blog-copyright">
|
||||
<span>
|
||||
<strong>本文作者: </strong> Cxx
|
||||
</span>
|
||||
<span>
|
||||
<strong>本文链接: </strong>
|
||||
<a href="https://www.microsoft.com/">{{ currentUrl.href }}</a>
|
||||
</span>
|
||||
<span>
|
||||
<strong>版权声明: </strong>
|
||||
本博客所有文章除特别声明外,均采用
|
||||
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">©BY-NC-SA</a>
|
||||
许可协议。转载请注明出处!
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="blog-ending">
|
||||
<span>-------------本文结束♥感谢您的阅读♥给个五星好评吧~~-------------</span>
|
||||
</div>
|
||||
|
||||
<blog-comment-edit :parent-id="0"/>
|
||||
|
||||
<blog-comment />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BlogLabel from '@/components/Content/BlogLabel.vue'
|
||||
import BlogCommentEdit from '@/components/Comment/BlogCommentEdit.vue'
|
||||
import BlogComment from '@/components/Comment/BlogComment.vue'
|
||||
import { MdPreview } from 'md-editor-v3'
|
||||
import 'md-editor-v3/lib/style.css'
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { getBasicBlog } from '@/utils/blog/blogUtil.ts'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
const appStore = useAppStore()
|
||||
const route = useRoute()
|
||||
|
||||
const currentUrl = new URL(window.location.href)
|
||||
|
||||
onMounted(async () => {
|
||||
const blogId = route.params.id as number
|
||||
blogStore.currentBlogId = blogId
|
||||
await blogStore.queryBlogById(blogId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/blog/blog.detail.module";
|
||||
</style>
|
||||
92
src/components/Blog/Content/BlogLabel.vue
Normal file
92
src/components/Blog/Content/BlogLabel.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="label-list">
|
||||
<div class="container">
|
||||
<div class="top-value item" v-if="props.basicBlog?.topValue > 1">
|
||||
<i class="fa-solid fa-thumbtack"></i>
|
||||
</div>
|
||||
|
||||
<div v-if="props.basicBlog?.topValue > 1" class="item">|</div>
|
||||
|
||||
<div class="great item" v-if="props.basicBlog?.isGreat">
|
||||
<i class="fa-regular fa-newspaper"></i>
|
||||
</div>
|
||||
|
||||
<div v-if="props.basicBlog?.isGreat" class="item">|</div>
|
||||
|
||||
<div class="item">
|
||||
<i class="fa-regular fa-calendar"></i>
|
||||
<span class="date">{{ formatDate(props.basicBlog.createTime) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="item">|</div>
|
||||
|
||||
<div class="item">
|
||||
<i class="fa-regular fa-calendar-check"></i>
|
||||
<span class="date">{{ formatDate(props.basicBlog.createTime) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="item">|</div>
|
||||
|
||||
<div class="item">
|
||||
<i class="fa-regular fa-folder"></i>
|
||||
<span class="category" @click="viewBlogCategoryClick">
|
||||
{{ props.basicBlog.category }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="item">|</div>
|
||||
|
||||
<div class="item">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
<span>{{ props.basicBlog.visitCount }} 次</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="item">
|
||||
<i class="fa-regular fa-file-word"></i>
|
||||
<span>{{ props.basicBlog.wordCount }} 字</span>
|
||||
</div>
|
||||
|
||||
<div class="item">|</div>
|
||||
|
||||
<div class="item">
|
||||
<i class="fa-regular fa-clock"></i>
|
||||
<span>{{ props.basicBlog.readDuration.toFixed(0) }} 分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, PropType } from 'vue'
|
||||
import { IBasicBlog } from '@/types/blog.ts'
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
|
||||
const blogStore = useBlogStore()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
basicBlog: {
|
||||
required: true,
|
||||
type: Object as PropType<IBasicBlog>
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击查看博客分类按钮
|
||||
*/
|
||||
const viewBlogCategoryClick = async () => {
|
||||
const category = props.basicBlog?.category as string
|
||||
await blogStore.queryBlogByCondition({
|
||||
category
|
||||
})
|
||||
await router.push({ name: 'CategoriesBlog', params: { name: category } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/blog/blog.label.module";
|
||||
</style>
|
||||
66
src/components/Blog/Content/BlogOverview.vue
Normal file
66
src/components/Blog/Content/BlogOverview.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="blog-content-list blog-section card-item">
|
||||
<div v-if="blogStore.blogList.length === 0"
|
||||
class="blog-content blog-content-empty" >
|
||||
</div>
|
||||
|
||||
<div class="blog-content blog-content-summary"
|
||||
v-for="(item, index) in blogStore.blogList" :key="index">
|
||||
<span class="title">{{ item.title }}</span>
|
||||
|
||||
<blog-label :basic-blog="getBasicBlog(item)"/>
|
||||
|
||||
<p class="content">
|
||||
{{ item.summary }}
|
||||
</p>
|
||||
|
||||
<el-button type="primary" @click="readBlogClick(index)">阅读全文 »</el-button>
|
||||
<hr/>
|
||||
</div>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="blogStore.pageInfo.currentPage"
|
||||
:page-size="blogStore.pageInfo.pageSize"
|
||||
:total="blogStore.pageInfo.totalSize"
|
||||
layout="total, prev, pager, next"
|
||||
@current-change="changePageClick"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BlogLabel from '@/components/Content/BlogLabel.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { onMounted } from 'vue'
|
||||
import { useBlogStore } from '@/store/blog.ts'
|
||||
import { getBasicBlog } from '@/utils/blog/blogUtil.ts'
|
||||
|
||||
const router = useRouter()
|
||||
const blogStore = useBlogStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await blogStore.queryBlog()
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击阅读博客按钮
|
||||
* @param index 索引
|
||||
*/
|
||||
const readBlogClick = async (index: number) => {
|
||||
const blogId = blogStore.blogList[index].id
|
||||
await router.push({ name: 'DetailId', params: { id: blogId } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击切换博客页数按钮
|
||||
* @param page 页码
|
||||
*/
|
||||
const changePageClick = async (page: number) => {
|
||||
blogStore.pageInfo.currentPage = page
|
||||
await blogStore.queryBlog()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/styles/blog/blog.overview.module";
|
||||
</style>
|
||||
@@ -16,5 +16,5 @@ const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/styles/app.loading.module.scss";
|
||||
@use "@/styles/app.loading.module";
|
||||
</style>
|
||||
@@ -50,7 +50,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import WeatherIcon from '@/components/Mobile/WeatherIcon.vue'
|
||||
import WeatherIcon from '@/components/Common/WeatherIcon.vue'
|
||||
import { onMounted, onUnmounted, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { Solar } from 'lunar-javascript'
|
||||
@@ -10,12 +10,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FoodDailyItem from '@/components/Home/Food/FoodDailyItem.vue'
|
||||
import FoodDailyItem from '@/components/Food/FoodDailyItem.vue'
|
||||
import { reactive, watch, ref, onMounted } from 'vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
|
||||
import { useAppStore } from '@/store/app'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
|
||||
const calendarRef = ref()
|
||||
const foodStore = useFoodStore()
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { defineProps, onMounted, PropType, reactive } from 'vue'
|
||||
import { IFoodRecipe, IFoodRecord } from '@/types/food.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -110,11 +110,7 @@ const editFoodRecipeClick = async () => {
|
||||
}
|
||||
|
||||
const viewFoodRecipeClick = async () => {
|
||||
if (isMobile()) {
|
||||
await router.push({ name: 'MobileHomeFoodDetailId', params: { id: props.foodItem?.id } })
|
||||
} else {
|
||||
await router.push({ name: 'HomeLifeFoodId', params: { id: props.foodItem?.id } })
|
||||
}
|
||||
await router.push({ name: 'FoodDetailId', params: { id: props.foodItem?.id } })
|
||||
}
|
||||
|
||||
const deleteFoodRecipeClick = () => {
|
||||
@@ -130,5 +126,5 @@ const changeFoodEvent = (current: number) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/styles/home/life.record.module.scss";
|
||||
@use "@/styles/home/life.record.module";
|
||||
</style>
|
||||
@@ -22,8 +22,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DailySummary from '@/components/Home/DailySummary.vue'
|
||||
import DailyCard from '@/components/Home/DailyCard.vue'
|
||||
import DailySummary from '@/components/DailySummary.vue'
|
||||
import DailyCard from '@/components/DailyCard.vue'
|
||||
import { defineProps, onMounted, reactive, watch } from 'vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -89,7 +89,7 @@ const selectFoodRecordClick = async (foodRecord: IFoodRecord) => {
|
||||
if (isMobile()) {
|
||||
appStore.isShowMobileBack = true
|
||||
foodStore.currentFoodRecord = foodRecord
|
||||
await router.push({ name: 'MobileHomeFoodRecordForm' })
|
||||
await router.push({ name: 'FoodRecordForm' })
|
||||
} else {
|
||||
foodStore.foodRecordDialog = {
|
||||
title: '编辑记录',
|
||||
@@ -14,7 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FoodDailyItem from '@/components/Home/Food/FoodDailyItem.vue'
|
||||
import FoodDailyItem from '@/components/Food/FoodDailyItem.vue'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
|
||||
@@ -54,12 +54,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FoodMaterialForm from '@/components/Home/Food/Form/FoodMaterialForm.vue'
|
||||
import FoodStepForm from '@/components/Home/Food/Form/FoodStepForm.vue'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import FoodMaterialForm from '@/components/Food/Form/FoodMaterialForm.vue'
|
||||
import FoodStepForm from '@/components/Food/Form/FoodStepForm.vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { FormInstance, FormRules } from 'element-plus'
|
||||
import { computed, nextTick, ref, reactive } from 'vue'
|
||||
import { foodRules } from '@/utils/element/elRules'
|
||||
import { foodRules } from '@/utils/element/elRules.ts'
|
||||
|
||||
const rules = reactive<FormRules>(foodRules)
|
||||
const formRef = ref<FormInstance>()
|
||||
@@ -19,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ICategory } from '@/types'
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import FoodCard from '@/components/Home/Food/FoodCard.vue'
|
||||
import FoodCard from '@/components/Food/FoodCard.vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { isLargeScreen } from '@/utils'
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StatsCard from '@/components/Home/StatsCard.vue'
|
||||
import StatsCard from '@/components/StatsCard.vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
|
||||
const foodStore = useFoodStore()
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { nextTick, reactive, ref } from 'vue'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { foodMaterialRules } from '@/utils/element/elRules'
|
||||
import { foodMaterialRules } from '@/utils/element/elRules.ts'
|
||||
const rules = reactive<FormRules>(foodMaterialRules)
|
||||
|
||||
const formRef = ref()
|
||||
@@ -21,8 +21,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { IFoodMaterial } from '@/types/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { IFoodMaterial } from '@/types/food.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
|
||||
@@ -56,10 +56,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UploadImage } from 'vue3-common'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { computed, ref, reactive, nextTick } from 'vue'
|
||||
import { FormInstance, FormRules } from 'element-plus'
|
||||
import { foodRecordRules } from '@/utils/element/elRules'
|
||||
import { foodRecordRules } from '@/utils/element/elRules.ts'
|
||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UploadImage } from 'vue3-common'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { foodStepRules } from '@/utils/element/elRules'
|
||||
import { foodStepRules } from '@/utils/element/elRules.ts'
|
||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
||||
|
||||
const rules = reactive<FormRules>(foodStepRules)
|
||||
@@ -25,8 +25,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { IFoodStep } from '@/types/food'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { IFoodStep } from '@/types/food.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { serviceFileRootPath } from '@/utils'
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<div class="budget-chart">
|
||||
<div class="budget-rank-chart card-item" ref="budgetRankChartRef"/>
|
||||
<div class="budget-project-chart card-item" ref="budgetProjectChartRef"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { useBudgetStore } from '@/store/budget'
|
||||
import { barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
||||
|
||||
const budgetStore = useBudgetStore()
|
||||
|
||||
const budgetRankChartRef = ref<HTMLElement>()
|
||||
const budgetProjectChartRef = ref<HTMLElement>()
|
||||
|
||||
let budgetRankChart: echarts.ECharts
|
||||
let budgetProjectChart: echarts.ECharts
|
||||
|
||||
watch(() => budgetStore.isRefresh, () => {
|
||||
updateChart()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
budgetRankChart = echarts.init(budgetRankChartRef.value as HTMLElement)
|
||||
budgetProjectChart = echarts.init(budgetProjectChartRef.value as HTMLElement)
|
||||
})
|
||||
|
||||
const updateChart = () => {
|
||||
// 预算排行统计图
|
||||
const rankNameList = budgetStore.budgetRankStatsList.map((item) => item.name)
|
||||
const rankValueList = budgetStore.budgetRankStatsList.map((item) => item.value)
|
||||
budgetRankChart.setOption(barChartOptions('📊预算排行', '项目', rankNameList, '金额', rankValueList))
|
||||
|
||||
// 预算项目统计图
|
||||
budgetProjectChart.setOption(pieChartOptions('📊预算项目', '项目', '', budgetStore.budgetProjectStatsList))
|
||||
}
|
||||
</script>
|
||||
@@ -1,114 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-model="budgetStore.budgetDialog.visible"
|
||||
:title="budgetStore.budgetDialog.title"
|
||||
center @open="openDialogEvent"
|
||||
width="50%" class="budget-dialog">
|
||||
<el-form ref="formRef" :model="budgetStore.budgetDialog.data"
|
||||
:rules="rules" label-width="100px">
|
||||
<el-form-item label="预算类别" prop="category">
|
||||
<el-select
|
||||
v-model="budgetStore.budgetDialog.data.category"
|
||||
allow-create filterable placeholder="请输入或选择预算类别">
|
||||
<el-option
|
||||
v-for="(item, index) in budgetStore.budgetCategoryList"
|
||||
:key="index" :label="item" :value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="预算项目" prop="project">
|
||||
<el-input v-model="budgetStore.budgetDialog.data.project" autocomplete="off"
|
||||
placeholder="请输入预算项目" clearable
|
||||
show-word-limit maxlength="20">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="预算内容" prop="content">
|
||||
<el-input v-model="budgetStore.budgetDialog.data.content" autocomplete="off"
|
||||
placeholder="请输入预算内容" clearable
|
||||
show-word-limit maxlength="20">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="供应商" prop="vendor">
|
||||
<el-input v-model="budgetStore.budgetDialog.data.vendor" autocomplete="off"
|
||||
placeholder="请输入预算供应商" clearable
|
||||
show-word-limit maxlength="20">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="预算支出" prop="budget">
|
||||
<el-input
|
||||
v-model="budgetStore.budgetDialog.data.budget"
|
||||
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="actual">
|
||||
<el-input
|
||||
v-model="budgetStore.budgetDialog.data.actual"
|
||||
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="支付时间">
|
||||
<el-date-picker
|
||||
v-model="budgetStore.budgetDialog.data.payTime"
|
||||
type="datetime" placeholder="请选择支付时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remake">
|
||||
<el-input v-model="budgetStore.budgetDialog.data.remake" autocomplete="off"
|
||||
placeholder="请输入备注" clearable
|
||||
show-word-limit maxlength="20">
|
||||
</el-input>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useBudgetStore } from '@/store/budget'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { budgetRules } from '@/utils/element/elRules'
|
||||
import { nextTick, reactive, ref } from 'vue'
|
||||
|
||||
const formRef = ref()
|
||||
const rules = reactive<FormRules>(budgetRules)
|
||||
const budgetStore = useBudgetStore()
|
||||
|
||||
/**
|
||||
* 打开对话框事件
|
||||
*/
|
||||
const openDialogEvent = async () => {
|
||||
await nextTick(() => {
|
||||
formRef.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击确认按钮
|
||||
*/
|
||||
const confirmClick = () => {
|
||||
formRef.value.validate().then(async () => {
|
||||
await budgetStore.handleBlogApi(budgetStore.budgetDialog.data.id)
|
||||
budgetStore.queryInfo.category = budgetStore.budgetDialog.data.category
|
||||
})
|
||||
}
|
||||
|
||||
const cancelClick = () => {
|
||||
budgetStore.budgetDialog.visible = false
|
||||
}
|
||||
</script>
|
||||
@@ -1,54 +0,0 @@
|
||||
<template>
|
||||
<div class="budget-query common-query">
|
||||
<el-switch
|
||||
v-model="budgetStore.showDataType"
|
||||
inline-prompt
|
||||
style="--el-switch-on-color: #CA6C65; --el-switch-off-color: #009FA8"
|
||||
active-value="CHART" inactive-value="DATA"
|
||||
active-text="图表统计" inactive-text="数据显示"
|
||||
@change="changeShowDataTypeEvent"
|
||||
/>
|
||||
|
||||
<el-select
|
||||
v-model="budgetStore.queryInfo.category"
|
||||
placeholder="请选择类别" style="width: 120px"
|
||||
@focus="focusCategoryEvent" @change="changeCategoryEvent">
|
||||
<el-option
|
||||
v-for="(item, index) in budgetStore.budgetCategoryList"
|
||||
:key="index" :label="item" :value="item"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-button v-if="budgetStore.showDataType === 'DATA'"
|
||||
type="primary" @click="addBudgeClick">新增预算
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useBudgetStore } from '@/store/budget'
|
||||
|
||||
const budgetStore = useBudgetStore()
|
||||
|
||||
const focusCategoryEvent = async () => {
|
||||
await budgetStore.queryBudgetCategory()
|
||||
}
|
||||
|
||||
const changeCategoryEvent = async () => {
|
||||
await budgetStore.refreshInfo()
|
||||
}
|
||||
|
||||
const addBudgeClick = () => {
|
||||
budgetStore.budgetApiType = 'ADD'
|
||||
budgetStore.budgetDialog = {
|
||||
title: '新增预算',
|
||||
visible: true,
|
||||
data: budgetStore.getEmptyBudget()
|
||||
}
|
||||
budgetStore.budgetDialog.data.category = budgetStore.queryInfo.category
|
||||
}
|
||||
|
||||
const changeShowDataTypeEvent = async () => {
|
||||
await budgetStore.refreshInfo()
|
||||
}
|
||||
</script>
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<el-table :data="budgetStore.budgetList"
|
||||
border stripe show-summary
|
||||
:span-method="spanMethod" :summary-method="summaryMethod"
|
||||
:header-cell-style="tableHeaderStyle()"
|
||||
:row-style="tableRowStyle()" :cell-style="tableCellStyle()"
|
||||
class="budget-table card-item">
|
||||
<el-table-column type="index" align="center" width="80"/>
|
||||
<el-table-column prop="project" label="项目" align="center" min-width="5%"/>
|
||||
<el-table-column prop="content" label="内容" align="center" min-width="10%"/>
|
||||
<el-table-column prop="vendor" label="供应商" align="center" min-width="10%"/>
|
||||
<el-table-column prop="budget" label="预算支出(元)" align="center" min-width="10%">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.budget) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="actual" label="实际支出(元)" align="center" min-width="10%">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.actual) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="save" label="节省预算(元)" align="center" min-width="10%">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.budget - scope.row.actual) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="percent" label="支出占比" align="center" min-width="10%">
|
||||
<template #default="scope">
|
||||
{{ `${((scope.row.actual / totalActual) * 100).toFixed(2)}%` }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remake" label="备注" align="center" min-width="10%"/>
|
||||
<el-table-column fixed="right" label="操作" align="center" width="250">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" @click="editBudget(scope.row)">编辑</el-button>
|
||||
<el-button type="primary" @click="editBudget(scope.row)">账单</el-button>
|
||||
<el-button type="danger" @click="deleteBudget(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useBudgetStore } from '@/store/budget'
|
||||
import { IBudgetInfo } from '@/types/budget.ts'
|
||||
import { computed } from 'vue'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||
import { getRowspan, getSummaryData } from 'vue3-common/utils/elUtil'
|
||||
import { commonElMessageBox, tableHeaderStyle, tableCellStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
|
||||
|
||||
const budgetStore = useBudgetStore()
|
||||
|
||||
const spanIndex = 1
|
||||
const summaryColumnList = ['budget', 'actual', 'save']
|
||||
|
||||
const totalActual = computed(() => {
|
||||
return budgetStore.budgetList.reduce((sum, item: IBudgetInfo) => {
|
||||
return sum + Number(item.actual || 0)
|
||||
}, 0)
|
||||
})
|
||||
|
||||
const spanMethod = (data: { row: any; column: any; rowIndex: number; columnIndex: number }) => {
|
||||
// 需要合并的列索引
|
||||
if (data.columnIndex === spanIndex) {
|
||||
return getRowspan(budgetStore.budgetList, data.column.property, data.rowIndex, data.row)
|
||||
}
|
||||
}
|
||||
|
||||
const summaryMethod = (data: { columns: any[]; data: any[] }) => {
|
||||
const sums = getSummaryData(data.columns, data.data, summaryColumnList)
|
||||
// 格式化输出
|
||||
sums[6] = formatAmount(parseFloat(sums[4] as string) - parseFloat(sums[5] as string))
|
||||
sums[4] = formatAmount(parseFloat(sums[4] as string))
|
||||
sums[5] = formatAmount(parseFloat(sums[5] as string))
|
||||
sums[7] = '100%'
|
||||
return sums
|
||||
}
|
||||
|
||||
const editBudget = (budget: IBudgetInfo) => {
|
||||
budgetStore.budgetApiType = 'UPDATE'
|
||||
budgetStore.budgetDialog = {
|
||||
title: '编辑预算',
|
||||
visible: true,
|
||||
data: deepCopyObject(budget)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteBudget = (budget: IBudgetInfo) => {
|
||||
budgetStore.budgetApiType = 'DELETE'
|
||||
commonElMessageBox('是否删除该预算?').then(() => {
|
||||
budgetStore.handleBlogApi(budget.id as number)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -13,11 +13,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import JournalCard from '@/components/Home/Journal/JournalCard.vue'
|
||||
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'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { useJournalStore } from '@/store/journal.ts'
|
||||
import { IJournal } from '@/types/journal.ts'
|
||||
|
||||
@@ -115,7 +115,7 @@ const editClick = async () => {
|
||||
|
||||
if (isMobile()) {
|
||||
appStore.isShowMobileBack = true
|
||||
await router.push({ name: 'MobileHomeJournalDetailId', params: { id } })
|
||||
await router.push({ name: 'JournalDetailId', params: { id } })
|
||||
} else {
|
||||
journalStore.journalDialog = {
|
||||
title: '编辑日记',
|
||||
@@ -40,9 +40,9 @@ import { defineProps, PropType } from 'vue'
|
||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { useLedgerStore } from '@/store/ledger'
|
||||
import { useLedgerStore } from '@/store/ledger.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ILedger, ILegerItem } from '@/types/ledger'
|
||||
import { ILedger, ILegerItem } from '@/types/ledger.ts'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
@@ -58,15 +58,7 @@ const editClick = async (leger: ILegerItem) => {
|
||||
ledgerStore.ledgerApiType = 'UPDATE'
|
||||
ledgerStore.currentLedgerRecord = ledgerStore.ledgerRecordList.find((item) => item.id === leger.id)
|
||||
|
||||
if (isMobile()) {
|
||||
await router.push({ name: 'MobileHomeLedgerDetailId', params: { id: leger.id } })
|
||||
} else {
|
||||
ledgerStore.ledgerDialog = {
|
||||
title: '编辑账本内容',
|
||||
visible: true,
|
||||
data: deepCopyObject(ledgerStore.currentLedgerRecord)
|
||||
}
|
||||
}
|
||||
await router.push({ name: 'LedgerDetailId', params: { id: leger.id } })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -45,12 +45,6 @@
|
||||
:loading="authStore.loginLoading"
|
||||
@click.prevent="handleLogin('common')">登录
|
||||
</el-button>
|
||||
<div />
|
||||
<el-button type="primary" style="width: 100%;"
|
||||
:disabled="!authStore.isVerified"
|
||||
:loading="authStore.loginAdminLoading"
|
||||
@click.prevent="handleLogin('admin')">登录管理系统
|
||||
</el-button>
|
||||
|
||||
<div class="login-type">
|
||||
<el-button @click="authStore.changeLoginType('phone')">手机号登录</el-button>
|
||||
@@ -76,15 +70,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LoginAuth from '@/components/Home/Login/LoginAuth.vue'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import LoginAuth from '@/components/Login/LoginAuth.vue'
|
||||
import { useAuthStore } from '@/store/auth.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useUserStore } from '@/store/user.ts'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { loginRules } from '@/utils/element/elRules'
|
||||
import { loginRules } from '@/utils/element/elRules.ts'
|
||||
import { getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
||||
import { ILoginType } from '@/types/auth'
|
||||
import { ILoginType } from '@/types/auth.ts'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
|
||||
const rules = reactive<FormRules>(loginRules)
|
||||
@@ -130,7 +124,7 @@ const handleLogin = async (type: ILoginType) => {
|
||||
}
|
||||
|
||||
if (type === 'common') {
|
||||
await router.push('/mobile-home')
|
||||
await router.push('/home')
|
||||
} else {
|
||||
await router.push('/admin')
|
||||
}
|
||||
@@ -46,11 +46,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { useAuthStore } from '@/store/auth.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { loginRules } from '@/utils/element/elRules'
|
||||
import { loginRules } from '@/utils/element/elRules.ts'
|
||||
|
||||
const rules = reactive<FormRules>(loginRules)
|
||||
const authStore = useAuthStore()
|
||||
@@ -46,11 +46,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { useAuthStore } from '@/store/auth.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { loginRules } from '@/utils/element/elRules'
|
||||
import { loginRules } from '@/utils/element/elRules.ts'
|
||||
|
||||
const rules = reactive<FormRules>(loginRules)
|
||||
const authStore = useAuthStore()
|
||||
@@ -43,7 +43,7 @@ const editClick = async () => {
|
||||
passwordStore.passwordApiType = 'UPDATE'
|
||||
|
||||
passwordStore.currentPassword = props.passwordItem as IPassword
|
||||
await router.push({ name: 'MobileHomePasswordDetailId', params: { id } })
|
||||
await router.push({ name: 'PasswordDetailId', params: { id } })
|
||||
}
|
||||
|
||||
const deleteClick = () => {
|
||||
@@ -5,7 +5,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { Calendar, DateSelectArg, EventClickArg } from '@fullcalendar/core'
|
||||
import { initCommonCalendar, initMobileCalendar } from '@/utils/home/planUtil'
|
||||
import { initCommonCalendar, initMobileCalendar } from '@/utils/home/planUtil.ts'
|
||||
import { usePlanStore } from '@/store/plan.ts'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
|
||||
@@ -83,7 +83,7 @@ const eventClickEvent = (eventClick: EventClickArg) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/styles/home.variable.module.scss";
|
||||
@import "@/styles/home.variable.module";
|
||||
|
||||
$cell-height: 3.8vh;
|
||||
$time-slot-width: 70px;
|
||||
@@ -66,9 +66,9 @@
|
||||
import { defineProps, PropType } from 'vue'
|
||||
import { AlarmClock, Clock } from '@element-plus/icons-vue'
|
||||
import { IPlanInfo } from '@/types/plan.ts'
|
||||
import { getRemainDays, isBeforeToday } from '@/utils/home/planUtil'
|
||||
import { getRemainDays, isBeforeToday } from '@/utils/home/planUtil.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { usePlanStore } from '@/store/plan'
|
||||
import { usePlanStore } from '@/store/plan.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
@@ -103,9 +103,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage, FormInstance, FormRules } from 'element-plus'
|
||||
import { planRules } from '@/utils/element/elRules'
|
||||
import { planRules } from '@/utils/element/elRules.ts'
|
||||
import { computed, nextTick, reactive, ref } from 'vue'
|
||||
import { isBeforeTime } from '@/utils/home/planUtil'
|
||||
import { isBeforeTime } from '@/utils/home/planUtil.ts'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { usePlanStore } from '@/store/plan.ts'
|
||||
import { useUserStore } from '@/store/user.ts'
|
||||
@@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PlanCard from '@/components/Home/Plan/PlanCard.vue'
|
||||
import PlanCard from '@/components/Plan/PlanCard.vue'
|
||||
import { usePlanStore } from '@/store/plan.ts'
|
||||
|
||||
const planStore = usePlanStore()
|
||||
@@ -19,8 +19,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StatsCard from '@/components/Home/StatsCard.vue'
|
||||
import { usePlanStore } from '@/store/plan'
|
||||
import StatsCard from '@/components/StatsCard.vue'
|
||||
import { usePlanStore } from '@/store/plan.ts'
|
||||
|
||||
const planStore = usePlanStore()
|
||||
</script>
|
||||
@@ -91,7 +91,7 @@ const editClick = async () => {
|
||||
productStore.productApiType = 'UPDATE'
|
||||
|
||||
productStore.currentProduct = props.productItem as IProduct
|
||||
await router.push({ name: 'MobileHomeProductDetailId', params: { id } })
|
||||
await router.push({ name: 'ProductDetailId', params: { id } })
|
||||
}
|
||||
|
||||
const deleteClick = () => {
|
||||
@@ -103,5 +103,5 @@ const deleteClick = () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/styles/home/life.record.module.scss";
|
||||
@use "@/styles/home/life.record.module";
|
||||
</style>
|
||||
@@ -1,241 +0,0 @@
|
||||
<template>
|
||||
<div id="slideVerify"
|
||||
class="slide-verify"
|
||||
:style="{width: captchaInfo.sliderWidth + 'px'}"
|
||||
@selectstart.prevent>
|
||||
<div class="slide-verify-refresh-icon el-icon-refresh" />
|
||||
<div class="slide-verify-info"
|
||||
:class="{ fail: fail, show: showInfo }">{{ captchaInfo.infoText }}</div>
|
||||
<div
|
||||
class="slide-verify-slider"
|
||||
:style="{width: captchaInfo.sliderWidth + 'px'}"
|
||||
:class="{
|
||||
'container-active': containerActive,
|
||||
'container-success': containerSuccess,
|
||||
'container-fail': containerFail
|
||||
}"
|
||||
>
|
||||
<div class="slide-verify-slider-mask" :style="{ width: captchaInfo.sliderMaskWidth + 'px' }">
|
||||
<div
|
||||
class="slide-verify-slider-mask-item"
|
||||
:style="{ left: captchaInfo.sliderLeft + 'px' }"
|
||||
@mousedown="sliderDown"
|
||||
>
|
||||
<div class="slide-verify-slider-mask-item-icon el-icon-s-unfold" />
|
||||
</div>
|
||||
</div>
|
||||
<span class="slide-verify-slider-text">{{ captchaInfo.infoText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, reactive } from 'vue'
|
||||
|
||||
const captchaInfo = reactive({
|
||||
sliderWidth: 350,
|
||||
sliderMaskWidth: 0,
|
||||
sliderLeft: 0,
|
||||
sliderX: 0,
|
||||
sliderY: 0,
|
||||
sliderTrail: [],
|
||||
isOutOf: false,
|
||||
isMouseDown: false,
|
||||
infoText: '向右滑动'
|
||||
})
|
||||
|
||||
const containerActive = ref(false)
|
||||
const containerSuccess = ref(false)
|
||||
const containerFail = ref(false)
|
||||
const fail = ref(false)
|
||||
const showInfo = ref(false)
|
||||
|
||||
const initSlider = () => {
|
||||
captchaInfo.sliderLeft = 0
|
||||
captchaInfo.sliderMaskWidth = 0
|
||||
}
|
||||
|
||||
const sliderDown = (event) => {
|
||||
captchaInfo.sliderX = event.clientX
|
||||
captchaInfo.sliderY = event.clientY
|
||||
captchaInfo.isMouseDown = true
|
||||
captchaInfo.isOutOf = false
|
||||
captchaInfo.sliderTrail = []
|
||||
}
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
if (!captchaInfo.isMouseDown || captchaInfo.isOutOf) {
|
||||
return
|
||||
}
|
||||
|
||||
const moveX = e.clientX - captchaInfo.sliderX
|
||||
if (moveX < 0 || moveX + 38 >= captchaInfo.sliderWidth) return
|
||||
|
||||
captchaInfo.sliderLeft = moveX
|
||||
containerActive.value = true
|
||||
captchaInfo.sliderMaskWidth = moveX
|
||||
|
||||
const offsetY = e.clientY - captchaInfo.sliderY
|
||||
if (offsetY > 10) {
|
||||
initSlider()
|
||||
captchaInfo.isOutOf = true
|
||||
captchaInfo.infoText = '已超出边界 请重新移动'
|
||||
}
|
||||
|
||||
if (moveX >= captchaInfo.sliderWidth - 50) {
|
||||
console.log('校验成功')
|
||||
captchaInfo.infoText = '校验成功'
|
||||
}
|
||||
|
||||
// captchaInfo.sliderTrail.push(e.clientY - captchaInfo.sliderY)
|
||||
}
|
||||
|
||||
const onMouseUp = (e) => {
|
||||
console.log('cxx')
|
||||
if (!captchaInfo.isMouseDown) return
|
||||
captchaInfo.isMouseDown = false
|
||||
|
||||
console.log(e.clientX)
|
||||
console.log(captchaInfo.sliderX)
|
||||
if (e.clientX === captchaInfo.sliderX) return
|
||||
containerActive.value = false
|
||||
|
||||
console.log(captchaInfo.sliderTrail)
|
||||
// verify()
|
||||
}
|
||||
|
||||
const verify = () => {
|
||||
// const stddev = Math.sqrt(
|
||||
// captchaInfo.sliderTrail
|
||||
// .map((y) => y - trail.value.reduce((a, b) => a + b) / captchaInfo.sliderTrail.length)
|
||||
// .map((x) => x * x)
|
||||
// .reduce((a, b) => a + b) / captchaInfo.sliderTrail.length
|
||||
// )
|
||||
//
|
||||
// if (stddev < 10) {
|
||||
// handleSuccess()
|
||||
// } else {
|
||||
// handleFail()
|
||||
// }
|
||||
}
|
||||
|
||||
const handleFail = () => {
|
||||
fail.value = true
|
||||
showInfo.value = true
|
||||
captchaInfo.infoText = '验证失败'
|
||||
containerFail.value = true
|
||||
}
|
||||
|
||||
const handleSuccess = () => {
|
||||
showInfo.value = true
|
||||
captchaInfo.infoText = '验证失败'
|
||||
containerSuccess.value = true
|
||||
setTimeout(reset, 1000)
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
containerActive.value = false
|
||||
containerSuccess.value = false
|
||||
containerFail.value = false
|
||||
captchaInfo.sliderLeft = 0
|
||||
captchaInfo.sliderMaskWidth = 0
|
||||
fail.value = false
|
||||
showInfo.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.slide-verify {
|
||||
position: relative;
|
||||
width: 310px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slide-verify-refresh-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: #3fdeae;
|
||||
}
|
||||
|
||||
.slide-verify-slider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 310px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin-top: 15px;
|
||||
background: #f7f9fa;
|
||||
color: #45494c;
|
||||
border: 1px solid #e4e7eb;
|
||||
}
|
||||
|
||||
.slide-verify-slider-mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 40px;
|
||||
background: #d1e9fe;
|
||||
}
|
||||
|
||||
.slide-verify-info {
|
||||
position: absolute;
|
||||
top: 170px;
|
||||
left: 0;
|
||||
height: 30px;
|
||||
width: 350px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
background-color: #52ccba;
|
||||
opacity: 0;
|
||||
}
|
||||
.slide-verify-info.fail {
|
||||
background-color: #f57a7a;
|
||||
}
|
||||
.slide-verify-info.show {
|
||||
animation: hide 1s ease;
|
||||
}
|
||||
@keyframes hide {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 0.9; }
|
||||
}
|
||||
|
||||
.slide-verify-slider-mask-item {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s linear;
|
||||
}
|
||||
|
||||
.container-active .slide-verify-slider-mask-item {
|
||||
height: 38px;
|
||||
top: -1px;
|
||||
border: 1px solid #1991fa;
|
||||
}
|
||||
|
||||
.container-success .slide-verify-slider-mask-item {
|
||||
background-color: #52ccba !important;
|
||||
}
|
||||
|
||||
.container-fail .slide-verify-slider-mask-item {
|
||||
background-color: #f57a7a !important;
|
||||
}
|
||||
</style>
|
||||
@@ -14,7 +14,7 @@
|
||||
<script setup lang="ts">
|
||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||
import { defineProps, PropType } from 'vue'
|
||||
import { ITravelConsume } from '@/types/travel'
|
||||
import { ITravelConsume } from '@/types/travel.ts'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
@@ -50,10 +50,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UploadImage } from 'vue3-common'
|
||||
import { useTravelStore } from '@/store/travel'
|
||||
import { useTravelStore } from '@/store/travel.ts'
|
||||
import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { travelRecordRules } from '@/utils/element/elRules'
|
||||
import { travelRecordRules } from '@/utils/element/elRules.ts'
|
||||
import { MultiInput } from 'vue3-common'
|
||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
@@ -10,12 +10,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TravelDailyItem from '@/components/Home/Travel/TravelDailyItem.vue'
|
||||
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'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
|
||||
const calendarRef = ref()
|
||||
const travelStore = useTravelStore()
|
||||
@@ -63,17 +63,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
|
||||
import { useTravelStore } from '@/store/travel'
|
||||
import { useTravelStore } from '@/store/travel.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { defineProps, PropType, reactive } from 'vue'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
import { ITravelSpot } from '@/types/travel'
|
||||
import { ITravelSpot } from '@/types/travel.ts'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { serviceFileRootPath } from '@/utils'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
import { parsePerson } from '@/utils/home/travelUtil'
|
||||
import { parsePerson } from '@/utils/home/travelUtil.ts'
|
||||
|
||||
const router = useRouter()
|
||||
const travelStore = useTravelStore()
|
||||
@@ -95,25 +93,13 @@ const editClick = async () => {
|
||||
travelStore.travelSpotApiType = 'UPDATE'
|
||||
await travelStore.queryTravelSpot(id as number)
|
||||
|
||||
if (isMobile()) {
|
||||
appStore.isShowMobileBack = true
|
||||
await router.push({ name: 'MobileHomeTravelSpotForm' })
|
||||
} else {
|
||||
travelStore.travelSpotDialog = {
|
||||
title: '编辑旅行',
|
||||
visible: true,
|
||||
data: deepCopyObject(travelStore.currentTravelSpot)
|
||||
}
|
||||
}
|
||||
appStore.isShowMobileBack = true
|
||||
await router.push({ name: 'TravelSpotForm' })
|
||||
}
|
||||
|
||||
const viewClick = async () => {
|
||||
if (isMobile()) {
|
||||
appStore.isShowMobileBack = true
|
||||
await router.push({ name: 'MobileHomeTravelDetailId', params: { id: props.travelItem?.id } })
|
||||
} else {
|
||||
await router.push({ name: 'HomeLifeTravelId', params: { id: props.travelItem?.id } })
|
||||
}
|
||||
appStore.isShowMobileBack = true
|
||||
await router.push({ name: 'TravelDetailId', params: { id: props.travelItem?.id } })
|
||||
}
|
||||
|
||||
const deleteClick = () => {
|
||||
@@ -129,5 +115,5 @@ const changeTravelEvent = (current: number) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/styles/home/life.record.module.scss";
|
||||
@use "@/styles/home/life.record.module";
|
||||
</style>
|
||||
@@ -22,17 +22,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DailySummary from '@/components/Home/DailySummary.vue'
|
||||
import DailyCard from '@/components/Home/DailyCard.vue'
|
||||
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'
|
||||
import { ITravelRecord } from '@/types/travel.ts'
|
||||
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
import { parsePerson } from '@/utils/home/travelUtil'
|
||||
import { parsePerson } from '@/utils/home/travelUtil.ts'
|
||||
|
||||
const travelStore = useTravelStore()
|
||||
const appStore = useAppStore()
|
||||
@@ -83,17 +81,9 @@ const getTravelDailyByDate = (date: string) => {
|
||||
const selectTravelRecordClick = async (travelRecord: ITravelRecord) => {
|
||||
travelStore.travelRecordApiType = 'UPDATE'
|
||||
|
||||
if (isMobile()) {
|
||||
appStore.isShowMobileBack = true
|
||||
travelStore.currentTravelRecord = travelRecord
|
||||
await router.push({ name: 'MobileHomeTravelRecordForm' })
|
||||
} else {
|
||||
travelStore.travelRecordDialog = {
|
||||
title: '编辑记录',
|
||||
visible: true,
|
||||
data: deepCopyObject(travelRecord)
|
||||
}
|
||||
}
|
||||
appStore.isShowMobileBack = true
|
||||
travelStore.currentTravelRecord = travelRecord
|
||||
await router.push({ name: 'TravelRecordForm' })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TravelDailyItem from '@/components/Home/Travel/TravelDailyItem.vue'
|
||||
import TravelDailyItem from '@/components/Travel/TravelDailyItem.vue'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { useTravelStore } from '@/store/travel.ts'
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTravelStore } from '@/store/travel'
|
||||
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'
|
||||
@@ -19,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTravelStore } from '@/store/travel'
|
||||
import { useTravelStore } from '@/store/travel.ts'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ICategory } from '@/types'
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import TravelCard from '@/components/Home/Travel/TravelCard.vue'
|
||||
import TravelCard from '@/components/Travel/TravelCard.vue'
|
||||
import { useTravelStore } from '@/store/travel.ts'
|
||||
import { isLargeScreen } from '@/utils'
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StatsCard from '@/components/Home/StatsCard.vue'
|
||||
import StatsCard from '@/components/StatsCard.vue'
|
||||
import { useTravelStore } from '@/store/travel.ts'
|
||||
|
||||
const travelStore = useTravelStore()
|
||||
@@ -13,11 +13,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import KpiCard from '@/components/Home/kpi/KpiCard.vue'
|
||||
import KpiCard from '@/components/kpi/KpiCard.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'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { useKpiStore } from '@/store/kpi.ts'
|
||||
import { IKpi } from '@/types/kpi.ts'
|
||||
|
||||
@@ -20,7 +20,7 @@ import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { useKpiStore } from '@/store/kpi.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { IKpi } from '@/types/kpi'
|
||||
import { IKpi } from '@/types/kpi.ts'
|
||||
|
||||
const props = defineProps({
|
||||
itemList: {
|
||||
@@ -44,7 +44,7 @@ const editClick = async (kpi: IKpi) => {
|
||||
|
||||
if (isMobile()) {
|
||||
appStore.isShowMobileBack = true
|
||||
await router.push({ name: 'MobileHomeKpiDetailId', params: { id } })
|
||||
await router.push({ name: 'KpiDetailId', params: { id } })
|
||||
} else {
|
||||
kpiStore.kpiDialog = {
|
||||
title: '编辑绩效',
|
||||
Reference in New Issue
Block a user