mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-11 16:52:28 +08:00
fix Bug or add docs
This commit is contained in:
@@ -1,95 +0,0 @@
|
||||
BasicTable 重封装组件说明
|
||||
====
|
||||
|
||||
封装说明
|
||||
----
|
||||
|
||||
> 基础的使用方式与 API 与 [官方版(data-table)](https://www.naiveui.com/zh-CN/os-theme/components/data-table#tree) 本一致,在其基础上,封装了加载数据的方法。
|
||||
>
|
||||
> 你无需在你是用表格的页面进行分页逻辑处理,仅需向 BasicTable 组件传递绑定 `:api="Promise"` 对象即可
|
||||
>
|
||||
> 例子1
|
||||
----
|
||||
(基础使用)
|
||||
|
||||
```vue
|
||||
|
||||
<template>
|
||||
<BasicTable
|
||||
title="表格列表"
|
||||
:columns="columns"
|
||||
:api="loadDataTable"
|
||||
:row-key="row => row.id"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
>
|
||||
<template #toolbar>
|
||||
<n-button type="primary">添加会员</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { BasicTable } from '@/components/Table'
|
||||
import { getTableList } from '@/api/table/list'
|
||||
const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
key: 'address',
|
||||
auth: ['amdin'], // 同时根据权限控制是否显示
|
||||
ifShow: (row) => {
|
||||
return true; // 根据业务控制是否显示
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '日期',
|
||||
key: 'date'
|
||||
},
|
||||
]
|
||||
export default defineComponent({
|
||||
components: { BasicTable },
|
||||
setup() {
|
||||
const loadDataTable = async (params) => {
|
||||
const data = await getTableList(params);
|
||||
return data
|
||||
}
|
||||
return {
|
||||
columns,
|
||||
loadDataTable
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
API
|
||||
----
|
||||
BasicTable 在 NaiveUi 的 data-table 上进行了一层封装,支持了一些预设,并且封装了一些行为。这里只列出与 data-table 不同的 api。
|
||||
|
||||
> request:Promise 参考上面例子写法
|
||||
> ref:可绑定ref 调用组件内部方法(data-table本身的方法和参数)
|
||||
|
||||
Methods
|
||||
----
|
||||
> reload:actionRef.value.reload()
|
||||
|
||||
> 其余方法,请打印查看
|
||||
|
||||
Slots
|
||||
----
|
||||
> 名称:tableTitle | 表格顶部左侧区域
|
||||
> 名称:toolbar | 表格顶部右侧区域
|
||||
|
||||
|
||||
更新时间
|
||||
----
|
||||
|
||||
该文档最后更新于: 2021-07-12 PM 10:13
|
||||
@@ -59,6 +59,7 @@
|
||||
</div>
|
||||
<div class="s-table">
|
||||
<n-data-table
|
||||
ref="tableElRef"
|
||||
v-bind="getBindValues"
|
||||
:pagination="pagination"
|
||||
@update:page="updatePage"
|
||||
@@ -73,7 +74,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { NDataTable } from 'naive-ui';
|
||||
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs } from 'vue';
|
||||
import {
|
||||
ref,
|
||||
defineComponent,
|
||||
reactive,
|
||||
unref,
|
||||
toRaw,
|
||||
computed,
|
||||
toRefs,
|
||||
onMounted,
|
||||
nextTick,
|
||||
} from 'vue';
|
||||
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||
import { createTableContext } from './hooks/useTableContext';
|
||||
|
||||
@@ -88,6 +99,10 @@
|
||||
|
||||
import { BasicTableProps } from './types/table';
|
||||
|
||||
import { getViewportOffset } from '@/utils/domUtils';
|
||||
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
|
||||
import { isBoolean } from '@/utils/is';
|
||||
|
||||
const densityOptions = [
|
||||
{
|
||||
type: 'menu',
|
||||
@@ -117,9 +132,20 @@
|
||||
...NDataTable.props, // 这里继承原 UI 组件的 props
|
||||
...basicProps,
|
||||
},
|
||||
emits: ['fetch-success', 'fetch-error', 'update:checked-row-keys'],
|
||||
emits: [
|
||||
'fetch-success',
|
||||
'fetch-error',
|
||||
'update:checked-row-keys',
|
||||
'edit-end',
|
||||
'edit-cancel',
|
||||
'edit-row-end',
|
||||
'edit-change',
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const deviceHeight = ref(150);
|
||||
const tableElRef = ref<ComponentRef>(null);
|
||||
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
let paginationEl: HTMLElement | null;
|
||||
|
||||
const tableData = ref<Recordable[]>([]);
|
||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||
@@ -173,20 +199,14 @@
|
||||
emit('update:checked-row-keys', rowKeys);
|
||||
}
|
||||
|
||||
//重置 Columns
|
||||
const resetColumns = () => {
|
||||
columns.map((item) => {
|
||||
item.isShow = true;
|
||||
});
|
||||
};
|
||||
|
||||
//获取表格大小
|
||||
const getTableSize = computed(() => state.tableSize);
|
||||
|
||||
//组装表格信息
|
||||
const getBindValues = computed(() => {
|
||||
const tableData = unref(getDataSourceRef);
|
||||
let propsData = {
|
||||
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
||||
return {
|
||||
...unref(getProps),
|
||||
loading: unref(getLoading),
|
||||
columns: toRaw(unref(getPageColumns)),
|
||||
@@ -194,8 +214,8 @@
|
||||
data: tableData,
|
||||
size: unref(getTableSize),
|
||||
remote: true,
|
||||
'max-height': maxHeight,
|
||||
};
|
||||
return propsData;
|
||||
});
|
||||
|
||||
//获取分页信息
|
||||
@@ -205,7 +225,7 @@
|
||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||
}
|
||||
|
||||
const tableAction: TableActionType = {
|
||||
const tableAction = {
|
||||
reload,
|
||||
setColumns,
|
||||
setLoading,
|
||||
@@ -216,14 +236,54 @@
|
||||
setCacheColumnsField,
|
||||
emit,
|
||||
getSize: () => {
|
||||
return unref(getBindValues).size as SizeType;
|
||||
return unref(getBindValues).size;
|
||||
},
|
||||
};
|
||||
|
||||
const getCanResize = computed(() => {
|
||||
const { canResize } = unref(getProps);
|
||||
return canResize;
|
||||
});
|
||||
|
||||
async function computeTableHeight() {
|
||||
const table = unref(tableElRef);
|
||||
if (!table) return;
|
||||
if (!unref(getCanResize)) return;
|
||||
const tableEl: any = table?.$el;
|
||||
const headEl = tableEl.querySelector('.n-data-table-thead ');
|
||||
const { bottomIncludeBody } = getViewportOffset(headEl);
|
||||
const headerH = 64;
|
||||
let paginationH = 2;
|
||||
let marginH = 24;
|
||||
if (!isBoolean(pagination)) {
|
||||
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
||||
if (paginationEl) {
|
||||
const offsetHeight = paginationEl.offsetHeight;
|
||||
paginationH += offsetHeight || 0;
|
||||
} else {
|
||||
paginationH += 28;
|
||||
}
|
||||
}
|
||||
let height =
|
||||
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
||||
const maxHeight = props.maxHeight;
|
||||
height = maxHeight && maxHeight < height ? maxHeight : height;
|
||||
deviceHeight.value = height;
|
||||
}
|
||||
|
||||
useWindowSizeFn(computeTableHeight, 280);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
computeTableHeight();
|
||||
});
|
||||
});
|
||||
|
||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
tableElRef,
|
||||
getBindValues,
|
||||
densityOptions,
|
||||
reload,
|
||||
@@ -232,7 +292,6 @@
|
||||
updatePageSize,
|
||||
updateCheckedRowKeys,
|
||||
pagination,
|
||||
resetColumns,
|
||||
tableAction,
|
||||
};
|
||||
},
|
||||
|
||||
41
src/components/Table/src/componentMap.ts
Normal file
41
src/components/Table/src/componentMap.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Component } from 'vue';
|
||||
import {
|
||||
NInput,
|
||||
NSelect,
|
||||
NCheckbox,
|
||||
NInputNumber,
|
||||
NSwitch,
|
||||
NDatePicker,
|
||||
NTimePicker,
|
||||
} from 'naive-ui';
|
||||
import type { ComponentType } from './types/componentType';
|
||||
|
||||
export enum EventEnum {
|
||||
NInput = 'on-input',
|
||||
NInputNumber = 'on-input',
|
||||
NSelect = 'on-update:value',
|
||||
NSwitch = 'on-update:value',
|
||||
NCheckbox = 'on-update:value',
|
||||
NDatePicker = 'on-update:value',
|
||||
NTimePicker = 'on-update:value',
|
||||
}
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('NInput', NInput);
|
||||
componentMap.set('NInputNumber', NInputNumber);
|
||||
componentMap.set('NSelect', NSelect);
|
||||
componentMap.set('NSwitch', NSwitch);
|
||||
componentMap.set('NCheckbox', NCheckbox);
|
||||
componentMap.set('NDatePicker', NDatePicker);
|
||||
componentMap.set('NTimePicker', NTimePicker);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName);
|
||||
}
|
||||
|
||||
export { componentMap };
|
||||
@@ -41,6 +41,7 @@
|
||||
actions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default: null,
|
||||
required: true,
|
||||
},
|
||||
dropDownActions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { FunctionalComponent, defineComponent } from 'vue';
|
||||
import type { ComponentType } from '../../types/componentType';
|
||||
import { componentMap } from '@/components/Table/src/componentMap';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { NPopover } from 'naive-ui';
|
||||
|
||||
export interface ComponentProps {
|
||||
component: ComponentType;
|
||||
rule: boolean;
|
||||
popoverVisible: boolean;
|
||||
ruleMessage: string;
|
||||
}
|
||||
|
||||
export const CellComponent: FunctionalComponent = (
|
||||
{ component = 'NInput', rule = true, ruleMessage, popoverVisible }: ComponentProps,
|
||||
{ attrs }
|
||||
) => {
|
||||
const Comp = componentMap.get(component) as typeof defineComponent;
|
||||
|
||||
const DefaultComp = h(Comp, attrs);
|
||||
if (!rule) {
|
||||
return DefaultComp;
|
||||
}
|
||||
return h(
|
||||
NPopover,
|
||||
{ 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' },
|
||||
{
|
||||
trigger: () => DefaultComp,
|
||||
default: () =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
style: {
|
||||
color: 'red',
|
||||
width: '90px',
|
||||
display: 'inline-block',
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => ruleMessage,
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
402
src/components/Table/src/components/editable/EditableCell.vue
Normal file
402
src/components/Table/src/components/editable/EditableCell.vue
Normal file
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div class="editable-cell">
|
||||
<div v-show="!isEdit" class="editable-cell-content" @click="handleEdit">
|
||||
{{ getValues }}
|
||||
<n-icon class="edit-icon" v-if="!column.editRow">
|
||||
<FormOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<div class="flex editable-cell-content" v-show="isEdit" v-click-outside="onClickOutside">
|
||||
<CellComponent
|
||||
v-bind="getComponentProps"
|
||||
:component="getComponent"
|
||||
:style="getWrapperStyle"
|
||||
:popoverVisible="getRuleVisible"
|
||||
:ruleMessage="ruleMessage"
|
||||
:rule="getRule"
|
||||
:class="getWrapperClass"
|
||||
ref="elRef"
|
||||
@options-change="handleOptionsChange"
|
||||
@pressEnter="handleEnter"
|
||||
/>
|
||||
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||
<n-icon class="cursor-pointer mx-2">
|
||||
<CheckOutlined @click="handleSubmit" />
|
||||
</n-icon>
|
||||
<n-icon class="cursor-pointer mx-2">
|
||||
<CloseOutlined @click="handleCancel" />
|
||||
</n-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import type { BasicColumn } from '../../types/table';
|
||||
import type { EditRecordRow } from './index';
|
||||
|
||||
import { defineComponent, ref, unref, nextTick, computed, watchEffect, toRaw } from 'vue';
|
||||
import { FormOutlined, CloseOutlined, CheckOutlined } from '@vicons/antd';
|
||||
import { CellComponent } from './CellComponent';
|
||||
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
|
||||
import clickOutside from '@/directives/clickOutside';
|
||||
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { isString, isBoolean, isFunction, isNumber, isArray } from '@/utils/is';
|
||||
import { createPlaceholderMessage } from './helper';
|
||||
import { set, omit } from 'lodash-es';
|
||||
import { EventEnum } from '@/components/Table/src/componentMap';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditableCell',
|
||||
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent },
|
||||
directives: {
|
||||
clickOutside,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
|
||||
default: '',
|
||||
},
|
||||
record: {
|
||||
type: Object as PropType<EditRecordRow>,
|
||||
},
|
||||
column: {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: () => ({}),
|
||||
},
|
||||
index: propTypes.number,
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
const isEdit = ref(false);
|
||||
const elRef = ref();
|
||||
const ruleVisible = ref(false);
|
||||
const ruleMessage = ref('');
|
||||
const optionsRef = ref<LabelValueOptions>([]);
|
||||
const currentValueRef = ref<any>(props.value);
|
||||
const defaultValueRef = ref<any>(props.value);
|
||||
|
||||
// const { prefixCls } = useDesign('editable-cell');
|
||||
|
||||
const getComponent = computed(() => props.column?.editComponent || 'NInput');
|
||||
const getRule = computed(() => props.column?.editRule);
|
||||
|
||||
const getRuleVisible = computed(() => {
|
||||
return unref(ruleMessage) && unref(ruleVisible);
|
||||
});
|
||||
|
||||
const getIsCheckComp = computed(() => {
|
||||
const component = unref(getComponent);
|
||||
return ['NCheckbox', 'NSwitch'].includes(component);
|
||||
});
|
||||
|
||||
const getComponentProps = computed(() => {
|
||||
const compProps = props.column?.editComponentProps ?? {};
|
||||
const editComponent = props.column?.editComponent ?? null;
|
||||
const component = unref(getComponent);
|
||||
const apiSelectProps: Recordable = {};
|
||||
|
||||
const isCheckValue = unref(getIsCheckComp);
|
||||
|
||||
const valueField = isCheckValue ? 'checked' : 'value';
|
||||
const val = unref(currentValueRef);
|
||||
|
||||
let value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
||||
|
||||
if (component === 'NDatePicker') {
|
||||
value = dayjs(value).valueOf();
|
||||
}
|
||||
|
||||
const onEvent: any = editComponent ? EventEnum[editComponent] : undefined;
|
||||
|
||||
return {
|
||||
placeholder: createPlaceholderMessage(unref(getComponent)),
|
||||
...apiSelectProps,
|
||||
...omit(compProps, 'onChange'),
|
||||
[onEvent]: handleChange,
|
||||
[valueField]: value,
|
||||
};
|
||||
});
|
||||
|
||||
const getValues = computed(() => {
|
||||
const { editComponentProps, editValueMap } = props.column;
|
||||
|
||||
const value = unref(currentValueRef);
|
||||
|
||||
if (editValueMap && isFunction(editValueMap)) {
|
||||
return editValueMap(value);
|
||||
}
|
||||
|
||||
const component = unref(getComponent);
|
||||
if (!component.includes('NSelect')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
|
||||
const option = options.find((item) => `${item.value}` === `${value}`);
|
||||
|
||||
return option?.label ?? value;
|
||||
});
|
||||
|
||||
const getWrapperStyle = computed((): CSSProperties => {
|
||||
// if (unref(getIsCheckComp) || unref(getRowEditable)) {
|
||||
// return {};
|
||||
// }
|
||||
return {
|
||||
width: 'calc(100% - 48px)',
|
||||
};
|
||||
});
|
||||
|
||||
const getWrapperClass = computed(() => {
|
||||
const { align = 'center' } = props.column;
|
||||
return `edit-cell-align-${align}`;
|
||||
});
|
||||
|
||||
const getRowEditable = computed(() => {
|
||||
const { editable } = props.record || {};
|
||||
return !!editable;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
defaultValueRef.value = props.value;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const { editable } = props.column;
|
||||
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
|
||||
isEdit.value = !!editable || unref(getRowEditable);
|
||||
}
|
||||
});
|
||||
|
||||
function handleEdit() {
|
||||
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
|
||||
ruleMessage.value = '';
|
||||
isEdit.value = true;
|
||||
nextTick(() => {
|
||||
const el = unref(elRef);
|
||||
el?.focus?.();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleChange(e: any) {
|
||||
const component = unref(getComponent);
|
||||
if (!e) {
|
||||
currentValueRef.value = e;
|
||||
} else if (e?.target && Reflect.has(e.target, 'value')) {
|
||||
currentValueRef.value = (e as ChangeEvent).target.value;
|
||||
} else if (component === 'NCheckbox') {
|
||||
currentValueRef.value = (e as ChangeEvent).target.checked;
|
||||
} else if (isString(e) || isBoolean(e) || isNumber(e)) {
|
||||
currentValueRef.value = e;
|
||||
}
|
||||
|
||||
//TODO 这里组件参数格式,和dayjs格式不一致
|
||||
if (component === 'NDatePicker') {
|
||||
let format = (props.column.editComponentProps?.format)
|
||||
.replace(/yyyy/g, 'YYYY')
|
||||
.replace(/dd/g, 'DD');
|
||||
currentValueRef.value = dayjs(currentValueRef.value).format(format);
|
||||
}
|
||||
|
||||
const onChange = props.column?.editComponentProps?.onChange;
|
||||
if (onChange && isFunction(onChange)) onChange(...arguments);
|
||||
|
||||
table.emit?.('edit-change', {
|
||||
column: props.column,
|
||||
value: unref(currentValueRef),
|
||||
record: toRaw(props.record),
|
||||
});
|
||||
await handleSubmiRule();
|
||||
}
|
||||
|
||||
async function handleSubmiRule() {
|
||||
const { column, record } = props;
|
||||
const { editRule } = column;
|
||||
const currentValue = unref(currentValueRef);
|
||||
|
||||
if (editRule) {
|
||||
if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
|
||||
ruleVisible.value = true;
|
||||
const component = unref(getComponent);
|
||||
ruleMessage.value = createPlaceholderMessage(component);
|
||||
return false;
|
||||
}
|
||||
if (isFunction(editRule)) {
|
||||
const res = await editRule(currentValue, record as Recordable);
|
||||
if (!!res) {
|
||||
ruleMessage.value = res;
|
||||
ruleVisible.value = true;
|
||||
return false;
|
||||
} else {
|
||||
ruleMessage.value = '';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ruleMessage.value = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSubmit(needEmit = true, valid = true) {
|
||||
if (valid) {
|
||||
const isPass = await handleSubmiRule();
|
||||
if (!isPass) return false;
|
||||
}
|
||||
|
||||
const { column, index, record } = props;
|
||||
if (!record) return false;
|
||||
const { key } = column;
|
||||
const value = unref(currentValueRef);
|
||||
if (!key) return;
|
||||
|
||||
const dataKey = key as string;
|
||||
|
||||
set(record, dataKey, value);
|
||||
//const record = await table.updateTableData(index, dataKey, value);
|
||||
needEmit && table.emit?.('edit-end', { record, index, key, value });
|
||||
isEdit.value = false;
|
||||
}
|
||||
|
||||
async function handleEnter() {
|
||||
if (props.column?.editRow) {
|
||||
return;
|
||||
}
|
||||
await handleSubmit();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
isEdit.value = false;
|
||||
currentValueRef.value = defaultValueRef.value;
|
||||
const { column, index, record } = props;
|
||||
const { key } = column;
|
||||
ruleVisible.value = true;
|
||||
ruleMessage.value = '';
|
||||
table.emit?.('edit-cancel', {
|
||||
record,
|
||||
index,
|
||||
key: key,
|
||||
value: unref(currentValueRef),
|
||||
});
|
||||
}
|
||||
|
||||
function onClickOutside() {
|
||||
if (props.column?.editable || unref(getRowEditable)) {
|
||||
return;
|
||||
}
|
||||
const component = unref(getComponent);
|
||||
|
||||
if (component.includes('NInput')) {
|
||||
handleCancel();
|
||||
}
|
||||
}
|
||||
|
||||
// only ApiSelect
|
||||
function handleOptionsChange(options: LabelValueOptions) {
|
||||
optionsRef.value = options;
|
||||
}
|
||||
|
||||
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
|
||||
if (props.record) {
|
||||
/* eslint-disable */
|
||||
isArray(props.record[cbs])
|
||||
? props.record[cbs]?.push(handle)
|
||||
: (props.record[cbs] = [handle]);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.record) {
|
||||
initCbs('submitCbs', handleSubmit);
|
||||
initCbs('validCbs', handleSubmiRule);
|
||||
initCbs('cancelCbs', handleCancel);
|
||||
|
||||
if (props.column.key) {
|
||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||
props.record.editValueRefs[props.column.key] = currentValueRef;
|
||||
}
|
||||
/* eslint-disable */
|
||||
props.record.onCancelEdit = () => {
|
||||
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
|
||||
};
|
||||
/* eslint-disable */
|
||||
props.record.onSubmitEdit = async() => {
|
||||
if (isArray(props.record?.submitCbs)) {
|
||||
const validFns = (props.record?.validCbs || []).map((fn) => fn());
|
||||
|
||||
const res = await Promise.all(validFns);
|
||||
|
||||
const pass = res.every((item) => !!item);
|
||||
|
||||
if (!pass) return;
|
||||
const submitFns = props.record?.submitCbs || [];
|
||||
submitFns.forEach((fn) => fn(false, false));
|
||||
table.emit?.('edit-row-end');
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isEdit,
|
||||
handleEdit,
|
||||
currentValueRef,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
handleCancel,
|
||||
elRef,
|
||||
getComponent,
|
||||
getRule,
|
||||
onClickOutside,
|
||||
ruleMessage,
|
||||
getRuleVisible,
|
||||
getComponentProps,
|
||||
handleOptionsChange,
|
||||
getWrapperStyle,
|
||||
getWrapperClass,
|
||||
getRowEditable,
|
||||
getValues,
|
||||
handleEnter,
|
||||
// getSize,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.editable-cell {
|
||||
&-content {
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.edit-icon {
|
||||
font-size: 14px;
|
||||
//position: absolute;
|
||||
//top: 4px;
|
||||
//right: 0;
|
||||
display: none;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.edit-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/components/Table/src/components/editable/helper.ts
Normal file
15
src/components/Table/src/components/editable/helper.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ComponentType } from '../../types/componentType';
|
||||
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component === 'NInput') return '请输入';
|
||||
if (
|
||||
['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes(
|
||||
component
|
||||
)
|
||||
)
|
||||
return '请选择';
|
||||
return '';
|
||||
}
|
||||
49
src/components/Table/src/components/editable/index.ts
Normal file
49
src/components/Table/src/components/editable/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { BasicColumn } from '@/components/Table/src/types/table';
|
||||
import { h, Ref } from 'vue';
|
||||
|
||||
import EditableCell from './EditableCell.vue';
|
||||
|
||||
export function renderEditCell(column: BasicColumn) {
|
||||
return (record, index) => {
|
||||
const _key = column.key;
|
||||
const value = record[_key];
|
||||
record.onEdit = async (edit: boolean, submit = false) => {
|
||||
if (!submit) {
|
||||
record.editable = edit;
|
||||
}
|
||||
|
||||
if (!edit && submit) {
|
||||
const res = await record.onSubmitEdit?.();
|
||||
if (res) {
|
||||
record.editable = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// cancel
|
||||
if (!edit && !submit) {
|
||||
record.onCancelEdit?.();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return h(EditableCell, {
|
||||
value,
|
||||
record,
|
||||
column,
|
||||
index,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export type EditRecordRow<T = Recordable> = Partial<
|
||||
{
|
||||
onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
|
||||
editable: boolean;
|
||||
onCancel: Fn;
|
||||
onSubmit: Fn;
|
||||
submitCbs: Fn[];
|
||||
cancelCbs: Fn[];
|
||||
validCbs: Fn[];
|
||||
editValueRefs: Recordable<Ref>;
|
||||
} & T
|
||||
>;
|
||||
@@ -158,7 +158,7 @@
|
||||
table.getColumns().forEach((item) => {
|
||||
newRet.push({ ...item });
|
||||
});
|
||||
return newRet;
|
||||
return newRet.filter((item) => item.key != 'action' && item.title != '操作');
|
||||
}
|
||||
|
||||
//重置
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue';
|
||||
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw, h } from 'vue';
|
||||
import type { BasicColumn, BasicTableProps } from '../types/table';
|
||||
import { isEqual, cloneDeep } from 'lodash-es';
|
||||
import { isArray, isString, isBoolean, isFunction } from '@/utils/is';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { ActionItem } from '@/components/Table';
|
||||
import { renderEditCell } from '../components/editable';
|
||||
import { NTooltip, NIcon } from 'naive-ui';
|
||||
import { FormOutlined } from '@vicons/antd';
|
||||
|
||||
export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
||||
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
|
||||
@@ -33,13 +36,48 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const renderTooltip = (trigger, content) => {
|
||||
return h(NTooltip, null, {
|
||||
trigger: () => trigger,
|
||||
default: () => content,
|
||||
});
|
||||
};
|
||||
|
||||
const getPageColumns = computed(() => {
|
||||
const pageColumns = unref(getColumnsRef);
|
||||
const columns = cloneDeep(pageColumns);
|
||||
return columns.filter((column) => {
|
||||
// @ts-ignore
|
||||
return hasPermission(column.auth) && isIfShow(column);
|
||||
});
|
||||
return columns
|
||||
.filter((column) => {
|
||||
// @ts-ignore
|
||||
return hasPermission(column.auth) && isIfShow(column);
|
||||
})
|
||||
.map((column) => {
|
||||
const { edit, editRow } = column;
|
||||
if (edit) {
|
||||
column.render = renderEditCell(column);
|
||||
if (edit) {
|
||||
const title: any = column.title;
|
||||
column.title = () => {
|
||||
return renderTooltip(
|
||||
h('span', {}, [
|
||||
h('span', { style: { 'margin-right': '5px' } }, title),
|
||||
h(
|
||||
NIcon,
|
||||
{
|
||||
size: 14,
|
||||
},
|
||||
{
|
||||
default: () => h(FormOutlined),
|
||||
}
|
||||
),
|
||||
]),
|
||||
'该列可编辑'
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
return column;
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PropType } from 'vue';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { BasicColumn } from './types/table';
|
||||
|
||||
export const basicProps = {
|
||||
@@ -16,7 +17,7 @@ export const basicProps = {
|
||||
},
|
||||
tableData: {
|
||||
type: [Object],
|
||||
default: () => {},
|
||||
default: () => [],
|
||||
},
|
||||
columns: {
|
||||
type: [Array] as PropType<BasicColumn[]>,
|
||||
@@ -36,6 +37,7 @@ export const basicProps = {
|
||||
type: [Object, Boolean],
|
||||
default: () => {},
|
||||
},
|
||||
//废弃
|
||||
showPagination: {
|
||||
type: [String, Boolean],
|
||||
default: 'auto',
|
||||
@@ -44,4 +46,6 @@ export const basicProps = {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: null,
|
||||
},
|
||||
canResize: propTypes.bool.def(true),
|
||||
resizeHeightOffset: propTypes.number.def(0),
|
||||
};
|
||||
|
||||
8
src/components/Table/src/types/componentType.ts
Normal file
8
src/components/Table/src/types/componentType.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type ComponentType =
|
||||
| 'NInput'
|
||||
| 'NInputNumber'
|
||||
| 'NSelect'
|
||||
| 'NCheckbox'
|
||||
| 'NSwitch'
|
||||
| 'NDatePicker'
|
||||
| 'NTimePicker';
|
||||
@@ -1,6 +1,20 @@
|
||||
import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
|
||||
|
||||
export type BasicColumn = TableBaseColumn;
|
||||
import { ComponentType } from './componentType';
|
||||
export interface BasicColumn extends TableBaseColumn {
|
||||
//编辑表格
|
||||
edit?: boolean;
|
||||
editRow?: boolean;
|
||||
editable?: boolean;
|
||||
editComponent?: ComponentType;
|
||||
editComponentProps?: Recordable;
|
||||
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
|
||||
editValueMap?: (value: any) => string;
|
||||
onEditRow?: () => void;
|
||||
// 权限编码控制是否显示
|
||||
auth?: RoleEnum | RoleEnum[] | string | string[];
|
||||
// 业务控制是否显示
|
||||
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||
}
|
||||
|
||||
export interface TableActionType {
|
||||
reload: (opt) => Promise<void>;
|
||||
@@ -16,4 +30,6 @@ export interface BasicTableProps {
|
||||
pagination: object;
|
||||
showPagination: boolean;
|
||||
actionColumn: any[];
|
||||
canResize: boolean;
|
||||
resizeHeightOffset: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user