diff --git a/.eslintignore b/.eslintignore index ecb138df..31c5bf05 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,5 @@ auto-imports.d.ts components.d.ts .gitignore .vscode -public \ No newline at end of file +public +yarn.* \ No newline at end of file diff --git a/components.d.ts b/components.d.ts index 3e725e7c..c69eab31 100644 --- a/components.d.ts +++ b/components.d.ts @@ -10,5 +10,6 @@ declare module '@vue/runtime-core' { RayTransitionComponent: typeof import('./src/components/RayTransitionComponent/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + TableSetting: typeof import('./src/components/RayTable/src/components/TableSetting/index.vue')['default'] } } diff --git a/src/components/RayTable/src/components/TableSetting/index.scss b/src/components/RayTable/src/components/TableSetting/index.scss new file mode 100644 index 00000000..961a9b36 --- /dev/null +++ b/src/components/RayTable/src/components/TableSetting/index.scss @@ -0,0 +1,51 @@ +$iconSpace: 5px; +$width: 140px; +$activedColor: #2080f0; + +.ray-table__setting-option--draggable { + display: grid; + grid-template-columns: repeat(1, $width); + grid-gap: 10px 0; + justify-items: center; + align-items: center; + justify-content: center; + + & .draggable-item { + display: flex; + align-items: center; + cursor: pointer; + + &:hover { + & .draggable-item__d--icon { + opacity: 1; + } + } + + & .draggable-item__d--icon { + transition: opacity 0.3s var(--r-bezier), transform 0.3s var(--r-bezier); + opacity: 0; + } + + & .draggable-item__d--icon, + & .draggable-item__icon { + padding: $iconSpace; + outline: none; + border: none; + } + + & .draggable-item__icon { + cursor: pointer; + } + + & .draggable-item__icon { + &.draggable-item__icon--actived { + color: $activedColor; + } + } + + & .n-ellipsis { + max-width: 71px; + min-width: 71px; + } + } +} diff --git a/src/components/RayTable/src/components/TableSetting/index.tsx b/src/components/RayTable/src/components/TableSetting/index.tsx index aa1acd4f..a3d06b07 100644 --- a/src/components/RayTable/src/components/TableSetting/index.tsx +++ b/src/components/RayTable/src/components/TableSetting/index.tsx @@ -9,15 +9,157 @@ * @remark 今天也是元气满满撸代码的一天 */ +import './index.scss' +import { NCard, NPopover, NEllipsis, NCheckbox } from 'naive-ui' import RayIcon from '@/components/RayIcon/index' +import VueDraggable from 'vuedraggable' + +import type { + RayTableProvider, + SettingOptions, + ActionOptions, +} from '@/components/RayTable/src/type' const TableSetting = defineComponent({ name: 'TableSetting', - setup() { - return {} + emits: ['columnsUpdate'], + setup(_, { emit }) { + const rayTableProvider = inject('rayTableProvider', {} as RayTableProvider) + const settingOptions = ref(rayTableProvider.modelColumns.value) // 表格表头 + const disableDraggable = ref(true) // 拖拽开关 + + const handleDraggableEnd = () => { + emit('columnsUpdate', settingOptions.value) + } + + /** + * + * @param type 列所需固定方向 + * @param idx 当前操作栏索引位置 + * + * @remark 操作栏锁定列 + */ + const handleFiexClick = (type: 'left' | 'right', idx: number) => { + const key = `${type}FiexActivated` + const value = settingOptions.value[idx] + + value[key] = !value[key] + + if (value[key]) { + value.fixed = type + } else { + value.fixed = void 0 + } + + settingOptions.value[idx] = value + + emit('columnsUpdate', settingOptions.value) + } + + return { + settingOptions, + handleDraggableEnd, + handleFiexClick, + disableDraggable, + } }, render() { - return <> + return ( + + {{ + trigger: () => ( + + ), + default: () => ( + + {{ + default: () => ( + + {{ + item: ({ + element, + index, + }: { + element: ActionOptions + index: number + }) => ( +
+ + + {element.title} + + + {{ + trigger: () => ( + + ), + default: () => '向左固定', + }} + + + {{ + trigger: () => ( + + ), + default: () => '向右固定', + }} + +
+ ), + }} +
+ ), + header: () => ( + + 拖拽 + + ), + }} +
+ ), + }} +
+ ) }, }) diff --git a/src/components/RayTable/src/index.tsx b/src/components/RayTable/src/index.tsx index 02cce7e5..1d671d56 100644 --- a/src/components/RayTable/src/index.tsx +++ b/src/components/RayTable/src/index.tsx @@ -10,42 +10,135 @@ */ import './index.scss' -import { NDataTable, NCard } from 'naive-ui' +import { NDataTable, NCard, NDropdown } from 'naive-ui' import props from './props' -import RayIcon from '@/components/RayIcon/index' +import TableSetting from './components/TableSetting/index' + +import type { ActionOptions } from './type' +import type { WritableComputedRef } from 'vue' +import type { DropdownOption } from 'naive-ui' const RayTable = defineComponent({ name: 'RayTable', props: props, - setup(props) { + emits: ['update:columns', 'menuSelect'], + setup(props, { emit }) { const modelRightClickMenu = computed(() => props.rightClickMenu) + const modelColumns = computed({ + get: () => props.columns, + set: (arr) => { + emit('update:columns', arr) + }, + }) as unknown as WritableComputedRef + const menuConfig = reactive({ + x: 0, + y: 0, + showMenu: false, + }) + let prevRightClickIndex = -1 provide('rayTableProvider', { modelRightClickMenu, + modelColumns, }) - return {} + const handleColumnsUpdate = (arr: ActionOptions[]) => { + modelColumns.value = arr + } + + /** + * + * @param key 右键菜单当前选择 `key` + * @param option 右键菜单当前 `item` + * + * @remark (key: string | number, index: number,option: DropdownOption) => void + */ + const handleRightMenuSelect = ( + key: string | number, + option: DropdownOption, + ) => { + emit('menuSelect', key, prevRightClickIndex, option) + + menuConfig.showMenu = false + } + + /** + * + * @param arr 表格当前行 + * @param idx 表格当前索引位置 + * @returns 自定义属性集 + * + * @remark 集成右键菜单属性, 会自动拦截右键方法, 会自动合并自定义行属性 + */ + const handleRowProps = (arr: ActionOptions, idx: number) => { + const interceptRowProps = props.rowProps?.(arr, idx) + + return { + ...interceptRowProps, + onContextmenu: (e: MouseEvent) => { + e.preventDefault() + + prevRightClickIndex = idx + + menuConfig.showMenu = false + + nextTick().then(() => { + menuConfig.showMenu = true + + menuConfig.x = e.clientX + menuConfig.y = e.clientY + }) + }, + } + } + + return { + handleColumnsUpdate, + ...toRefs(menuConfig), + handleRowProps, + handleRightMenuSelect, + } }, render() { return ( {{ default: () => ( - - {{ - empty: () => this.$slots?.empty, - loading: () => this.$slots?.loading, - }} - +
+ + {{ + empty: () => this.$slots?.empty, + loading: () => this.$slots?.loading, + }} + + {this.showMenu ? ( + (this.showMenu = false)} + onSelect={this.handleRightMenuSelect.bind(this)} + /> + ) : ( + '' + )} +
), header: () => this.title, - 'header-extra': () => ( - - ), + 'header-extra': () => + this.action ? ( + + ) : ( + '' + ), }}
) @@ -53,3 +146,16 @@ const RayTable = defineComponent({ }) export default RayTable + +/** + * + * 完全继承 `NDataTable` + * + * 以实现抬头, 操作栏, 右键菜单功能拓展 + * + * 右键菜单功能, 需要同时启用 `showMenu` 与配置菜单选项才能正常使用 + * + * 可以通过设置 `action` 为 `false` 隐藏操作栏 + * + * 具体拓展 `props` 方法, 可以查看 `props.ts` 中相关注释与代码 + */ diff --git a/src/components/RayTable/src/props.ts b/src/components/RayTable/src/props.ts index 8ca2ec24..61f52c6c 100644 --- a/src/components/RayTable/src/props.ts +++ b/src/components/RayTable/src/props.ts @@ -58,6 +58,16 @@ const rayTableProps = { type: Object as PropType, default: () => ({}), }, + showMenu: { + /** + * + * 是否展示右键菜单 + * + * 默认启用 + */ + type: Boolean, + default: true, + }, } as const export default rayTableProps @@ -66,5 +76,5 @@ export default rayTableProps * * `Ray Table Props` * - * 继承 `naive ui Data Table` + * 继承 `Naive UI Data Table` */ diff --git a/src/components/RayTable/src/type.ts b/src/components/RayTable/src/type.ts index e30c1fcf..978e2299 100644 --- a/src/components/RayTable/src/type.ts +++ b/src/components/RayTable/src/type.ts @@ -3,13 +3,26 @@ import type { DropdownGroupOption, DropdownDividerOption, DropdownRenderOption, - DataTableColumns, + DataTableBaseColumn, } from 'naive-ui' +import type { ComputedRef, WritableComputedRef } from 'vue' -export interface ActionOptions extends DataTableColumns {} +export interface ActionOptions extends DataTableBaseColumn { + leftFiexActivated?: boolean // 向左固定 + rightFiexActivated?: boolean // 向右固定 +} export type DropdownMixedOption = | DropdownOption | DropdownGroupOption | DropdownDividerOption | DropdownRenderOption + +export type SettingOptions = WritableComputedRef + +export type RightClickMenu = ComputedRef + +export interface RayTableProvider { + modelRightClickMenu: RightClickMenu + modelColumns: SettingOptions +} diff --git a/src/icons/draggable.svg b/src/icons/draggable.svg new file mode 100644 index 00000000..d5ff298a --- /dev/null +++ b/src/icons/draggable.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/icons/left_arrow.svg b/src/icons/left_arrow.svg new file mode 100644 index 00000000..356171b0 --- /dev/null +++ b/src/icons/left_arrow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/icons/right_arrow.svg b/src/icons/right_arrow.svg new file mode 100644 index 00000000..a1659e24 --- /dev/null +++ b/src/icons/right_arrow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/styles/base.scss b/src/styles/base.scss index c2b00757..2cd59438 100644 --- a/src/styles/base.scss +++ b/src/styles/base.scss @@ -1,4 +1,5 @@ @import "@/styles/animate.scss"; +@import "@/styles/root.scss"; body, h1, diff --git a/src/styles/root.scss b/src/styles/root.scss new file mode 100644 index 00000000..9a93bae0 --- /dev/null +++ b/src/styles/root.scss @@ -0,0 +1,3 @@ +:root { + --r-bezier: cubic-bezier(0.4, 0, 0.2, 1); +} diff --git a/src/views/table/index.tsx b/src/views/table/index.tsx index 288a8076..2205584b 100644 --- a/src/views/table/index.tsx +++ b/src/views/table/index.tsx @@ -9,26 +9,173 @@ * @remark 今天也是元气满满撸代码的一天 */ -import { NLayout, NCard } from 'naive-ui' +import { NLayout, NCard, NTag, NButton } from 'naive-ui' import RayTable from '@/components/RayTable/index' +import type { DataTableColumns } from 'naive-ui' + +type RowData = { + key: number + name: string + age: number + address: string + tags: string[] +} + const TableView = defineComponent({ name: 'TableView', setup() { - return {} + const baseColumns = [ + { + title: 'Name', + key: 'name', + }, + { + title: 'Age', + key: 'age', + }, + { + title: 'Address', + key: 'address', + }, + { + title: 'Tags', + key: 'tags', + render: (row: RowData) => { + const tags = row.tags.map((tagKey) => { + return h( + NTag, + { + style: { + marginRight: '6px', + }, + type: 'info', + bordered: false, + }, + { + default: () => tagKey, + }, + ) + }) + + return tags + }, + }, + { + title: 'Action', + key: 'actions', + render: (row: RowData) => + h( + NButton, + { + size: 'small', + }, + { default: () => 'Send Email' }, + ), + }, + ] + const actionColumns = ref>( + [...baseColumns].map((curr) => ({ ...curr, width: 400 })), + ) + const tableData = ref([ + { + key: 0, + name: 'John Brown', + age: 32, + address: 'New York No. 1 Lake Park', + tags: ['nice', 'developer'], + }, + { + key: 1, + name: 'Jim Green', + age: 42, + address: 'London No. 1 Lake Park', + tags: ['wow'], + }, + { + key: 2, + name: 'Joe Black', + age: 32, + address: 'Sidney No. 1 Lake Park', + tags: ['cool', 'teacher'], + }, + ]) + const tableMenuOptions = [ + { + label: '编辑', + key: 'edit', + }, + { + label: () => h('span', { style: { color: 'red' } }, '删除'), + key: 'delete', + }, + ] + + const handleMenuSelect = (key: string | number, idx: number) => { + if (key === 'delete') { + tableData.value.splice(idx, 1) + } + } + + return { + tableData, + actionColumns, + baseColumns, + tableMenuOptions, + handleMenuSelect, + } }, render() { return ( - 该组件基于 Naive UI DataTable 组件封装. 实现右键菜单, 表格标题, - 操作栏等功能 +

+ 该组件基于 Naive UI DataTable 组件封装. 实现右键菜单, 表格标题, + 操作栏等功能 +

+

RayTable 完全继承 DataTable 的所有属性与方法

+

+ 相关拓展 props 属性, 可以在源码位置 + src/components/RayTable/src/props.ts 中查看相关代码与注释 +

- + - + {{ + header: () => ( +
+

+ 使用响应式方法代理 columns 并且打开 action 则可以启用操作栏 +

+

拖拽操作栏动态切换表格列

+

点击左右固定按钮, 即可动态固定列

+
+ ), + default: () => ( + + ), + }} +
+ +
)