feat: 增加日历控件

This commit is contained in:
cico 2025-12-19 13:26:04 +08:00
parent 13154d2c87
commit ad7d73208c
8 changed files with 3416 additions and 2 deletions

View File

@ -14,6 +14,7 @@
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src --fix"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@amap/amap-jsapi-loader": "^1.0.1",
"@amap/amap-jsapi-types": "^0.0.8",
"@iconify/json": "^2.2.158",
@ -32,6 +33,7 @@
"echarts-liquidfill": "^3.1.0",
"echarts-stat": "^1.2.0",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^2.11.7",
"gsap": "^3.11.3",
"highlight.js": "^11.5.0",
"html2canvas": "^1.4.1",
@ -44,6 +46,7 @@
"screenfull": "^6.0.1",
"three": "^0.145.0",
"vue": "^3.5.13",
"vue-element-plus-x": "^1.3.7",
"vue-demi": "^0.13.1",
"vue-i18n": "9.2.2",
"vue-router": "4.0.12",

2244
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
// 公共类型
import { PublicConfigClass } from '@/packages/public'
// 公共类型
import { CreateComponentType } from '@/packages/index.d'
// 获取上面的 index 配置内容
import { CustomCalendarConfig } from './index'
// 深拷贝
import cloneDeep from 'lodash/cloneDeep'
export const option = {
// 数据源字段
dataset: [],
// 配置项
options1: '',
options2: ''
// ...
}
// 图表类
export default class Config extends PublicConfigClass implements CreateComponentType {
public key = CustomCalendarConfig.key
public chartConfig = cloneDeep(CustomCalendarConfig)
public option = cloneDeep(option)
}

View File

@ -0,0 +1,34 @@
<template>
<!-- 默认展开 -->
<CollapseItem name="样式" :expanded="true">
<SettingItemBox name="边框">
<SettingItem name="圆角">
<!-- config.ts 里的 option 对应 -->
<!-- n-input-number NaiveUI 的控件 -->
<n-input-number
v-model:value="optionData.options1"
size="small"
:min="0"
placeholder="日历圆角"
></n-input-number>
</SettingItem>
<!-- 颜色粗细等等... -->
</SettingItemBox>
</CollapseItem>
</template>
<script setup lang="ts">
import { PropType, computed } from 'vue'
//
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import { CreateComponentType } from '@/packages/index.d'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const optionData = computed(() => props.chartConfig.option)
</script>

View File

@ -0,0 +1,26 @@
// 公共类型声明
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
// 当前[信息模块]分类声明
import { ChatCategoryEnum,ChatCategoryEnumName } from '../../index.d'
export const CustomCalendarConfig: ConfigType = {
// 唯一key注意文件夹名称需要修改成与当前组件一致
key: 'CustomCalendar',
// 图表组件渲染 Components 格式: V + key
chartKey: 'VCustomCalendar',
// 配置组件渲染 Components 格式: VC + key
conKey: 'VCCustomCalendar',
// 名称
title: '自定义日历',
// 子分类目录
category: ChatCategoryEnum.MORE,
// 子分类目录
categoryName: ChatCategoryEnumName.MORE,
// 包分类
package: PackagesCategoryEnum.INFORMATIONS,
// 图表类型
chartFrame: ChartFrameEnum.COMMON,
// 图片 (注意!图片存放的路径必须在 src/assets/images/chart/包分类名称/*)
// 文件夹名称需要和包分类名称一致: PackagesCategoryEnum.INFORMATIONS
image: 'photo.png'
}

View File

@ -0,0 +1,406 @@
<template>
<div class="go-custom-calendar" :style="sizeStyle">
<el-card shadow="hover" class="item-background">
<div class="custome-canlendar">
<el-config-provider :locale="zhCn">
<el-calendar ref="calendar" v-model="value" :key="updateKey">
<template #header>
<span>{{ currentYear }}{{ ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'][currentMonth - 1] }}</span>
<el-button-group>
<el-button size="small" @click="selectDate('prev-month')">上个月</el-button>
<el-button size="small" @click="selectDate('today')">今天</el-button>
<el-button size="small" @click="selectDate('next-month')">下个月</el-button>
</el-button-group>
</template>
<!-- 自定义日期单元格模板 -->
<template #date-cell="{ data }">
<div class="date-cell" :class="{
'is-today': data.day === new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().split('T')[0], //
'is-selected': data.isSelected, //
'is-other-month': data.type === 'prev-month' || data.type === 'next-month' //
}">
<div class="spandate">{{ data.day.split('-').slice(2).join('-') }}</div> <!-- 显示日期仅日部分 -->
<div style="font-size: 10px">
{{ solarDate2lunar(data.day) }} <!-- 显示农历日期 -->
</div>
</div>
</template>
</el-calendar>
</el-config-provider>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, toRefs, watch, ref, shallowReactive, onMounted } from 'vue'
import type { PropType, Ref } from 'vue'
import { ElCalendar, ElCard, ElButtonGroup, ElButton, ElConfigProvider } from 'element-plus'
import type { CalendarInstance, CalendarDateType } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { useSizeStyle } from '@/views/chart/ContentEdit/hooks/useStyle.hook'
import { useChartDataFetch } from '@/hooks/useChartDataFetch.hook'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { CreateComponentType } from '@/packages/index.d'
// @ts-ignore
import calendarCom from '@/utils/calendar.js'
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const { w, h } = toRefs(props.chartConfig.attr)
const calendar = ref<CalendarInstance>()
//
const value = ref(new Date(Date.now() + 8 * 60 * 60 * 1000))
//
const currentYear = ref(value.value.getFullYear())
const currentMonth = ref(value.value.getMonth() + 1)
//
const calendarStyle = computed(() => ({
width: `${w.value}px`,
height: `${h.value}px`
}))
// 使key
const updateKey = ref(0)
watch([w, h], () => {
updateKey.value++
}, { deep: true })
// value
watch(value, (newVal) => {
currentYear.value = newVal.getFullYear()
currentMonth.value = newVal.getMonth() + 1
}, { deep: true })
// 使useSizeStyle
const sizeStyle = computed(() => {
return useSizeStyle(props.chartConfig.attr)
})
//
const selectDate = async (val: CalendarDateType | 'prev-month' | 'today' | 'next-month') => {
if (!calendar.value) return;
if (val === 'today') {
//
value.value = new Date(Date.now() + 8 * 60 * 60 * 1000);
currentYear.value = value.value.getFullYear();
currentMonth.value = value.value.getMonth() + 1;
} else if (val === 'prev-month') {
//
const newDate = new Date(currentYear.value, currentMonth.value - 2, 1);
value.value = newDate;
currentYear.value = newDate.getFullYear();
currentMonth.value = newDate.getMonth() + 1;
} else if (val === 'next-month') {
//
const newDate = new Date(currentYear.value, currentMonth.value, 1);
value.value = newDate;
currentYear.value = newDate.getFullYear();
currentMonth.value = newDate.getMonth() + 1;
} else {
//
value.value = new Date(val);
currentYear.value = value.value.getFullYear();
currentMonth.value = value.value.getMonth() + 1;
}
// Element Plus 2.xv-model
// selectDate
};
//
const solarDate2lunar = (solarDate: any) => {
var solar = solarDate.split('-');
var lunar = calendarCom.solar2lunar(solar[0], solar[1], solar[2]);
return lunar.IMonthCn + lunar.IDayCn;
};
//
const formatDate = (date: any) => {
// date Date
const dateObj = date instanceof Date ? date : new Date(date);
//
if (isNaN(dateObj.getTime())) {
return '';
}
const year = dateObj.getFullYear();
const month = dateObj.getMonth() + 1;
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
return `${year}${monthNames[month - 1]}`;
};
//
onMounted(() => {
// Element Plus 2.xv-model
});
//
useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
//
})
</script>
<style lang="scss" scoped>
.go-custom-calendar {
width: 100%;
height: 100%;
overflow: hidden;
:deep(.el-card) {
width: 100%;
height: 100%;
border-radius: 8px;
overflow: hidden;
.el-card__header {
padding: 10px 20px;
background-color: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
}
.el-card__body {
padding: 10px;
height: calc(100% - 60px);
}
}
.custome-canlendar {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
:deep(.el-calendar) {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.el-calendar__header {
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
.el-button-group {
.el-button {
background-color: transparent;
border: 1px solid #dcdfe6;
color: #333;
padding: 4px 12px;
margin: 0 -1px 0 0;
font-size: 14px;
&:first-child {
border-radius: 4px 0 0 4px;
}
&:last-child {
border-radius: 0 4px 4px 0;
margin-right: 0;
}
&:hover {
background-color: #f5f7fa;
color: #ff4d4f;
}
}
}
}
.el-calendar__body {
padding: 10px;
flex: 1;
display: flex;
flex-direction: column;
}
.el-calendar-table {
width: 100%;
min-height: 200px;
table-layout: fixed;
border-spacing: 0;
border-collapse: collapse;
tr {
height: 50px;
display: table-row;
}
td {
display: table-cell;
vertical-align: middle;
padding: 0;
border: 1px solid #e8e8e8;
}
.el-calendar-day {
padding: 0;
height: 50px;
.date-cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 50px;
padding: 5px;
box-sizing: border-box;
&:hover {
background-color: #fff1f0;
cursor: pointer;
}
&.is-today {
background-color: #fff1f0;
color: #333;
.spandate {
background-color: #ff4d4f;
color: white;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
> div:last-child {
color: #ff4d4f;
}
}
&.is-other-month {
color: #c0c4cc;
.spandate {
color: #c0c4cc;
}
> div:last-child {
color: #c0c4cc;
}
}
&.is-selected {
background-color: #fff1f0;
color: #333;
.spandate {
color: #ff4d4f;
font-weight: bold;
}
> div:last-child {
color: #333;
}
}
//
&.is-today.is-selected {
background-color: #fff1f0;
.spandate {
background-color: #ff4d4f;
color: white;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
> div:last-child {
color: #ff4d4f;
}
}
}
.spandate {
font-size: 14px;
margin-bottom: 5px;
}
}
}
//
.el-calendar__weekdays {
.el-calendar__weekday:nth-child(1)::after {
content: '日';
visibility: visible;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.el-calendar__weekday:nth-child(2)::after {
content: '一';
visibility: visible;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.el-calendar__weekday:nth-child(3)::after {
content: '二';
visibility: visible;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.el-calendar__weekday:nth-child(4)::after {
content: '三';
visibility: visible;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.el-calendar__weekday:nth-child(5)::after {
content: '四';
visibility: visible;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.el-calendar__weekday:nth-child(6)::after {
content: '五';
visibility: visible;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.el-calendar__weekday:nth-child(7)::after {
content: '六';
visibility: visible;
position: absolute;
left: 0;
right: 0;
text-align: center;
}
.el-calendar__weekday {
visibility: hidden;
position: relative;
}
}
}
}
</style>

View File

@ -3,5 +3,6 @@ import { ImageCarouselConfig } from './ImageCarousel/index'
import { IframeConfig } from './Iframe/index'
import { VideoConfig } from './Video/index'
import { WordCloudConfig } from './WordCloud/index'
import { CustomCalendarConfig } from './CustomCalendar/index'
export default [ImageConfig, ImageCarouselConfig, VideoConfig, IframeConfig, WordCloudConfig]
export default [ImageConfig, ImageCarouselConfig, VideoConfig, IframeConfig, WordCloudConfig, CustomCalendarConfig]

678
src/utils/calendar.js Normal file

File diff suppressed because one or more lines are too long