diff --git a/CHANGELOG.md b/CHANGELOG.md index a977e597cf6ff18be7fad3baf7ec2cf7d8a38d6f..9879dfd67218878b4e2384539a009e58bea05163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ ## [Unreleased] +### Changed + +- 日历事件项支持默认呈现背景色为主题色,未配置数据源背景色时,边框激活色改为黑色 +- 日历统一图例显示样式,月样式调整图例绘制位置及支持图例点击后显示或隐藏相关数据的能力 +- 日历周、天样式调整移入气泡样式,调整气泡只在部件参数showdetail=true并且配置了布局面板时才会显示,适配头部的事件气泡显示逻辑与内容区保持一致 +- 日历周、天样式头部拖拽限制最大高度为320,周样式头部事件容器适配虚拟滚动条 +- 日历天样式支持显示开始及结束时间包含了当前选中时间的事件项,调整事件项高度计算逻辑 +- 日历周、天样式适配单双击逻辑及右键菜单逻辑与其它日历样式保持一致 +- 调整日历样式与主题同步 + +### Fixed + +- 修复日历周样式事件显示不全及头部事件项定位异常 +- 修复日历时间轴样式在父容器存在高度时未出现滚动条 +- 修复时间轴在内置导航中会一直重复触发load方法 + ## [0.7.41-alpha.14] - 2025-07-23 ### Added diff --git a/src/control/calendar/calendar-util.ts b/src/control/calendar/calendar-util.ts index a4b942e10ad66d1cb1deccf4a46b2045357cca56..e237508cb36cfeb9120d7180c134be58799266dc 100644 --- a/src/control/calendar/calendar-util.ts +++ b/src/control/calendar/calendar-util.ts @@ -1,3 +1,4 @@ +import { Namespace } from '@ibiz-template/core'; import dayjs from 'dayjs'; /** @@ -51,3 +52,71 @@ export const isTimeBetween = (_argrs: { dayjs(date).isBefore(endTime, unit)) ); }; + +/** + * 用于处理日历图例 + * + * @export + * @param {Namespace} ns 菜单样式处理命名空间 + */ +export function useCalendarLegend(ns: Namespace): { + getFontColor: () => string; + getBkColor: (_index: number) => string; + getActBdrColors: (_index: number) => string; +} { + /** + * @description 获取css变量值 + * @param {string} varName + * @return {*} + */ + const getVarValue = (varName: string): string => { + const root = document.documentElement; + return getComputedStyle(root).getPropertyValue(varName); + }; + + // 图例背景色 + const bgColors = [ + // 首个颜色用主题色 + getVarValue(`${ns.cssVarName('color-primary')}`), + '#2196F3', + '#4CAF50', + '#3F51B5', + '#FF9800', + '#673AB7', + '#757575', + ]; + + // 图例边框激活色 + const actBdrColors = bgColors.map(() => + getVarValue(`${ns.cssVarName('color-black')}`), + ); + + /** + * @description 获取背景颜色 + * @param {number} _index + * @returns {string} + */ + const getBkColor = (_index: number): string => { + return bgColors[_index] || bgColors[0]; + }; + + /** + * @description 获取激活背景颜色 + * @param {number} _index + * @returns {string} + */ + const getActBdrColors = (_index: number): string => { + return ( + actBdrColors[_index] || getVarValue(`${ns.cssVarName('color-primary')}`) + ); + }; + + /** + * @description 获取字体颜色 + * @returns {string} + */ + const getFontColor = (): string => { + return getVarValue(`${ns.cssVarName('color-primary-text')}`); + }; + return { getFontColor, getBkColor, getActBdrColors }; +} diff --git a/src/control/calendar/calendar.scss b/src/control/calendar/calendar.scss index bfe1544523569f27ee1b6729eec0799e2bc4fd62..752ec81b3946a0ae412981d3a50e8ed0ce4ac277 100644 --- a/src/control/calendar/calendar.scss +++ b/src/control/calendar/calendar.scss @@ -9,6 +9,10 @@ $control-calendar: ( margin: getCssVar(spacing, extra-tight), ); +$control-calendar-item: ( + color: none, +); + @include b(control-calendar) { @include set-component-css-var(control-calendar, $control-calendar); @@ -51,7 +55,7 @@ $control-calendar: ( } .el-calendar__header { - flex-wrap: wrap; + flex-wrap: nowrap; } .el-calendar-table, @@ -110,11 +114,11 @@ $control-calendar: ( border-left: none; &.is-begin-time { - border-left: solid 0.5px getCssVar(control-calendar, border-color); + border-left: solid 0.5px var(#{getCssVarName(control-calendar,item-active-border-color)}, #{getCssVar(control-calendar, border-color)}); } &.is-end-time { - border-right: solid 0.5px getCssVar(control-calendar, border-color); + border-right: solid 0.5px var(#{getCssVarName(control-calendar,item-active-border-color)}, #{getCssVar(control-calendar, border-color)}); } } @@ -146,7 +150,10 @@ $control-calendar: ( @include b(control-calendar-item) { @include set-component-css-var(control-calendar, $control-calendar); + @include set-component-css-var(control-calendar-item, $control-calendar-item); + #{getCssVarName(color, text, 2)}: getCssVar(control-calendar-item, color); + #{getCssVarName(editor, default, text, color)}: getCssVar(control-calendar-item, color); height: 100%; padding: getCssVar(control-calendar, item-padding); margin-bottom: getCssVar(control-calendar, margin); @@ -165,7 +172,7 @@ $control-calendar: ( @include when(active) { background-color: getCssVar(control-calendar, active-bg-color); - border: solid 0.5px getCssVar(control-calendar, border-color); + border: solid 0.5px var(#{getCssVarName(control-calendar,item-active-border-color)}, #{getCssVar(control-calendar, border-color)}); } } @@ -183,6 +190,7 @@ $control-calendar: ( @include b(control-calendar-content-title) { line-height: getCssVar(editor, default, line-height); + white-space: nowrap; } @include b(control-calendar-content-header) { @@ -194,20 +202,50 @@ $control-calendar: ( & > * + * { margin-left: getCssVar(control-calendar, item-padding); } + + .el-button-group { + display: flex; + } } @include b(control-calendar-legend) { display: flex; - justify-content: flex-end; + justify-content: center; width: 100%; - & > * + * { - margin-left: getCssVar(spacing, base-loose); + @include e('item') { + display: flex; + align-items: center; + padding: 0 getCssVar('spacing', 'tight'); + line-height: 1em; + cursor: pointer; + + @include m('tip') { + display: inline-block; + width: 1.5em; + height: 0.9em; + margin-right: getCssVar('spacing', 'extra-tight'); + border-radius: getCssVar(border, radius, extra, small); + } + + @include m('text') { + display: inline-block; + width: auto; + max-width: 100px; + overflow: hidden; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + } } } @include b(control-calendar-timeline-content) { + // 适配父容器有高度时未出滚动条 + height: 100%; + overflow: auto; + .el-timeline { padding-left: 0; } -} +} \ No newline at end of file diff --git a/src/control/calendar/calendar.tsx b/src/control/calendar/calendar.tsx index d1f9046118db2132d6652f58dd9b406dfdc5eeaf..8518a33bbfbb8d737e9446180376cd05e5489f74 100644 --- a/src/control/calendar/calendar.tsx +++ b/src/control/calendar/calendar.tsx @@ -4,6 +4,7 @@ import { IBizCustomRender, useControlController, useNamespace, + useUIStore, } from '@ibiz-template/vue3-util'; import { defineComponent, @@ -33,7 +34,11 @@ import dayjs from 'dayjs'; import { showTitle } from '@ibiz-template/core'; import { MenuItem } from '@imengyu/vue3-context-menu'; import CustomCalendar from './components/custom-calendar'; -import { getWeekRange, isTimeBetween } from './calendar-util'; +import { + getWeekRange, + isTimeBetween, + useCalendarLegend, +} from './calendar-util'; import './calendar.scss'; export const CalendarControl = defineComponent({ @@ -75,10 +80,68 @@ export const CalendarControl = defineComponent({ (...args) => new CalendarController(...args), ); const ns = useNamespace(`control-${c.model.controlType!.toLowerCase()}`); + const { UIStore } = useUIStore(); + + const { getFontColor, getBkColor, getActBdrColors } = useCalendarLegend(ns); + const calendarRef = ref(); const curPopover = ref(); const showDateRange = ref(c.controlParams.showmode === 'daterange'); const showDateList = ref(c.controlParams.showmode === 'expand'); + + // 月样式图例点击切换显示与隐藏 + const monthLegendClick = (_item: IData): void => { + Object.assign(_item, { isShow: !_item.isShow }); + }; + + const legendItems = ref([]); + // 存在图例的日历样式 + const legendType = ['DAY', 'WEEK', 'MONTH']; + // 计算图例样式 + const calcLegend = (): void => { + legendItems.value = c.state.legends.map((_item, index) => { + const tempItem = { + ..._item, + isShow: true, + }; + + // 没有图例的日历样式不附加默认背景颜色 + if ( + !c.model.calendarStyle || + !legendType.includes(c.model.calendarStyle) + ) + return tempItem; + + // 如果数据源配置了背景色,则边框激活色为主题激活色 + if (!_item.bkcolor) { + Object.assign(tempItem, { + bkcolor: getBkColor(index), + actBdrColor: getActBdrColors(index), + }); + } + if (!_item.color) + Object.assign(tempItem, { + color: getFontColor(), + }); + return tempItem; + }); + }; + watch( + () => c.state.legends, + () => { + calcLegend(); + }, + { deep: true }, + ); + + // 适配主题切换 + watch( + () => UIStore.theme, + () => { + calcLegend(); + }, + ); + // 气泡框对应显示日期 const popoverValue = ref(''); @@ -142,6 +205,10 @@ export const CalendarControl = defineComponent({ } return false; } + if (c.model.calendarStyle === 'TIMELINE') { + // 比较两个时间是否相等 + return dayjs(newVal).isSame(dayjs(oldVal), 'millisecond'); + } // 其他情况直接更新 return false; }; @@ -166,9 +233,15 @@ export const CalendarControl = defineComponent({ * @return {*} {IData} */ const calcItemStyle = (data: ICalendarItemData): IData => { + const _legend = legendItems.value.find( + _item => _item.id === data.itemType, + ); return { color: data.color, backgroundColor: data.bkColor, + [`${ns.cssVarBlockName('item-color')}`]: data.color, + [`${ns.cssVarBlockName('item-active-border-color')}`]: + _legend?.actBdrColor, }; }; @@ -181,6 +254,12 @@ export const CalendarControl = defineComponent({ const calcCalendarItems = (date: Date): ICalendarItemData[] => { const weekRange = getWeekRange(date); const calendarItems = c.state.items.filter((item: ICalendarItemData) => { + // 适配图例显隐逻辑 + const _legend = legendItems.value.find( + legend => legend.id === item.itemType, + ); + if (_legend && !_legend.isShow) return false; + if (!showDateRange.value) { return dayjs(date).isSame(item.beginTime, 'day'); } @@ -205,7 +284,18 @@ export const CalendarControl = defineComponent({ return result; }); } - return calendarItems; + return calendarItems.map(_item => { + const targetLegend = legendItems.value.find((legendItem: IData) => + Object.is(legendItem.id, _item.itemType), + ); + if (!targetLegend) return _item; + + // 图例没有配置颜色时,赋默认颜色 + if (!_item.bkColor) + Object.assign(_item, { bkColor: targetLegend.bkcolor }); + if (!_item.color) Object.assign(_item, { color: targetLegend.color }); + return _item; + }); }; // *上下文菜单相关 / @@ -404,10 +494,12 @@ export const CalendarControl = defineComponent({ showDateRange, showDateList, popoverValue, + legendItems, selectDate, calcItemStyle, calcCalendarItems, onNodeContextmenu, + monthLegendClick, }; }, render() { @@ -485,6 +577,7 @@ export const CalendarControl = defineComponent({ }); const itemClass = [ this.ns.b('item'), + this.ns.be('item', 'default'), this.ns.is('active', findIndex !== -1), this.ns.is( 'begin-time', @@ -623,6 +716,43 @@ export const CalendarControl = defineComponent({ header: ({ date }: { date: string }): VNode[] => { return [ {date}, +
+ {this.legendItems && + this.legendItems.length > 1 && + this.legendItems.map((_legend: IData) => { + let label = _legend.name!; + const _model = this.c.model.sysCalendarItems?.find( + _calendarItem => _calendarItem.id === _legend.id, + ); + if (_model?.nameLanguageRes) { + label = ibiz.i18n.t( + _model.nameLanguageRes.lanResTag!, + _model.name, + ); + } + return ( +
this.monthLegendClick(_legend)} + > +
+
+ {label} +
+
+ ); + })} +
,
, -
- {this.c.model.sysCalendarItems && - this.c.model.sysCalendarItems.length > 1 && - this.c.model.sysCalendarItems?.map( - (calendarItem: IData) => { - let label = calendarItem.name!; - if (calendarItem.nameLanguageRes) { - label = ibiz.i18n.t( - calendarItem.nameLanguageRes.lanResTag!, - calendarItem.name, - ); - } - return ( -
-
- {label} -
- ); - }, - )} -
, ]; }, 'date-cell': ({ data }: { data: IData }): VNode => { @@ -774,7 +877,7 @@ export const CalendarControl = defineComponent({ ref='calendarRef' viewType={this.c.model.calendarStyle} events={this.c.state.items} - legends={this.c.state.legends} + legends={this.legendItems} multiple={!this.c.state.singleSelect} selectedData={this.c.state.selectedData} onEventClick={(value: IParams) => { @@ -787,6 +890,11 @@ export const CalendarControl = defineComponent({ const item = data[0]; if (item) this.c.onDbRowClick(item); }} + onEventContextmenu={(value: IParams) => { + const { data, evt } = value; + const item = data[0]; + if (item && evt) this.onNodeContextmenu(item, evt); + }} > {slots} diff --git a/src/control/calendar/components/calendar-daily/calendar-daily.scss b/src/control/calendar/components/calendar-daily/calendar-daily.scss index dd5f6fe07d4886c53d5d58700c9e1c705d766de9..8b13c1879f57a18fe5f61c6bd4a9a2263856052e 100644 --- a/src/control/calendar/components/calendar-daily/calendar-daily.scss +++ b/src/control/calendar/components/calendar-daily/calendar-daily.scss @@ -18,7 +18,7 @@ min-width: 90px; height: 100%; padding-left: 40px; - color: rgb(153, 153, 153); + color: getCssVar(color, text, 3); overflow: hidden; display: flex; align-items: center; @@ -35,6 +35,16 @@ } @include m(work-items-body) { height: auto; + &:has(.#{bem('no-data')}) { + height: 100%; + .#{bem('no-data')} { + min-height: 100%; + min-width: 100%; + .el-empty__description { + margin: 0; + } + } + } } @include m(event-box) { width: 100%; @@ -55,24 +65,15 @@ outline: none; text-align: left; border-radius: 4px; - background: rgb(245, 245, 245); + background: getCssVar(color, tertiary, light, hover); border-radius: 4px; position: relative; cursor: pointer; - &:active { - animation: move 1s linear; + &.is-selected-event { + padding-right: 20px; } } - @include m(morphing-animation) { - height: 100%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: pink; - animation: move 1s linear; - } @include m(event-summary) { width: 100%; height: 100%; @@ -90,7 +91,7 @@ margin-right: 6px; } @include m(check) { - color: var(--ibiz-color-primary); + color: getCssVar(color, primary); position: absolute; right: 6px; top: 50%; @@ -100,7 +101,7 @@ @include e(allday-resize-line) { width: 100%; height: 3px; - background-color: var(--ibiz-color-border); + background-color: getCssVar(color, border); } @include e(resizable-handle) { width: 100%; @@ -129,7 +130,7 @@ width: 90px; min-width: 90px; padding: 27px 0 27px 40px; - border-right: 1px solid var(--ibiz-color-border); + border-right: 1px solid getCssVar(color, border); position: sticky; left: 0; } @@ -149,13 +150,13 @@ } @include m(time-column) { height: 60px; - border-top: 1px dashed var(--ibiz-color-border); + border-top: 1px dashed getCssVar(color, border); &:first-child { border-top: none; } } @include m(time-column-last) { - border-bottom: 1px dashed var(--ibiz-color-border); + border-bottom: 1px dashed getCssVar(color, border); } @include m(event-timed-container) { position: absolute; @@ -180,28 +181,25 @@ padding-left: 4px; } @include m(event-content) { + position: relative; + z-index: 3; width: 100%; height: 100%; - padding: 12px 8px; + min-height: 22px; + padding: 8px; + overflow: hidden; line-height: 100%; - border: none; - outline: none; text-align: left; - overflow: hidden; - position: relative; - z-index: 3; cursor: pointer; - &:active { - animation: pulse 1s linear; - } + border: none; + outline: none; &.is-selected-event { - border: 1px solid var(--ibiz-color-primary); + border: 1px solid getCssVar(color, primary); } } @include m(event-summary) { width: 100%; height: 100%; - line-height: 30px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -216,7 +214,7 @@ top: 38px; left: -3px; height: 2px; - background-color: var(--ibiz-color-primary); + background-color: getCssVar(color, primary); display: flex; align-items: center; } @@ -230,19 +228,25 @@ line-height: 24px; border: none; border-radius: 4px; - background: var(--ibiz-color-primary); - color: #fff; + background: getCssVar(color, primary); + color: getCssVar(color, primary, text); } } // 面板项popper @include e(event-popover) { - padding: 0; + position: relative; width: auto; height: auto; - position: relative; + padding: 0; + &.el-popover.el-popper { - padding: 24px 12px 8px 12px; + padding: 8px; + } + + // 当配置了 showdetail 参数,只有配置了布局面板时才会显示气泡 + &:has(.#{bem('control-calendar-item-default')}) { + display: none !important; } @include m(body) { @@ -252,61 +256,52 @@ } @include m(scroll) { width: auto; - height: auto; min-width: 200px; - min-height: 100px; + height: auto; + max-height: 80%; overflow: auto; + pointer-events: none; + + .#{bem('control-calendar-item')} { + &:last-child { + margin-bottom: 0; + } + @include when(active) { + background-color: transparent; + border-color: transparent; + } + } } @include m(close) { position: absolute; - right: 8px; top: 8px; + right: 8px; + display: none; cursor: pointer; + &:hover { .el-icon { - background: var(--ibiz-color-primary); + background: getCssVar(color, primary); border-radius: 50%; + svg { - fill: #FFF; + fill: getCssVar(color, white); } } } } + .el-icon { - font-size: 20px; - min-height: 24px; - min-width: 24px; display: flex; - justify-content: center; align-items: center; + justify-content: center; + min-width: 24px; + min-height: 24px; + font-size: 20px; + svg { fill: var(--el-text-color-secondary); } } } -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.1); - } - 100% { - transform: scale(1); - } -} - - -@keyframes move { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.03); - } - 100% { - transform: scale(1); - } -} +} \ No newline at end of file diff --git a/src/control/calendar/components/calendar-daily/calendar-daily.tsx b/src/control/calendar/components/calendar-daily/calendar-daily.tsx index 9a6a709ec260aa57243ad7e8ae51e7d29636860a..6494e53bbe0312517ed1eb628be716c96f54aadf 100644 --- a/src/control/calendar/components/calendar-daily/calendar-daily.tsx +++ b/src/control/calendar/components/calendar-daily/calendar-daily.tsx @@ -1,8 +1,9 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { defineComponent, onMounted, onUnmounted, watch } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; import { showTitle } from '@ibiz-template/core'; import { IUIEvent, calendarDailyEmits, calendarDailyProps } from '../interface'; -import { isToday } from '../util'; +import { closeIcon, handlePopClose, isToday } from '../util'; import { useCalendarDaily } from './use-calendar-daily'; import './calendar-daily.scss'; @@ -21,11 +22,13 @@ export const CalendarDaily = defineComponent({ curTimeTimer, calendarDaily, selectedData, - eventClick, + handleEventClick, + handleEventDblClick, handleDrag, handleDragStart, handleCurTime, initDrawData, + eventContextmenu, } = useCalendarDaily(props, emit); watch( @@ -77,6 +80,14 @@ export const CalendarDaily = defineComponent({ slotNme: string, classEName: string, ) => { + let title = `${event.text || ''}`; + // 适配内容区移入不显示时间范围提示 + if (location === 'header' && event.timeRange) + title = `${title} ${event.timeRange || ''}`; + // 根据环境变量判断是否展示title + title = showTitle(title) || ''; + // 如果配置了显示详情,则内容区不用显示快捷提示,提示由气泡展示 + if (props.showDetail) title = ''; return (
eventClick(event, 'head')} + onClick={() => { + handleEventClick(event, 'head'); + }} + onDblclick={() => { + handleEventDblClick(event, 'head'); + }} + onContextmenu={(e: MouseEvent) => eventContextmenu(event, e)} style={eventContentStyle} + title={title} > {slots[slotNme] ? ( slots[slotNme]?.({ data: event }) ) : (
{event.icon && ( { + if (!props.showDetail) { + return content; + } + // 适配弹框内详情不要背景色 + const _tempEvent = { ...event, color: '', bkColor: '' }; + return ( + + {{ + reference: () => content, + default: () => { + return ( +
+
handlePopClose(el)} + v-html={closeIcon} + >
+
+ {slots?.event ? ( + slots.event?.({ data: _tempEvent }) + ) : ( +
{`${ + event.text || '' + } ${event.timeRange || ''}`}
+ )} +
+
+ ); + }, + }} +
+ ); + }; + /** * 绘制头部 */ @@ -143,22 +208,30 @@ export const CalendarDaily = defineComponent({
- {events.value.map((event: IUIEvent, index: number) => { - const eventBoxStyle = {}; - const eventContentStyle = { - color: event.color, - background: event.bkColorFade, - }; - return renderEventItem( - 'header', - eventBoxStyle, - eventContentStyle, - event, - index, - 'head-event', - 'allday-info', - ); - })} + {events.value.length > 0 ? ( + events.value.map((event: IUIEvent, index: number) => { + const eventBoxStyle = {}; + const eventContentStyle = { + background: event.bkColorFade, + }; + const tempContent = renderEventItem( + 'header', + eventBoxStyle, + eventContentStyle, + event, + index, + 'head-event', + 'allday-info', + ); + return renderPopover( + tempContent as unknown as Element, + event, + 'bottom', + ); + }) + ) : ( + + )}
@@ -221,7 +294,6 @@ export const CalendarDaily = defineComponent({ const eventContentStyle = { background: event.bkColorFade, 'border-left': `3px solid ${event.bkColor}`, - color: event.color, }; const tempContent = renderEventItem( 'content', @@ -229,10 +301,13 @@ export const CalendarDaily = defineComponent({ eventContentStyle, event, index, - 'event', + '', 'time-pane', ); - return tempContent; + return renderPopover( + tempContent as unknown as Element, + event, + ); })}
diff --git a/src/control/calendar/components/calendar-daily/use-calendar-daily.ts b/src/control/calendar/components/calendar-daily/use-calendar-daily.ts index ab3d908cac49897399e0bec791ea067649e80f9a..8c960cfe595d30ca11d518f6fdf545572efda46d 100644 --- a/src/control/calendar/components/calendar-daily/use-calendar-daily.ts +++ b/src/control/calendar/components/calendar-daily/use-calendar-daily.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable no-use-before-define */ /* eslint-disable no-shadow */ @@ -11,9 +12,10 @@ import { IUILegend, } from '../interface'; import { + checkDateRangeIncludes, fade, handleBkColor, - handleEVentClick, + handleEmit, handleTimeRange, isToday, } from '../util'; @@ -54,7 +56,9 @@ export const useCalendarDaily = ( const handleDrag = (event: DragEvent) => { const heightChange: number = (event as IParams).clientY - initialMouseY + initialHeight; - resizableHand.value.style.height = `${heightChange}px`; + // 顶部拖动高度时增加最大高度限制 + if (heightChange < 320) + resizableHand.value.style.height = `${heightChange}px`; }; /** @@ -73,23 +77,26 @@ export const useCalendarDaily = ( /** * @description 计算events数据 - * @param {IUIEvent[]} events + * @param {IUIEvent[]} _events * @returns {IEvent[]} */ const computeUIEvents = ( - events: IUIEvent[], + _events: IUIEvent[], curDate: Date, - legends: IUILegend[], - calendarDaily: HTMLElement, + _legends: IUILegend[], + _calendarDaily: HTMLElement, ) => { const tempEvents: IUIEvent[] = []; const tempColors = new Map(); let contentWidth = 0; - if (calendarDaily) { - contentWidth = parseInt(window.getComputedStyle(calendarDaily).width, 10); + if (_calendarDaily) { + contentWidth = parseInt( + window.getComputedStyle(_calendarDaily).width, + 10, + ); } // 排序处理事件显示顺序 - events.sort((a: IUIEvent, b: IUIEvent) => { + _events.sort((a: IUIEvent, b: IUIEvent) => { const beginTimeDifference = new Date(a.beginTime).getTime() - new Date(b.beginTime).getTime(); let type; @@ -101,25 +108,40 @@ export const useCalendarDaily = ( return type; }); // 过滤数组 - const tempArray: IUIEvent[] = events.filter((event: IUIEvent) => { - return ( + const tempArray: IUIEvent[] = []; + _events.forEach((event: IUIEvent) => { + const targetLegend = _legends.find((legendItem: IUILegend) => + Object.is(legendItem.id, event.itemType), + ); + // 图例过滤 + if (!targetLegend || !targetLegend.isShow) return; + if ( event && event.beginTime && - event.endTime && - isToday(curDate, event.beginTime) && - isToday(curDate, event.endTime) - ); + checkDateRangeIncludes(event.beginTime, event.endTime, curDate) + ) { + const item = { ...event, targetLegend } as IUIEvent; + const targetEvent = selectedData.value.find( + (tempItem: IUIEvent) => tempItem.id === event.id, + ); + // 判断事件是否默认选中 + if (targetEvent) { + Object.assign(item, { isSelectedEvent: true }); + } else { + Object.assign(item, { isSelectedEvent: false }); + } + // 如果没有结束时间,默认赋值开始时间 + if (!event.endTime && event.beginTime) { + Object.assign(item, { endTime: event.beginTime }); + } + tempArray.push(item); + } }); // 处理绘制需要的UI参数 tempArray.forEach((event: IUIEvent, index: number) => { + const { targetLegend } = event; const tempItem = {} as IUIEvent; Object.assign(tempItem, event); - const targetLegend = legends.find((legendItem: IUILegend) => - Object.is(legendItem.id, event.itemType), - ); - const targetEvent = selectedData.value.find( - (item: IUIEvent) => item.id === event.id, - ); Object.assign(tempItem, { zIndex: index + 1 }); const length = tempArray.length; const itemWidth = (contentWidth - 90) / length; @@ -133,28 +155,50 @@ export const useCalendarDaily = ( styleLeft = `${index * 100}px`; } Object.assign(tempItem, { width, styleLeft }); - // 计算高度 - if (event.beginTime && event.endTime) { - const height = handleEventHeight( - event.beginTime, - event.endTime, - curDate, - ); - const timeRange = handleTimeRange( - event.beginTime, - event.endTime, - 'HH:mm', - ); - Object.assign(tempItem, { height, timeRange }); - } - if (event.beginTime) { - const { styleTop } = handleTopOrCurVal(new Date(event.beginTime)); - Object.assign(tempItem, { styleTop }); + + // 计算高度定位及显示时间范围 + let height: string | number = 'auto'; + let styleTop = 60; + const timeRange = handleTimeRange( + event.beginTime, + event.endTime, + 'YYYY-MM-DD HH:mm:ss', + ); + if (isToday(event.beginTime, event.endTime)) { + height = getEventHeight(event.beginTime, event.endTime); + const tempVal = handleTopOrCurVal(new Date(event.beginTime)); + styleTop = tempVal.styleTop; + } else if ( + isToday(event.beginTime, curDate) && + !isToday(event.endTime, curDate) + ) { + const endDate = new Date(curDate); + endDate.setHours(23, 59, 59, 0); + height = getEventHeight(event.beginTime, endDate); + const tempVal = handleTopOrCurVal(new Date(event.beginTime)); + styleTop = tempVal.styleTop; + } else if ( + isToday(event.endTime, curDate) && + !isToday(event.beginTime, curDate) + ) { + const beginDate = new Date(curDate); + beginDate.setHours(0, 0, 0, 0); + height = getEventHeight(beginDate, event.endTime); + const tempVal = handleTopOrCurVal(new Date(beginDate)); + styleTop = tempVal.styleTop; + } else { + const date = new Date(curDate); + const beginDate = new Date(date.setHours(0, 0, 0, 0)); + const endDate = new Date(date.setHours(23, 59, 59, 59)); + height = getEventHeight(beginDate, endDate); + const tempVal = handleTopOrCurVal(new Date(beginDate)); + styleTop = tempVal.styleTop; } + Object.assign(tempItem, { height, styleTop, timeRange }); if (!event?.bkColor) { - const bkColor = targetLegend - ? targetLegend.bkcolor - : handleBkColor(tempColors, event.itemType); + const bkColor = + (targetLegend && targetLegend.bkcolor) || + handleBkColor(tempColors, event.itemType); Object.assign(tempItem, { bkColor }); } if (tempItem.bkColor) { @@ -163,6 +207,12 @@ export const useCalendarDaily = ( bkColorFade: tempBkColor, }); } + if (!event?.color) { + const tempColor = targetLegend && targetLegend.color; + Object.assign(tempItem, { + color: tempColor, + }); + } // 计算边框显示 if (tempItem?.height && (tempItem as IParams)?.height > 10) { const border = '1px solid #FFF'; @@ -170,34 +220,22 @@ export const useCalendarDaily = ( } else { Object.assign(tempItem, { border: 'none' }); } - // 判断事件是否默认选中 - if (targetEvent) { - Object.assign(tempItem, { isSelectedEvent: true }); - } else { - Object.assign(tempItem, { isSelectedEvent: false }); - } // 图例过滤 - if ((targetLegend && targetLegend.isShow) || !targetLegend) { - tempEvents.push(tempItem); - } + tempEvents.push(tempItem); }); tempColors.clear(); return tempEvents; }; /** - * @description 处理事件卡片展示高度 + * @description 获取事件卡片展示高度 * @param {string | Date} startTime * @param {string | Date} endTime * @returns {number} */ - const handleEventHeight = ( - startTime: string | Date, - endTime: string | Date, - date: Date, - ) => { + const getEventHeight = (startTime: string | Date, endTime: string | Date) => { let height: string | number = 1; - if (isToday(startTime, date) && isToday(endTime, date)) { + if (isToday(startTime, endTime)) { const difference = new Date(endTime).getTime() - new Date(startTime).getTime(); height = Math.floor(difference / (1000 * 60)); @@ -259,8 +297,31 @@ export const useCalendarDaily = ( * @param {IUIEvent} item * @param {string} location */ - const eventClick = async (item: IUIEvent, location: string) => { - const res = await handleEVentClick( + const handleEventClick = (item: IUIEvent, location: string) => { + const res = handleEmit( + 'eventClick', + item, + location, + props.events, + multiple.value, + selectedData.value, + emit, + ); + const { tempSelectedData, isSelectedEvent } = res as IParams; + if (!isSelectedEvent) { + Object.assign(item, { isSelectedEvent }); + } + selectedData.value = tempSelectedData; + }; + + /** + * @description 事件点击 + * @param {IUIEvent} item + * @param {string} location + */ + const handleEventDblClick = (item: IUIEvent, location: string) => { + const res = handleEmit( + 'eventDblClick', item, location, props.events, @@ -275,6 +336,16 @@ export const useCalendarDaily = ( selectedData.value = tempSelectedData; }; + /** + * @description 事件右键点击 + * @param {IUIEvent} item + * @param {MouseEvent} evt + * @param {string} location + */ + const eventContextmenu = async (item: IUIEvent, evt: MouseEvent) => { + emit('eventContextmenu', { evt, data: [item] }); + }; + return { drawData, curTimeTimer, @@ -287,7 +358,9 @@ export const useCalendarDaily = ( handleCurTime, handleDrag, handleDragStart, - eventClick, + handleEventClick, + handleEventDblClick, + eventContextmenu, initDrawData, }; }; diff --git a/src/control/calendar/components/calendar-week/calendar-week.scss b/src/control/calendar/components/calendar-week/calendar-week.scss index 6dd7c0d9bbee8e03565ebbe45e37420eb8377d31..f761aeeb7368993b9261327224bc1194dcdbaa6b 100644 --- a/src/control/calendar/components/calendar-week/calendar-week.scss +++ b/src/control/calendar/components/calendar-week/calendar-week.scss @@ -10,15 +10,15 @@ z-index: 100; display: flex; flex-direction: column; - width: calc(100% - 7px); + width: 100%; height: 150px; min-height: 150px; @include m(date-cell-first) { width: 90px; max-width: 90px; height: 100%; - border-right: 1px solid var(--ibiz-color-border); - border-bottom: 1px solid var(--ibiz-color-border); + border-right: 1px solid getCssVar(color, border); + border-bottom: 1px solid getCssVar(color, border); } @include m(event-cell-first) { display: flex; @@ -28,12 +28,12 @@ max-width: 90px; height: 100%; padding-left: 40px; - border-right: 1px solid var(--ibiz-color-border); + border-right: 1px solid getCssVar(color, border); } @include m(dates) { display: flex; justify-content: space-between; - width: 100%; + width: calc(100% - 7px); height: 96px; } @include m(date-cell) { @@ -41,8 +41,8 @@ flex: 1; flex-direction: column; align-items: center; - border-right: 1px solid var(--ibiz-color-border); - border-bottom: 1px solid var(--ibiz-color-border); + border-right: 1px solid getCssVar(color, border); + border-bottom: 1px solid getCssVar(color, border); } @include m(date-week) { width: 100%; @@ -62,17 +62,31 @@ border-radius: 50%; &.is-today { - color: #FFF; - background: var(--ibiz-color-primary); + color: getCssVar(color, primary, text); + background: getCssVar(color, primary); } } @include m(events) { display: flex; flex: 1; - justify-content: space-between; width: 100%; height: 30px; overflow: hidden; + position: relative; + } + @include m('event-container') { + width: calc(100% - 97px); + height: 100%; + overflow-y: auto; + overflow-x: hidden; + + &::-webkit-scrollbar { + display: none; + } + } + @include m('container-scroll') { + display: flex; + height: auto; } @include m(event-cell) { position: relative; @@ -80,23 +94,41 @@ left: 0; flex: 1; width: 100%; - height: 100%; - border-right: 1px solid var(--ibiz-color-border); + height: auto; + + &::after { + content: ''; + display: block; + position: absolute; + top: 0; + right: 0; + width: 1px; + height: getCssVar(calendar-week-header-event-scroll,real-height); + border-right: 1px solid getCssVar(color, border); + } } @include m(event-box) { position: absolute; + display: flex; + min-width: 32px; + &:has(.#{bem('calendar-week__header--check')}) { + min-width: 50px; + } } @include m(event-content) { position: relative; z-index: 2; width: 100%; - height: 22px; + height: 100%; + min-height: 22px; padding-left: 4px; cursor: pointer; - background-color: var(--ibiz-color-primary); + background-color: getCssVar(color, primary); border: none; - border-radius: 4px; outline: none; + &.is-selected-event { + padding-right: 20px; + } } @include m(event-summary) { width: 100%; @@ -110,20 +142,20 @@ bottom: -6px; display: flex; align-items: center; - width: 100%; - height: 10PX; + width: calc(100% - 7px); + height: 10px; cursor: row-resize; } @include m(resize-line) { width: 100%; height: 1px; - background-color: var(--ibiz-color-border); + background-color: getCssVar(color, border); } @include m(check) { position: absolute; top: 50%; right: 6px; - color: var(--ibiz-color-primary); + color: getCssVar(color, primary); transform: translate(0, -50%); } } @@ -144,7 +176,7 @@ width: 90px; min-width: 90px; padding: 27px 0 27px 40px; - border-right: 1px solid var(--ibiz-color-border); + border-right: 1px solid getCssVar(color, border); } @include m(time-label) { height: 22px; @@ -161,18 +193,18 @@ position: relative; flex: 1; padding-bottom: 60px; - border-right: 1px solid var(--ibiz-color-border); + border-right: 1px solid getCssVar(color, border); } @include m(time-column) { height: 60px; - border-top: 1px solid var(--ibiz-color-border); + border-top: 1px solid getCssVar(color, border); &:nth-child(1){ border-top: none; } } @include m(time-column-last) { - border-bottom: 1px solid var(--ibiz-color-border); + border-bottom: 1px solid getCssVar(color, border); } @include m(event-timed-container) { position: absolute; @@ -208,12 +240,8 @@ border: none; outline: none; - &:active { - animation: pulse 1s linear; - } - &.is-selected-event { - border: 1px solid var(--ibiz-color-primary); + border: 1px solid getCssVar(color, primary); } } @include m(event-summary) { @@ -238,7 +266,12 @@ padding: 0; &.el-popover.el-popper { - padding: 24px 12px 8px; + padding: 8px; + } + + // 当配置了 showdetail 参数,只有配置了布局面板时才会显示气泡 + &:has(.#{bem('control-calendar-item-default')}) { + display: none !important; } @include m(body) { @@ -248,24 +281,36 @@ } @include m(scroll) { width: auto; - min-width: 450px; + min-width: 200px; height: auto; - min-height: 200px; + max-height: 80%; overflow: auto; + pointer-events: none; + + .#{bem('control-calendar-item')} { + &:last-child { + margin-bottom: 0; + } + @include when(active) { + background-color: transparent; + border-color: transparent; + } + } } @include m(close) { position: absolute; top: 8px; right: 8px; + display: none; cursor: pointer; &:hover { .el-icon { - background: var(--ibiz-color-primary); + background: getCssVar(color, primary); border-radius: 50%; svg { - fill: #FFF; + fill: getCssVar(color, white); } } } @@ -286,31 +331,19 @@ } } -@keyframes pulse { - 0% { - transform: scale(1); - } - - 50% { - transform: scale(1.1); - } - - 100% { - transform: scale(1); - } -} - - -@keyframes move { - 0% { - transform: scale(1); - } - - 50% { - transform: scale(1.03); - } - - 100% { - transform: scale(1); - } +@include b('calendar-week-scroll-bar'){ + position: absolute; + top: getCssVar(calendar-week-header-event-scroll-bar,thumb-top); + right: 0; + z-index: 10; + width: 4px; + height: 100%; + border-radius: getCssVar(border-radius,extra-small); + @include e('thumb'){ + width: 4px; + height: getCssVar(calendar-week-header-event-scroll-bar,thumb-height); + cursor: pointer; + background-color: getCssVar(color, fill, 2); + border-radius: getCssVar(border-radius,extra-small); + } } diff --git a/src/control/calendar/components/calendar-week/calendar-week.tsx b/src/control/calendar/components/calendar-week/calendar-week.tsx index b8bef5899d78795ba870b08632d857843c45d5c4..bb88c3464fed40c45db234441fffa6e43c60c170 100644 --- a/src/control/calendar/components/calendar-week/calendar-week.tsx +++ b/src/control/calendar/components/calendar-week/calendar-week.tsx @@ -1,4 +1,5 @@ -import { defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'; +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { defineComponent, onMounted, onUnmounted, watch } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; import { showTitle } from '@ibiz-template/core'; import { IUIEvent, calendarWeekEmits, calendarWeekProps } from '../interface'; @@ -22,20 +23,25 @@ export const CalendarWeek = defineComponent({ selectedData, legends, rowsHeader, - eventClick, + headerEventRef, + thumbHeight, + scrollTop, + realHeight, + isScroll, + handleEventClick, + handleEventDblClick, handleDrag, handleDragStart, + handleDragEnd, handleCurTime, initDrawData, handleUIEvents, + onScrollbarMouseDown, + onHeaderEventScroll, + initHeaderEventScroll, + eventContextmenu, } = useCalendarWeek(props, emit); - // 当前悬浮项 - const hoverItem = ref(''); - - // 控制项popover显示 - const visible = ref(false); - watch( () => props.selectedData, () => { @@ -65,6 +71,7 @@ export const CalendarWeek = defineComponent({ () => props.events, () => { handleUIEvents(); + initHeaderEventScroll(); }, { immediate: true, deep: true }, ); @@ -84,27 +91,18 @@ export const CalendarWeek = defineComponent({ }); /** - * 显示popover - * - * @param {IUIEvent} event - */ - const onMouseenter = (event: IUIEvent) => { - hoverItem.value = String(event.id); - visible.value = true; - }; - - // 项点击 - const onClickCalendarItem = () => { - visible.value = false; - }; - - /** - * 隐藏popover + * 绘制虚拟滚动条 * */ - const onMouseleavee = () => { - hoverItem.value = ''; - visible.value = false; + const renderScrollbar = () => { + return ( +
+
+
+ ); }; /** @@ -126,36 +124,42 @@ export const CalendarWeek = defineComponent({ slotNme: string, classEName: string, ) => { + let title = `${event.text || ''}`; + // 适配内容区移入不显示时间范围提示 + if (location === 'header' && event.timeRange) + title = `${title} ${event.timeRange || ''}`; + // 根据环境变量判断是否展示title + title = showTitle(title) || ''; + // 如果配置了显示详情,则内容区不用显示快捷提示,提示由气泡展示 + if (props.showDetail) title = ''; + return (
onMouseenter(event)} - onMouseleave={onMouseleavee} - onClick={onClickCalendarItem} >