diff --git a/CHANGELOG.md b/CHANGELOG.md index ef0adb17d8bb10d6ac5d0ef3cacf60aa549d4654..0fc737a82bdf7cb68c9bd6f57d5b8b65717b4f96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ - 修复工具栏无工具栏栏控制台报错 +### Added + +- 新增甘特表格列配置基础组件 +- 甘特部件适配主题包样式及新增甘特表格列自定义配置 + ## [0.7.38-alpha.14] - 2024-10-13 ### Added diff --git a/src/common/gantt-setting/gantt-setting.scss b/src/common/gantt-setting/gantt-setting.scss new file mode 100644 index 0000000000000000000000000000000000000000..5b6a3ab6c98eb5039676674c11eda866f11cc191 --- /dev/null +++ b/src/common/gantt-setting/gantt-setting.scss @@ -0,0 +1,148 @@ +$gantt-setting: ( + width: 100vw, + max-width: 980px, + max-height: 85vh, + header-height: 56px, + list-height: 360px, + disabled-opacity: .5, +); + +@include b('gantt-setting') { + @include set-component-css-var(gantt-setting, $gantt-setting); + + display: flex; + flex-direction: column; + width: getCssVar(gantt-setting, width); + max-width: getCssVar(gantt-setting, width); + max-height: getCssVar(gantt-setting, max-height); + + @include e('header') { + width: 100%; + height: getCssVar(gantt-setting, header-height); + padding: 0 getCssVar('spacing', 'extra-loose'); + font-size: getCssVar('font-size', 'header-5'); + line-height: getCssVar(gantt-setting, header-height); + } + + @include e('content') { + display: flex; + flex: 1; + gap: getCssVar('spacing', 'extra-loose'); + justify-content: space-between; + width: 100%; + padding: getCssVar('spacing', 'tight') getCssVar('spacing', 'extra-loose') getCssVar('spacing', 'base'); + + @include m('optional') { + flex: 1; + } + + @include m('selected') { + flex: 1; + } + } + + @include e('bottom') { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: getCssVar('spacing', 'base') getCssVar('spacing', 'extra-loose'); + + @include m('btn-right') { + display: flex; + align-items: center; + } + } +} + +@include b('gantt-setting-search-list') { + width: 100%; + + @include e('caption') { + margin-bottom: getCssVar('spacing', 'tight'); + } + + @include e('content') { + border: 1px solid getCssVar(color, border); + border-radius: getCssVar('border-radius', 'small'); + } + + @include e('search') { + padding: 0 getCssVar('spacing', 'base-loose'); + margin: getCssVar('spacing', 'tight') 0; + } + + @include e('list') { + @include set-component-css-var(gantt-setting, $gantt-setting); + + height: getCssVar(gantt-setting, list-height); + padding-bottom: getCssVar('spacing', 'tight'); + overflow-y: auto; + } +} + +@include b('gantt-setting-list-item') { + @include set-component-css-var(gantt-setting, $gantt-setting); + + display: flex; + align-items: center; + height: getCssVar('spacing', 'extra-loose'); + padding: 0 getCssVar('spacing', 'base-loose'); + cursor: pointer; + + &:hover { + background-color: getCssVar('color', 'fill', 2); + } + + @include e('caption') { + flex: 1; + } + + @include e('end-icon') { + display: flex; + align-items: center; + justify-content: center; + cursor: pointers; + + svg { + fill: getCssVar(color, primary); + } + + @include m('close') { + width: 100%; + height: 100%; + + svg { + fill: getCssVar(color, text, 3); + + &:hover { + fill: getCssVar(color, primary); + } + } + } + } + + @include when('optional') { + @include e('end-icon') { + cursor: pointers; + + svg { + fill: getCssVar(color, primary); + } + } + } + + @include when('disabled') { + cursor: not-allowed; + background: none; + opacity: getCssVar('gantt-setting', disabled-opacity); + } +} + +@media (width >=992px) { + @include b('gantt-setting') { + @include set-component-css-var(gantt-setting, $gantt-setting); + + max-width: getCssVar(gantt-setting, max-width);; + } +} diff --git a/src/common/gantt-setting/gantt-setting.tsx b/src/common/gantt-setting/gantt-setting.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c9c69d00b0a143787d28880f109a2e65069d81bd --- /dev/null +++ b/src/common/gantt-setting/gantt-setting.tsx @@ -0,0 +1,257 @@ +import { defineComponent, PropType, ref, watch } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IModal, IColumnState, IModalData } from '@ibiz-template/runtime'; +import { clone } from 'ramda'; +import './gantt-setting.scss'; + +const selectedIcon = (): JSX.Element => ( + + + + + +); + +const closeIcon = (): JSX.Element => ( + + + + + +); + +// 列表类型 +type ListType = 'optional' | 'selected'; + +export const IBizGanttSetting = defineComponent({ + name: 'IBizGanttSetting', + props: { + modal: { type: Object as PropType, required: true }, + // 表格列状态数组 + columnStates: { type: Object as PropType, required: true }, + // 必须显示的列 + mustShowColumns: { + type: Array as PropType, + required: true, + default: () => ['sn', 'name'], + }, + }, + emits: [], + setup(props) { + const ns = useNamespace('gantt-setting'); + + // 左侧输入框绑定 + const optionalInput = ref(''); + // 右侧输入框绑定 + const selectedInput = ref(''); + + // 绘制数据 + const states = ref([]); + + // 计算是否为必须显示的值 + const calcMustShowColumn = (item: IColumnState): boolean => { + return props.mustShowColumns.some(item2 => item.key === item2); + }; + + // 初始化数据 + const initData = (): void => { + states.value = clone(props.columnStates); + }; + + watch( + () => props.columnStates, + () => { + initData(); + }, + { immediate: true, deep: true }, + ); + + // 处理右侧关闭 + const onListItemClose = (e: MouseEvent, item: IData): void => { + e.stopPropagation(); + Object.assign(item, { hidden: !item.hidden }); + }; + + // 处理右侧项点击 + const onListItemClick = (item: IColumnState): void => { + Object.assign(item, { hidden: !item.hidden }); + }; + + // 取消 + const onClose = (): void => { + props.modal.dismiss(); + }; + + // 确认 + const onConfirm = (): void => { + const modalData: IModalData = { + ok: true, + data: states.value, + }; + props.modal.dismiss(modalData); + }; + + // 恢复默认 + const onResultDefault = (): void => { + initData(); + }; + + // 绘制左侧搜索 + const renderLeftSearch = (): JSX.Element => { + return ( + + {{ + prefix: () => , + }} + + ); + }; + + // 绘制右侧搜索 + const renderRightSearch = (): JSX.Element => { + return ( + + {{ + prefix: () => , + }} + + ); + }; + + // 绘制列表项 + const renderListItem = ( + item: IColumnState, + type: ListType = 'optional', + ): JSX.Element | null => { + const caption = item.caption || ''; + const isOptional = type === 'optional'; + const isMust = calcMustShowColumn(item); + if (!isOptional && item.hidden && !isMust) { + return null; + } + const isSelectedShow = + isOptional && + states.value.some( + (item2: IData) => item.key === item2.key && !item.hidden, + ); + return ( +
isOptional && !isMust && onListItemClick(item)} + > +
{caption}
+
+ {(isSelectedShow || (isOptional && isMust)) && selectedIcon()} + {!isOptional && !isMust && ( +
onListItemClose(e, item)} + > + {closeIcon()} +
+ )} +
+
+ ); + }; + + // 绘制列表 + const renderSearchList = ( + listData: IColumnState[] = [], + type: ListType = 'optional', + ): JSX.Element => { + const isOptional = type === 'optional'; + const searchVal = isOptional ? optionalInput.value : selectedInput.value; + const values: IColumnState[] = []; + listData.forEach(item => { + if (item.caption?.includes(searchVal)) { + // eslint-disable-next-line no-unused-expressions + isOptional ? values.push(item) : !item.hidden && values.push(item); + } + }); + const caption = isOptional + ? ibiz.i18n.t('component.ganttSetting.optionalAttribute') + : ibiz.i18n.t('component.ganttSetting.selectedAttribute'); + + return ( +
+
+ {`${caption} · ${values.length}`} +
+
+
+ {isOptional ? renderLeftSearch() : renderRightSearch()} +
+
+ {values.map((item: IColumnState) => { + return renderListItem(item, type); + })} +
+
+
+ ); + }; + return { + ns, + optionalInput, + selectedInput, + states, + renderSearchList, + onClose, + onConfirm, + onResultDefault, + }; + }, + + render() { + return ( +
+
+ {ibiz.i18n.t('component.ganttSetting.headerCaption')} +
+
+
+ {this.renderSearchList(this.states, 'optional')} +
+
+ {this.renderSearchList(this.states, 'selected')} +
+
+
+ + {ibiz.i18n.t('component.ganttSetting.resultDefault')} + +
+ + {ibiz.i18n.t('app.cancel')} + + + {ibiz.i18n.t('app.confirm')} + +
+
+
+ ); + }, +}); diff --git a/src/common/index.ts b/src/common/index.ts index c95c3a1211bde74f0c74573c072241ff3bd1c4f5..2c707c30d0e874927533f0faf4bfb09235188d77 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -37,6 +37,7 @@ import { IBizCustomFilterCondition } from './custom-filter-condition/custom-filt import { IBizAnchorContainer } from './anchor-container/anchor-container'; import { IBizButtonList } from './button-list/button-list'; import { IBizControlNavigation } from './control-navigation/control-navigation'; +import { IBizGanttSetting } from './gantt-setting/gantt-setting'; export * from './col/col'; export * from './row/row'; @@ -92,6 +93,7 @@ export const IBizCommonComponents = { v.component(IBizAnchorContainer.name, IBizAnchorContainer); v.component(IBizButtonList.name, IBizButtonList); v.component(IBizControlNavigation.name, IBizControlNavigation); + v.component(IBizGanttSetting.name, IBizGanttSetting); }, }; diff --git a/src/control/gantt/gantt.scss b/src/control/gantt/gantt.scss index 17f87bbbb1a74a1809d643a0df2f1729ba8c380c..2ce1038a79a471699446bf8241987496ef84c550 100644 --- a/src/control/gantt/gantt.scss +++ b/src/control/gantt/gantt.scss @@ -3,6 +3,33 @@ height: 100%; .xg-root { + // 背景 + --gantt-bg: var(--ibiz-color-bg-1); + --gantt-bg-hover: var(--ibiz-color-disabled-border); + --gantt-bg-active: var(--ibiz-color-disabled-border); + --gantt-bg-checkmark: transparent; + + // 阴影 + --gantt-shadow: var(--ibiz-color-shadow); + --gantt-shadow-toolbar-item: var(--ibiz-color-shadow); + + // 边框 + --gantt-border: var(--ibiz-color-border); + --gantt-border-hover: var(--ibiz-color-disabled-border); + --gantt-border-dashed: var(--ibiz-color-disabled-border); + --gantt-border-toolbar-item: var(--ibiz-color-border); + + // 文本 + --gantt-text-0: var(--ibiz-color-text-0); + --gantt-text-3: var(--ibiz-color-text-3); + + // 主要颜色 + --gantt-white: var(--ibiz-color-white); + --gantt-black: var(--ibiz-color-black); + + // 警示色 + --gantt-warning: var(--ibiz-color-warning); + .xg-table-body .xg-table-row { cursor: pointer; @@ -30,6 +57,7 @@ height: 100%; padding: getCssVar(spacing,none) getCssVar(spacing,base-tight); cursor: pointer; + background-color: getCssVar('color', 'primary'); border-radius: getCssVar(border,radius,small); @include m(container-title) { .icon { @@ -64,6 +92,14 @@ } } } + @include e('setting') { + display: flex; + align-items: center; + @include m('icon') { + color: getCssVar('color','primary', 'text'); + cursor: pointer; + } + } .#{bem('tree-grid-ex-field-column','','ellipsis')} { .#{bem('tree-grid-ex-field-column-text-container')} { min-width: 0; diff --git a/src/control/gantt/gantt.tsx b/src/control/gantt/gantt.tsx index f8ab21fec21eda20321e85c6d5818be9379cd572..47a4d89df02295beda07523b462b840765ed451e 100644 --- a/src/control/gantt/gantt.tsx +++ b/src/control/gantt/gantt.tsx @@ -1,7 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-unused-vars */ /* eslint-disable no-restricted-syntax */ -import { useControlController, useNamespace } from '@ibiz-template/vue3-util'; +import { + useControlController, + useNamespace, + useUIStore, +} from '@ibiz-template/vue3-util'; import { computed, defineComponent, @@ -9,6 +13,7 @@ import { resolveComponent, VNode, h, + watch, onMounted, getCurrentInstance, ref, @@ -29,6 +34,8 @@ import { IGanttNodeData, IButtonState, IButtonContainerState, + IModal, + IModalData, } from '@ibiz-template/runtime'; import { MenuItem } from '@imengyu/vue3-context-menu'; import dayjs from 'dayjs'; @@ -36,6 +43,7 @@ import { AllowDropType, NodeDropType, } from 'element-plus/es/components/tree/src/tree.type'; +import { showTitle } from '@ibiz-template/core'; import { findNodeData, formatNodeDropType } from '../tree/el-tree-util'; import './gantt.scss'; @@ -71,26 +79,6 @@ export const GanttControl = defineComponent({ const selection: IGanttNodeData[] = []; - const color = [ - 'blue', - 'light-blue', - 'blue-cyan', - 'pink', - 'purple', - 'violet', - 'indigo', - 'cyan', - 'teal', - 'green', - 'light-green', - 'lime', - 'yellow', - 'amber', - 'orange', - 'grey', - 'sky-blue', - ]; - // 动态加载gantt组件 const app = getCurrentInstance()?.appContext.app; onMounted(() => { @@ -118,28 +106,32 @@ export const GanttControl = defineComponent({ return getComputedStyle(root).getPropertyValue(varName); }; - const ganttStyle = computed(() => { - const style = { + const { UIStore } = useUIStore(); + const ganttStyle = ref({}); + + // 计算甘特样式 + const calcGanttStyle = () => { + return { primaryColor: c.state.ganttStyle?.primaryColor || getVarValue('--ibiz-color-primary'), textColor: c.state.ganttStyle?.textColor || getVarValue('--ibiz-color-primary-text'), + bgColor: getVarValue('--ibiz-color-bg-1'), + weekendColor: getVarValue('--ibiz-color-disabled-border'), + todayColor: getVarValue('--ibiz-color-primary-light-active'), + borderColor: getVarValue('--ibiz-color-disabled-border'), }; - return style; - }); + }; - const sliderColor = computed(() => { - const nodeColor: IData = {}; - c.model.detreeNodes?.forEach((node, index) => { - const colorIndex = index % color.length; - nodeColor[node.id!] = `rgba(${getVarValue( - `--ibiz-${color[colorIndex]}-0`, - )}, 1)`; - }); - return nodeColor; - }); + watch( + () => UIStore.theme, + () => { + ganttStyle.value = calcGanttStyle(); + }, + { immediate: true }, + ); /** * 部件是否在加载状态 @@ -348,6 +340,30 @@ export const GanttControl = defineComponent({ c.modifyNodeTime(nodeData, newValue); }; + /** + * 表格列设置点击 + */ + const onSettingClick = async (): Promise => { + const res: IModalData = await ibiz.overlay.modal( + (modal: IModal): VNode => { + const comp = resolveComponent('IBizGanttSetting') as string; + const options: IData = { + modal, + columnStates: c.state.columnStates, + }; + if (c.state.mustShowColumns) { + options.mustShowColumns = c.state.mustShowColumns; + } + return h(comp, options); + }, + undefined, + { width: 'auto', height: 'auto' }, + ); + if (res.ok && res.data && res.data.length > 0) { + c.setColumnVisible(res.data); + } + }; + /** * 计算上下文菜单组件配置项集合 * @@ -589,6 +605,9 @@ export const GanttControl = defineComponent({ > {{ content: ({ row }: { row: IGanttNodeData }): VNode => { + const { sysCss } = c.getNodeModel(row._nodeId); + const sysCssName = sysCss?.cssName || ''; + return ( { return (
onNodeClick(row, evt)} onDblclick={() => onNodeDbClick(row)} onContextmenu={evt => onNodeContextmenu(row, evt)} @@ -636,6 +652,21 @@ export const GanttControl = defineComponent({ return [..._columns, slider]; }; + /** + * 绘制设置 + */ + const renderSetting = (): JSX.Element => { + return ( +
onSettingClick()}> + +
+ ); + }; + return { c, ns, @@ -652,6 +683,7 @@ export const GanttControl = defineComponent({ onNodeExpand, onNodeCollapse, renderContent, + renderSetting, onSliderMove, renderNoData, allowDrop, @@ -694,6 +726,13 @@ export const GanttControl = defineComponent({ onMoveSlider={this.onSliderMove} primaryColor={this.ganttStyle.primaryColor} headerStyle={{ textColor: this.ganttStyle.textColor }} + borderColor={this.ganttStyle.borderColor} + bodyStyle={{ + todayColor: this.ganttStyle.todayColor, + weekendColor: this.ganttStyle.weekendColor, + bgColor: this.ganttStyle.bgColor, + selectColor: this.ganttStyle.weekendColor, + }} > {{ default: () => { @@ -702,6 +741,9 @@ export const GanttControl = defineComponent({ empty: () => { return this.renderNoData(); }, + setting: () => { + return this.renderSetting(); + }, }} diff --git a/src/locale/en/index.ts b/src/locale/en/index.ts index 97083943aa97692060ecc63256569b0f1691c616..c29da447a9646894b9f55db9b5b536beb05bcbb7 100644 --- a/src/locale/en/index.ts +++ b/src/locale/en/index.ts @@ -536,6 +536,12 @@ export default { gridSetting: { hideControl: 'Column selection', }, + ganttSetting: { + resultDefault: 'Restore defaults', + headerCaption: 'Header display attributes', + optionalAttribute: 'Optional attributes', + selectedAttribute: 'Selected attributes', + }, actionToolbar: { more: 'More', }, diff --git a/src/locale/zh-CN/index.ts b/src/locale/zh-CN/index.ts index 67aab238ccc3a12abccd984d0289d7f019da4e56..c267eec4f63c3ccc0f3f0f00f8514d0d1e12b55f 100644 --- a/src/locale/zh-CN/index.ts +++ b/src/locale/zh-CN/index.ts @@ -498,6 +498,12 @@ export default { gridSetting: { hideControl: '列选择', }, + ganttSetting: { + resultDefault: '恢复默认值', + headerCaption: '表头显示属性', + optionalAttribute: '可选属性', + selectedAttribute: '已选择属性', + }, actionToolbar: { more: '更多', },