diff --git a/dbm-ui/frontend/src/components/editable-table/Column.vue b/dbm-ui/frontend/src/components/editable-table/Column.vue new file mode 100644 index 0000000000..c275ae8eeb --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/Column.vue @@ -0,0 +1,461 @@ + + + + diff --git a/dbm-ui/frontend/src/components/editable-table/Index.vue b/dbm-ui/frontend/src/components/editable-table/Index.vue new file mode 100644 index 0000000000..9418755c18 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/Index.vue @@ -0,0 +1,410 @@ + + + + diff --git a/dbm-ui/frontend/src/components/editable-table/Row.vue b/dbm-ui/frontend/src/components/editable-table/Row.vue new file mode 100644 index 0000000000..6f22c1d74c --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/Row.vue @@ -0,0 +1,63 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/component/RenderColGroup.vue b/dbm-ui/frontend/src/components/editable-table/component/RenderColGroup.vue new file mode 100644 index 0000000000..0bf83fbce9 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/RenderColGroup.vue @@ -0,0 +1,26 @@ + + diff --git a/dbm-ui/frontend/src/components/editable-table/component/render-body/Index.vue b/dbm-ui/frontend/src/components/editable-table/component/render-body/Index.vue new file mode 100644 index 0000000000..7d265f1541 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/render-body/Index.vue @@ -0,0 +1,28 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/component/render-body/render-cell.ts b/dbm-ui/frontend/src/components/editable-table/component/render-body/render-cell.ts new file mode 100644 index 0000000000..099aab1f6c --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/render-body/render-cell.ts @@ -0,0 +1,29 @@ +import { defineComponent, h } from 'vue'; + +import type { IContext as IColumnContext } from '../../Column.vue'; + +export default defineComponent({ + name: 'RenderColumnCell', + props: { + column: { + type: Object as () => IColumnContext, + required: true, + }, + }, + setup(props) { + return () => + h( + 'td', + { + class: 'bk-editable-table-body-column', + }, + h( + 'div', + { + class: 'bk-editable-table-cell', + }, + props.column.slots.default(), + ), + ); + }, +}); diff --git a/dbm-ui/frontend/src/components/editable-table/component/render-header/Index.vue b/dbm-ui/frontend/src/components/editable-table/component/render-header/Index.vue new file mode 100644 index 0000000000..d667c2bee9 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/render-header/Index.vue @@ -0,0 +1,74 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/component/render-header/render-th.ts b/dbm-ui/frontend/src/components/editable-table/component/render-header/render-th.ts new file mode 100644 index 0000000000..73f8fdc10e --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/render-header/render-th.ts @@ -0,0 +1,81 @@ +import { defineComponent, h, resolveDirective, withDirectives } from 'vue'; + +import type { IContext as IColumnContext } from '../../Column.vue'; + +export default defineComponent({ + name: 'RenderColumnHead', + props: { + column: { + type: Object as () => IColumnContext, + required: true, + }, + }, + setup(props) { + return () => { + const childNode = [ + withDirectives( + h( + 'div', + { + class: { + 'bk-editable-table-th-text': true, + 'bk-editable-table-th-text-description': Boolean(props.column.props.description), + }, + }, + props.column.slots.head ? props.column.slots.head() : props.column.props.label || '', + ), + [ + [ + resolveDirective('bk-tooltips'), + { + content: props.column.props.description || '', + disabled: !props.column.props.description, + }, + ], + ], + ), + ]; + + if (!props.column.slots.head && props.column.slots.headPrepend) { + childNode.unshift( + h( + 'div', + { + class: 'bk-editable-table-th-prepend', + }, + props.column.slots.headPrepend(), + ), + ); + } + + if (!props.column.slots.head && props.column.slots.headAppend) { + childNode.push( + h( + 'div', + { + class: 'bk-editable-table-th-append', + }, + props.column.slots.headAppend(), + ), + ); + } + return h( + 'th', + { + class: { + 'bk-editable-table-header-column': true, + 'is-required': props.column.props.required, + }, + 'data-name': props.column.key, + }, + h( + 'div', + { + class: 'bk-editable-table-label-cell', + }, + childNode, + ), + ); + }; + }, +}); diff --git a/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/Index.vue b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/Index.vue new file mode 100644 index 0000000000..16ce4af95f --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/Index.vue @@ -0,0 +1,256 @@ + + + + + + diff --git a/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-box-state.ts b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-box-state.ts new file mode 100644 index 0000000000..5a69f2a790 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-box-state.ts @@ -0,0 +1,79 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at https://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for + * the specific language governing permissions and limitations under the License. + */ + +import { type ComponentInternalInstance, getCurrentInstance, reactive } from 'vue'; + +import type { IContext } from '../index.vue'; + +export default function () { + const { proxy } = getCurrentInstance() as ComponentInternalInstance & { proxy: IContext }; + + const state = reactive({ + contentScrollHeight: 0, + contentScrollWidth: 0, + isRenderVerticalScroll: false, + isRenderHorizontalScrollbar: false, + styles: { + width: '', + height: '', + }, + }); + + const initState = () => { + if (proxy.$refs.scrollBox && proxy.$refs.scrollContent) { + const { scrollHeight, scrollWidth } = proxy.$refs.scrollContent as Element; + state.contentScrollHeight = scrollHeight; + state.contentScrollWidth = scrollWidth; + + const { width: boxWidth, height: boxHeight } = proxy.$refs.scrollBox.getBoundingClientRect(); + // 内容区高度大于容器高度显示垂直滚动条 + state.isRenderVerticalScroll = Math.ceil(state.contentScrollHeight) > Math.ceil(boxHeight); + // 内容区宽度大于容器宽度显示水平滚动条 + state.isRenderHorizontalScrollbar = Math.ceil(state.contentScrollWidth) > Math.ceil(boxWidth); + const styles = { + width: '100%', + height: '100%', + maxHeight: '', + maxWidth: '', + }; + // 计算滚动容器的展示宽高 + const { + height: scrollBoxStyleHeight, + maxHeight: scrollBoxStyleMaxHeight, + width: scrollBoxStyleWidth, + maxWidth: scrollBoxStyleMaxWidth, + } = proxy.$refs.scrollBox.style; + if (state.isRenderVerticalScroll) { + if (scrollBoxStyleHeight) { + styles.height = scrollBoxStyleHeight; + } else if (scrollBoxStyleMaxHeight) { + styles.maxHeight = scrollBoxStyleMaxHeight; + } + } + if (state.isRenderHorizontalScrollbar) { + if (scrollBoxStyleWidth) { + styles.width = scrollBoxStyleWidth; + } else if (scrollBoxStyleMaxWidth) { + styles.maxWidth = scrollBoxStyleMaxWidth; + } + } + + state.styles = Object.freeze(styles); + } + }; + + return { + state, + initState, + }; +} diff --git a/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-content.ts b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-content.ts new file mode 100644 index 0000000000..e896f4731a --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-content.ts @@ -0,0 +1,36 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at https://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for + * the specific language governing permissions and limitations under the License. + */ + +import { ref } from 'vue'; + +export default function () { + const isContentScroll = ref(false); + /** + * @desc 鼠标在内容区状态 + */ + const mouseenter = () => { + isContentScroll.value = true; + }; + /** + * @desc 鼠标离开内容区状态 + */ + const mouseleave = () => { + isContentScroll.value = false; + }; + + return { + isContentScroll, + mouseenter, + mouseleave, + }; +} diff --git a/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-horizotal.ts b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-horizotal.ts new file mode 100644 index 0000000000..c753d046b2 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-horizotal.ts @@ -0,0 +1,36 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at https://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for + * the specific language governing permissions and limitations under the License. + */ + +import { ref } from 'vue'; + +export default function () { + const isHorizontalScroll = ref(false); + /** + * @desc 鼠标在水平滚动条区域 + */ + const mouseenter = () => { + isHorizontalScroll.value = true; + }; + /** + * @desc 鼠标离开水平滚动条区域 + */ + const mouseleave = () => { + isHorizontalScroll.value = false; + }; + + return { + isHorizontalScroll, + mouseenter, + mouseleave, + }; +} diff --git a/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-vertical.ts b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-vertical.ts new file mode 100644 index 0000000000..8c3a59160a --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/component/scroll-faker/hooks/use-vertical.ts @@ -0,0 +1,36 @@ +/* + * TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. + * + * Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at https://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for + * the specific language governing permissions and limitations under the License. + */ + +import { ref } from 'vue'; + +export default function () { + const isVerticalScroll = ref(false); + /** + * @desc 鼠标在垂直滚动条区域 + */ + const mouseenter = () => { + isVerticalScroll.value = true; + }; + /** + * @desc 鼠标离开垂直滚动条区域 + */ + const mouseleave = () => { + isVerticalScroll.value = false; + }; + + return { + isVerticalScroll, + mouseenter, + mouseleave, + }; +} diff --git a/dbm-ui/frontend/src/components/editable-table/constants.ts b/dbm-ui/frontend/src/components/editable-table/constants.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dbm-ui/frontend/src/components/editable-table/edit/DatePicker.vue b/dbm-ui/frontend/src/components/editable-table/edit/DatePicker.vue new file mode 100644 index 0000000000..a7fc6f07ed --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/edit/DatePicker.vue @@ -0,0 +1,37 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/edit/Input.vue b/dbm-ui/frontend/src/components/editable-table/edit/Input.vue new file mode 100644 index 0000000000..a530bb0a3e --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/edit/Input.vue @@ -0,0 +1,41 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/edit/Select.vue b/dbm-ui/frontend/src/components/editable-table/edit/Select.vue new file mode 100644 index 0000000000..d6ee9b6a5e --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/edit/Select.vue @@ -0,0 +1,36 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/edit/TagInput.vue b/dbm-ui/frontend/src/components/editable-table/edit/TagInput.vue new file mode 100644 index 0000000000..3f1d6ee34c --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/edit/TagInput.vue @@ -0,0 +1,44 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/edit/Text.vue b/dbm-ui/frontend/src/components/editable-table/edit/Text.vue new file mode 100644 index 0000000000..31b85e4bfc --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/edit/Text.vue @@ -0,0 +1,141 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/edit/Textarea.vue b/dbm-ui/frontend/src/components/editable-table/edit/Textarea.vue new file mode 100644 index 0000000000..d8aaf10a66 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/edit/Textarea.vue @@ -0,0 +1,46 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/edit/TimePicker.vue b/dbm-ui/frontend/src/components/editable-table/edit/TimePicker.vue new file mode 100644 index 0000000000..472b801390 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/edit/TimePicker.vue @@ -0,0 +1,37 @@ + + + diff --git a/dbm-ui/frontend/src/components/editable-table/hooks/use-column-tips.ts b/dbm-ui/frontend/src/components/editable-table/hooks/use-column-tips.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dbm-ui/frontend/src/components/editable-table/hooks/use-resize.ts b/dbm-ui/frontend/src/components/editable-table/hooks/use-resize.ts new file mode 100644 index 0000000000..f0967d53df --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/hooks/use-resize.ts @@ -0,0 +1,194 @@ +import _ from 'lodash'; +import { onBeforeUnmount, onMounted, type Ref, ref, watch } from 'vue'; + +import type { IContext as IColumnContext } from '../Column.vue'; + +export default function ( + tableRef: Ref, + tableColumnResizeRef: Ref, + columnList: Ref, +) { + let dragable = false; + + const columnSizeConfig = ref< + Record< + string, + { + width: number; + minWidth: number; + maxWidth: number; + renderWidth: number; + } + > + >({}); + + const dragging = ref(false); + const dragState = ref({ + startMouseLeft: 0, + startLeft: 0, + startColumnLeft: 0, + tableLeft: 0, + }); + + watch( + columnList, + () => { + const columnSizeConfigCache = { ...columnSizeConfig.value }; + columnSizeConfig.value = columnList.value.reduce((result, columnConfig) => { + const width = columnConfig.props.width || 0; + const minWidth = + Number(columnConfig.props.minWidth) > 60 ? Number(columnConfig.props.minWidth) : Number.MIN_VALUE; + const maxWidth = + Number(columnConfig.props.maxWidth) > 60 ? Number(columnConfig.props.maxWidth) : Number.MAX_VALUE; + + const renderWidth = Math.min(Math.max(width, minWidth), maxWidth); + + Object.assign(result, { + [columnConfig.key]: columnSizeConfigCache[columnConfig.key] + ? columnSizeConfigCache[columnConfig.key] + : { + width, + minWidth, + maxWidth, + renderWidth, + }, + }); + + return result; + }, {}); + console.log('columnSizeConfig.value = ', columnSizeConfig.value); + }, + { + immediate: true, + }, + ); + + const handleMouseDown = (event: MouseEvent) => { + if (!dragable) { + return; + } + dragging.value = true; + + const tableEl = tableRef.value; + const tableLeft = tableEl!.getBoundingClientRect().left; + const columnEl = event.target as HTMLElement; + const columnRect = columnEl!.getBoundingClientRect(); + + const columnKey = columnEl.dataset.name as string; + + const minLeft = columnRect.left - tableLeft + 30; + + dragState.value = { + startMouseLeft: event.clientX, + startLeft: columnRect.right - tableLeft, + startColumnLeft: columnRect.left - tableLeft, + tableLeft, + }; + const resizeProxy = tableColumnResizeRef.value as HTMLElement; + resizeProxy.style.display = 'block'; + resizeProxy.style.left = `${dragState.value.startLeft}px`; + + document.onselectstart = function () { + return false; + }; + document.ondragstart = function () { + return false; + }; + + const handleMouseMove = (event: MouseEvent) => { + const deltaLeft = event.clientX - dragState.value.startMouseLeft; + const proxyLeft = dragState.value.startLeft + deltaLeft; + resizeProxy.style.display = 'block'; + resizeProxy.style.left = `${Math.max(minLeft, proxyLeft)}px`; + }; + + const handleMouseUp = () => { + if (dragging.value) { + const containerWidth = tableEl!.getBoundingClientRect().width; + const containerScrollWidth = tableEl!.scrollWidth; + const isScrolling = containerScrollWidth > containerWidth; + const { startColumnLeft } = dragState.value; + const finalLeft = Number.parseInt(resizeProxy.style.left, 10); + const latestColumnWidth = Math.ceil(Math.max(finalLeft - startColumnLeft, 60)); + + const nextSiblingEl = columnEl!.nextElementSibling as HTMLElement; + + if (nextSiblingEl!.classList.contains('table-column-resize')) { + return; + } + // 没有出现滚动条时缩小当前列的宽度同时会放大后面一列的宽度 + if (!isScrolling && columnRect.width > latestColumnWidth) { + const latestWidth = columnRect.width - latestColumnWidth + nextSiblingEl!.getBoundingClientRect().width; + console.log('latestWidth = ', latestWidth); + } + + resizeProxy.style.display = 'none'; + document.body.style.cursor = ''; + dragging.value = false; + + const realWidth = Math.max( + columnSizeConfig.value[columnKey].minWidth as number, + Math.min(latestColumnWidth, columnSizeConfig.value[columnKey].maxWidth as number), + ); + columnSizeConfig.value[columnKey].renderWidth = realWidth; + } + + dragable = false; + + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + document.onselectstart = null; + document.ondragstart = null; + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + + const handleMouseMove = (event: MouseEvent) => { + const target = (event.target as HTMLElement).closest('th'); + + if (!target) { + return; + } + + const rect = target!.getBoundingClientRect(); + + const bodyStyle = document.body.style; + if (rect.width > 12 && rect.right - event.pageX < 16) { + bodyStyle.cursor = 'col-resize'; + bodyStyle.userSelect = 'none'; + dragable = true; + } else if (!dragging.value) { + bodyStyle.cursor = ''; + bodyStyle.userSelect = ''; + dragable = false; + } + }; + + const handleOuterMousemove = _.throttle((event: Event) => { + let i = event.composedPath().length - 1; + while (i >= 0) { + const target = event.composedPath()[i] as HTMLElement; + if (target.classList && target.classList.contains('bk-editable-table')) { + return; + } + i = i - 1; + } + document.body.style.cursor = ''; + }, 500); + + onMounted(() => { + document.addEventListener('mousemove', handleOuterMousemove); + }); + + onBeforeUnmount(() => { + document.removeEventListener('mousemove', handleOuterMousemove); + }); + + return { + columnSizeConfig, + handleMouseDown, + handleMouseMove, + }; +} diff --git a/dbm-ui/frontend/src/components/editable-table/hooks/use-scroll.ts b/dbm-ui/frontend/src/components/editable-table/hooks/use-scroll.ts new file mode 100644 index 0000000000..c9030713a2 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/hooks/use-scroll.ts @@ -0,0 +1,57 @@ +import _ from 'lodash'; +import { onBeforeUnmount, onMounted, type Ref, shallowRef } from 'vue'; + +export default function (tableContentRef: Ref) { + const leftFixedStyles = shallowRef({}); + const rightFixedStyles = shallowRef({}); + + const handleHorizontalScroll = _.throttle(() => { + const tableEl = tableContentRef.value as HTMLElement; + const { scrollLeft } = tableEl; + const tableContentWidth = tableEl.getBoundingClientRect().width; + const tableWidth = tableEl.querySelector('table')!.getBoundingClientRect().width; + if (scrollLeft === 0) { + leftFixedStyles.value = { + display: 'none', + }; + } else { + const fixedColumns = tableEl.querySelectorAll('th.is-column-fixed-left'); + const fixedWidth = Array.from(fixedColumns).reduce( + (result, itemEl) => result + itemEl.getBoundingClientRect().width, + 0, + ); + leftFixedStyles.value = { + width: `${fixedWidth}px`, + }; + } + + if (tableContentWidth + scrollLeft >= tableWidth) { + rightFixedStyles.value = { + display: 'none', + }; + } else { + const fixedRightColumns = tableEl.querySelectorAll('th.is-column-fixed-right'); + const fixeRightdWidth = Array.from(fixedRightColumns).reduce( + (result, itemEl) => result + itemEl.getBoundingClientRect().width, + 0, + ); + rightFixedStyles.value = { + width: `${fixeRightdWidth}px`, + }; + } + }, 30); + + onMounted(() => { + const tableEl = tableContentRef.value as HTMLElement; + tableEl.addEventListener('scroll', handleHorizontalScroll); + onBeforeUnmount(() => { + tableEl.removeEventListener('scroll', handleHorizontalScroll); + }); + }); + + return { + leftFixedStyles, + rightFixedStyles, + initalScroll: handleHorizontalScroll, + }; +} diff --git a/dbm-ui/frontend/src/components/editable-table/types.ts b/dbm-ui/frontend/src/components/editable-table/types.ts new file mode 100644 index 0000000000..627b8f6f9d --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/types.ts @@ -0,0 +1,11 @@ +export interface IRule { + required?: boolean; + email?: boolean; + min?: number; + max?: number; + maxlength?: number; + pattern?: RegExp; + validator?: (value: any) => Promise | boolean | string; + message: (() => string) | string; + trigger: string; +} diff --git a/dbm-ui/frontend/src/components/editable-table/useColumn.ts b/dbm-ui/frontend/src/components/editable-table/useColumn.ts new file mode 100644 index 0000000000..075c4cb9b3 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/useColumn.ts @@ -0,0 +1,22 @@ +import { inject } from 'vue'; + +import { EditableTableColumnKey } from './Column.vue'; +import { type IRule } from './types'; + +export default ( + options = {} as { + rules?: IRule[]; + }, +) => { + const columnContext = inject(EditableTableColumnKey); + + // if (!columnContext) { + // throw new Error('not found EditColumn'); + // } + + if (columnContext && options.rules) { + columnContext.registerRules(options.rules); + } + + return columnContext; +}; diff --git a/dbm-ui/frontend/src/components/editable-table/validator.ts b/dbm-ui/frontend/src/components/editable-table/validator.ts new file mode 100644 index 0000000000..c9912af514 --- /dev/null +++ b/dbm-ui/frontend/src/components/editable-table/validator.ts @@ -0,0 +1,20 @@ +import isDate from 'lodash/isDate'; +import isEmpty from 'lodash/isEmpty'; + +export default { + required: (value: any): boolean => { + if (typeof value === 'number' || typeof value === 'boolean' || isDate(value)) { + return true; + } + return !isEmpty(value); + }, + min: (value: number, min: number): boolean => value >= min, + max: (value: number, max: number): boolean => max >= value, + email: (value: string): boolean => /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/.test(value), + maxlength: (value: string, maxlength: number): boolean => value.length <= maxlength, + pattern: (value: string, pattern: RegExp): boolean => { + const result = pattern.test(value); + pattern.lastIndex = 0; // eslint-disable-line no-param-reassign + return result; + }, +}; diff --git a/dbm-ui/frontend/src/components/resource-host-owner/Index.vue b/dbm-ui/frontend/src/components/resource-host-owner/Index.vue new file mode 100644 index 0000000000..2aa7905f2e --- /dev/null +++ b/dbm-ui/frontend/src/components/resource-host-owner/Index.vue @@ -0,0 +1,38 @@ + + + diff --git a/dbm-ui/frontend/src/components/resource-host-selector/Index.vue b/dbm-ui/frontend/src/components/resource-host-selector/Index.vue new file mode 100644 index 0000000000..d6cf5fcc31 --- /dev/null +++ b/dbm-ui/frontend/src/components/resource-host-selector/Index.vue @@ -0,0 +1,280 @@ + + + diff --git a/dbm-ui/frontend/src/components/resource-host-selector/components/PanelTab.vue b/dbm-ui/frontend/src/components/resource-host-selector/components/PanelTab.vue new file mode 100644 index 0000000000..030909208b --- /dev/null +++ b/dbm-ui/frontend/src/components/resource-host-selector/components/PanelTab.vue @@ -0,0 +1,61 @@ + + + diff --git a/dbm-ui/frontend/src/components/resource-host-selector/hooks/use-search-select-data.ts b/dbm-ui/frontend/src/components/resource-host-selector/hooks/use-search-select-data.ts new file mode 100644 index 0000000000..769c293cbf --- /dev/null +++ b/dbm-ui/frontend/src/components/resource-host-selector/hooks/use-search-select-data.ts @@ -0,0 +1,124 @@ +import { computed } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useRequest } from 'vue-request'; + +import { fetchDiskTypes, fetchMountPoints, getOsTypeList } from '@services/source/dbresourceResource'; +import { fetchDbTypeList } from '@services/source/infras'; +import { getCloudList } from '@services/source/ipchooser'; + +import { useGlobalBizs } from '@stores'; + +import type { SearchValue } from '@components/vue2/search-select/index.vue'; + +import { getSearchSelectorParams } from '@utils'; + +export default (props: any) => { + const { t } = useI18n(); + const globalBizsStore = useGlobalBizs(); + + const value = ref([]); + + const searchSelectData = computed(() => { + const serachList = [ + { + name: 'IP', + id: 'hosts', + }, + { + name: t('所属业务'), + id: 'bk_biz_id', + children: globalBizsStore.bizs.map((item) => ({ + id: `${item.bk_biz_id}`, + name: item.name, + })), + }, + { + name: t('所属DB类型'), + id: 'resource_type', + children: [{ id: 'PUBLIC', name: t('通用') }].concat(dbTypeList.value ?? []), + }, + { + name: t('管控区域'), + id: 'bk_cloud_ids', + children: cloudList.value?.map((item) => ({ + id: item.bk_cloud_id, + name: item.bk_cloud_name, + })), + }, + { + name: t('Agent 状态'), + id: 'agent_status', + children: [ + { + name: t('正常'), + id: '1', + }, + { + name: t('异常'), + id: '0', + }, + ], + }, + { + name: t('操作系统类型'), + id: 'mount_point', + children: osTypeList.value?.map((item) => ({ + id: item, + name: item, + })), + }, + { + name: t('磁盘挂载点'), + id: 'mount_point', + children: mountPointList.value?.map((item) => ({ + id: item, + name: item, + })), + }, + { + name: t('磁盘类型'), + id: 'disk_type', + children: diskTypeList.value?.map((item) => ({ + id: item, + name: item, + })), + }, + ]; + + return serachList.filter((item) => props.params[item.id] === undefined); + }); + + const formatSearchValue = computed(() => getSearchSelectorParams(value.value)); + + const { data: cloudList } = useRequest(getCloudList, { + initialData: [], + }); + + const { data: diskTypeList } = useRequest(fetchDiskTypes, { + initialData: [], + }); + + const { data: mountPointList } = useRequest(fetchMountPoints, { + initialData: [], + }); + + const { data: osTypeList } = useRequest(getOsTypeList, { + defaultParams: [ + { + offset: 0, + limit: -1, + }, + ], + initialData: [], + }); + + const { data: dbTypeList } = useRequest(fetchDbTypeList, { + initialData: [], + }); + + return { + value, + searchSelectData, + formatSearchValue, + }; +};