push
This commit is contained in:
22
lib/component/table/basic-table.vue
Normal file
22
lib/component/table/basic-table.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="ns-basic-table">
|
||||
<a-table ref="nsBasicTable" v-bind="$attrs">
|
||||
<template #emptyText> <a-empty /> </template>
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
|
||||
<slot v-if="item == 'emptyText'" :name="item"> </slot>
|
||||
<slot v-else :name="item" v-bind="data || {}"> </slot>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsBasicTable',
|
||||
setup() {},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
18
lib/component/table/edit/index.ts
Normal file
18
lib/component/table/edit/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ColumnProps } from 'ant-design-vue/es/table';
|
||||
import { NsTableCell } from './table-cell';
|
||||
import { h } from 'vue';
|
||||
interface Params {
|
||||
text: string;
|
||||
record: Recordable;
|
||||
index: number;
|
||||
}
|
||||
export function renderEditCell(column: ColumnProps) {
|
||||
return ({ text: value, record, index }: Params) => {
|
||||
return h(NsTableCell, {
|
||||
value,
|
||||
record,
|
||||
column,
|
||||
index,
|
||||
});
|
||||
};
|
||||
}
|
||||
243
lib/component/table/edit/table-cell.vue
Normal file
243
lib/component/table/edit/table-cell.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<div class="editable-cell">
|
||||
<!-- 显示模式 -->
|
||||
<div class="editable-cell-text-wrapper" v-if="!getRowValue && !editable">
|
||||
<div> {{ value }}</div>
|
||||
<div class="editable-cell-action">
|
||||
<edit-outlined class="editable-cell-icon" @click="editCell" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 编辑模式 -->
|
||||
<a-popover
|
||||
v-else
|
||||
:destroyTooltipOnHide="true"
|
||||
:visible="!isUndefined(getValidateInfo) && visible"
|
||||
:content="getValidateInfo?.message"
|
||||
:overlayClassName="'ns-cell-rule-popover'"
|
||||
:getPopupContainer="(triggerNode) => triggerNode.parentNode"
|
||||
>
|
||||
<div class="editable-cell-input-wrapper">
|
||||
<component :is="column.edit.component || 'NsInput'" v-bind="formProps" />
|
||||
<!--单元格编辑-->
|
||||
<div class="editable-cell-action" v-if="editable">
|
||||
<check-outlined class="editable-cell-icon" @click="saveCell" />
|
||||
<!-- <close-outlined class="editable-cell-icon" @click="cancelCell" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, nextTick, PropType, ref, watch } from 'vue';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { CheckOutlined, CloseOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import { TableEdit } from '/nerv-lib/component/table/use-table-edit';
|
||||
import { isUndefined, upperFirst } from 'lodash-es';
|
||||
import Schema from 'async-validator';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsTableCell',
|
||||
components: {
|
||||
EditOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
record: {
|
||||
type: Object as PropType<Recordable>,
|
||||
required: true,
|
||||
},
|
||||
column: {
|
||||
type: Object as PropType<Recordable>,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
index: PropTypes.number,
|
||||
},
|
||||
setup(props) {
|
||||
const {
|
||||
getValidate,
|
||||
setValidate,
|
||||
setValue,
|
||||
getValue,
|
||||
getKey,
|
||||
validate,
|
||||
tableEdit,
|
||||
tableSave,
|
||||
} = inject('tableEdit') as TableEdit;
|
||||
const visible = ref(true);
|
||||
|
||||
const getRowKey = computed(() => {
|
||||
return getKey({ ...props.record });
|
||||
});
|
||||
|
||||
const getField = computed(() => {
|
||||
return props.column.dataIndex;
|
||||
});
|
||||
|
||||
let editable = ref(false);
|
||||
|
||||
const getCellValue = computed({
|
||||
get: () => getValue(getRowKey.value, getField.value),
|
||||
set: (val) => setValue(getRowKey.value, val, getField.value),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
() => {
|
||||
console.log('index', props.record, props.index);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
const getRowValue = computed(() => {
|
||||
return getValue(getRowKey.value);
|
||||
});
|
||||
|
||||
const getValidateInfo = computed({
|
||||
get: () => getValidate(getRowKey.value, getField.value),
|
||||
set: (val) => setValidate(getRowKey.value, val, getField.value),
|
||||
});
|
||||
|
||||
// setTimeout(() => console.log('getCellValue', getCellValue.value), 1000);
|
||||
|
||||
watch(
|
||||
[() => getCellValue.value, () => getRowValue.value],
|
||||
() => {
|
||||
validate(getRowKey.value, getField.value);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 解决删除后验证遗留的问题
|
||||
*/
|
||||
watch(
|
||||
() => props.index,
|
||||
() => {
|
||||
if (getValidateInfo.value) {
|
||||
visible.value = false;
|
||||
// 延迟等待隐藏动画操作
|
||||
setTimeout(() => {
|
||||
visible.value = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const editCell = () => {
|
||||
tableEdit(getRowKey.value, getField.value);
|
||||
editable.value = true;
|
||||
};
|
||||
|
||||
const cancelCell = () => {
|
||||
getValidateInfo.value = undefined; //清空验证信息
|
||||
editable.value = false;
|
||||
};
|
||||
|
||||
const saveCell = () => {
|
||||
// console.log('getValidated.value', getValidated.value);
|
||||
if (!getValidateInfo.value) {
|
||||
editable.value = false;
|
||||
tableSave(getRowKey.value, getField.value);
|
||||
}
|
||||
};
|
||||
|
||||
const formProps = computed(() => {
|
||||
const {
|
||||
component,
|
||||
valueField,
|
||||
changeEvent = 'change',
|
||||
props: componentProps,
|
||||
} = props.column.edit;
|
||||
|
||||
const isCheck =
|
||||
component && ['NsSwitch', 'NsCheckbox', 'Switch', 'Checkbox'].includes(component);
|
||||
const bindValue: Recordable = {
|
||||
[valueField || (isCheck ? 'checked' : 'value')]: getCellValue.value,
|
||||
};
|
||||
const eventKey = `on${upperFirst(changeEvent)}`;
|
||||
|
||||
const on = {
|
||||
[eventKey]: (...args: Nullable<Recordable>[]) => {
|
||||
const [e] = args;
|
||||
const target = e ? e.target : null;
|
||||
getCellValue.value = target ? (isCheck ? target.checked : target.value) : e;
|
||||
},
|
||||
};
|
||||
return {
|
||||
...componentProps,
|
||||
...bindValue,
|
||||
...on,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
editCell,
|
||||
saveCell,
|
||||
cancelCell,
|
||||
editable,
|
||||
formProps,
|
||||
visible,
|
||||
getValidateInfo,
|
||||
getCellValue,
|
||||
getRowValue,
|
||||
isUndefined,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ns-cell-rule-popover {
|
||||
color: @error-color;
|
||||
}
|
||||
|
||||
.editable-cell {
|
||||
position: relative;
|
||||
|
||||
.editable-cell-input-wrapper,
|
||||
.editable-cell-text-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.editable-cell-action {
|
||||
width: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.editable-cell-icon {
|
||||
width: 24px;
|
||||
line-height: 28px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover .editable-cell-icon {
|
||||
color: #108ee9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.editable-add-btn {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.editable-cell:hover .editable-cell-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
:deep(.ant-popover) {
|
||||
z-index: 50;
|
||||
}
|
||||
</style>
|
||||
12
lib/component/table/index.ts
Normal file
12
lib/component/table/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { withInstall } from '/nerv-lib/util';
|
||||
import nsBasicTable from './basic-table.vue';
|
||||
import nsTableAction from './table-action.vue';
|
||||
import nsTableHeader from './table-header.vue';
|
||||
import nsTable from './table.vue';
|
||||
import nsTooltip from './table-tips.vue';
|
||||
|
||||
export const NsBasicTable = withInstall(nsBasicTable);
|
||||
export const NsTableAction = withInstall(nsTableAction);
|
||||
export const NsTableHeader = withInstall(nsTableHeader);
|
||||
export const NsTable = withInstall(nsTable);
|
||||
export const NsTooltip = withInstall(nsTooltip);
|
||||
61
lib/component/table/props.ts
Normal file
61
lib/component/table/props.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { PropType } from 'vue';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { tableConfig } from '/nerv-base/config/table.config';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { tableProps as TableProps } from 'ant-design-vue/es/table/Table';
|
||||
|
||||
export const tableProps = {
|
||||
...TableProps(),
|
||||
api: {
|
||||
type: [String, Object, Function] as PropType<string | Function | AxiosRequestConfig>,
|
||||
default: undefined,
|
||||
},
|
||||
params: PropTypes.object.def(() => ({})),
|
||||
dynamicParams: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.array,
|
||||
PropTypes.object,
|
||||
PropTypes.func,
|
||||
]),
|
||||
requiredParams: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.array,
|
||||
PropTypes.object,
|
||||
PropTypes.bool,
|
||||
]),
|
||||
rowSelection: PropTypes.oneOfType([PropTypes.object.def(() => ({})), PropTypes.bool]).def({}),
|
||||
pagination: PropTypes.oneOfType([PropTypes.object.def(() => ({})), PropTypes.bool]).def(true), //分页
|
||||
headerActions: PropTypes.array.def(() => []), //顶部操作栏
|
||||
footerActions: PropTypes.array.def(() => []), //顶部操作栏
|
||||
columnActions: PropTypes.oneOfType([
|
||||
PropTypes.object.def(() => tableConfig.columnActions),
|
||||
PropTypes.bool,
|
||||
]).def(false), //操作栏
|
||||
scroll: PropTypes.object.def(() => tableConfig.scroll), //操作栏
|
||||
size: PropTypes.string.def('middle'),
|
||||
showBack: PropTypes.bool.def(false),
|
||||
pageField: PropTypes.string.def(tableConfig.pageField),
|
||||
pageCountField: PropTypes.string.def(tableConfig.pageCount),
|
||||
listField: PropTypes.string.def(tableConfig.listField),
|
||||
totalField: PropTypes.string.def(tableConfig.totalField),
|
||||
pageFieldOffset: PropTypes.number.def(tableConfig.pageFieldOffset),
|
||||
sizeField: PropTypes.string.def(tableConfig.sizeField),
|
||||
pageSizeOptions: PropTypes.array.def(tableConfig.pageSizeOptions),
|
||||
defaultPageSize: PropTypes.number.def(tableConfig.defaultPageSize),
|
||||
paramsPageSizeField: PropTypes.string.def(tableConfig.paramsPageSizeField),
|
||||
paramsPageField: PropTypes.string.def(tableConfig.paramsPageField),
|
||||
paramsOrderField: PropTypes.string.def(tableConfig.paramsOrderField),
|
||||
|
||||
formConfig: PropTypes.object, //查询表单
|
||||
tableTitle: PropTypes.string,
|
||||
|
||||
treeConfig: PropTypes.object.def(() => ({})), //树型菜单
|
||||
editable: PropTypes.bool.def(false),
|
||||
ediRowData: PropTypes.object.def(() => ({})),
|
||||
value: PropTypes.array.def(() => []),
|
||||
rowKey: PropTypes.oneOfType([PropTypes.func, PropTypes.string]).def('key'),
|
||||
refreshTime: PropTypes.number.def(0),
|
||||
enableTableSession: PropTypes.bool.def(false),
|
||||
expand: PropTypes.bool.def(true),
|
||||
showExpand: PropTypes.bool.def(true),
|
||||
};
|
||||
142
lib/component/table/table-action.vue
Normal file
142
lib/component/table/table-action.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="table-action">
|
||||
<template v-for="(item, index) in getActions" :key="item.name">
|
||||
<template v-if="item.children">
|
||||
<template v-if="getDropdownActions(item.children).length > 0">
|
||||
<a-dropdown :trigger="['hover']" :dropMenuList="item.children">
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
<ns-v-node :content="item.label" />
|
||||
<DownOutlined />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<div v-for="itemChild in getDropdownActions(item.children)" :key="itemChild.name">
|
||||
<a-menu-item style="padding: 0px" v-if="itemChild.type !== 'divider'">
|
||||
<a-button
|
||||
style="width: 100%; text-align: left"
|
||||
type="link"
|
||||
:disabled="itemChild.dynamicDisabled"
|
||||
@click="itemChild.finalHandle(data, itemChild.name)">
|
||||
<ns-v-node :content="itemChild.label" />
|
||||
</a-button>
|
||||
</a-menu-item>
|
||||
<a-menu-divider v-else style="margin: 4px 0" />
|
||||
</div>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:type="item.type || 'link'"
|
||||
:disabled="item.dynamicDisabled"
|
||||
@click="item.finalHandle(data, item.name)">
|
||||
<ns-v-node :content="item.label" />
|
||||
</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, toRefs, unref } from 'vue';
|
||||
import { DownOutlined } from '@ant-design/icons-vue';
|
||||
import { cloneDeep, isFunction, isUndefined } from 'lodash-es';
|
||||
import { useAction } from '/nerv-lib/use/use-action';
|
||||
import { TableEdit } from '/nerv-lib/component/table/use-table-edit';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { PropTypes } from '/nerv-lib/util/type';
|
||||
import { useTableColumn } from '/nerv-lib/component/table/use-table-column';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsTableAction',
|
||||
components: {
|
||||
DownOutlined,
|
||||
},
|
||||
props: {
|
||||
searchData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
columnActions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { columnActions, searchData, data } = toRefs(props);
|
||||
const reload = inject('reload', () => {});
|
||||
const { filterAction, transformAction } = useAction({ reload });
|
||||
const route = useRoute();
|
||||
const getData = computed(() => {
|
||||
return {
|
||||
...route.query,
|
||||
...route.params,
|
||||
...searchData.value,
|
||||
...data.value,
|
||||
};
|
||||
});
|
||||
const { transformColumnAction } = useTableColumn({ columnActions: columnActions.value });
|
||||
|
||||
const getActions = computed(() => {
|
||||
let actions = cloneDeep(unref(columnActions).actions);
|
||||
//是否开启自动隐藏字段
|
||||
if (columnActions.value.autoMergeAction === true) {
|
||||
actions = transformColumnAction(actions);
|
||||
}
|
||||
|
||||
actions = actions
|
||||
.filter((action) => {
|
||||
return filterAction(action, getData.value);
|
||||
})
|
||||
.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
});
|
||||
|
||||
const getDropdownActions = (actions) => {
|
||||
actions = actions
|
||||
.filter((action) => {
|
||||
return filterAction(action, getData.value);
|
||||
})
|
||||
.map((action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
};
|
||||
const { serviceMode } = __APP_INFO__;
|
||||
|
||||
return { getActions, getDropdownActions, isFunction, serviceMode };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-action {
|
||||
user-select: none;
|
||||
margin-left: -6px;
|
||||
margin-right: -6px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:deep(.ant-btn) {
|
||||
padding: 0 6px !important;
|
||||
height: 22px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
:deep(.ant-dropdown-link) {
|
||||
padding: 0 6px;
|
||||
}
|
||||
:deep(.ant-btn-link:hover) {
|
||||
border-color: transparent;
|
||||
}
|
||||
</style>
|
||||
34
lib/component/table/table-columns.less
Normal file
34
lib/component/table/table-columns.less
Normal file
@@ -0,0 +1,34 @@
|
||||
.status-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: flex;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
&.status-normal::before{
|
||||
background-color: @normal-color;
|
||||
}
|
||||
|
||||
&.status-ing::before{
|
||||
background-color: @primary-color;
|
||||
}
|
||||
|
||||
&.status-fail::before {
|
||||
background-color: @error-color;
|
||||
}
|
||||
|
||||
&.status-success::before {
|
||||
background-color: @success-color;
|
||||
}
|
||||
|
||||
&.status-warning::before {
|
||||
background-color: @warning-color;
|
||||
}
|
||||
}
|
||||
243
lib/component/table/table-columns.tsx
Normal file
243
lib/component/table/table-columns.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import { dateUtil } from '/nerv-lib/util/date-util';
|
||||
import { NsModal } from '/nerv-lib/component/modal';
|
||||
import { HttpRequestConfig, useApi } from '/nerv-lib/use/use-api';
|
||||
import { useParams } from '/nerv-lib/use/use-params';
|
||||
import { isFunction, isNumber, isString, toLower } from 'lodash-es';
|
||||
import { Loading3QuartersOutlined, QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import './table-columns.less';
|
||||
|
||||
interface RenderTransform {
|
||||
api?: string | Recordable | Function;
|
||||
messageField?: string | Function;
|
||||
params?: string | Array<string> | Recordable;
|
||||
pipe: string;
|
||||
statusField?: string | Function;
|
||||
textField?: string | Function;
|
||||
isShowQuIcon?: Function; //是否展示错误时的 ?icon,用法同上,需要返回boolean,传了,就代表你要控制icon的展示,不传就代表只在错误时展示
|
||||
}
|
||||
|
||||
type RenderTransformProps = Omit<RenderTransform, 'pipe'>;
|
||||
|
||||
interface ColumnData {
|
||||
index: number;
|
||||
record: Recordable;
|
||||
text: any;
|
||||
}
|
||||
|
||||
//格式化列数据
|
||||
export function transformColumns(columns: Array<any>) {
|
||||
return columns.map((item) => {
|
||||
if (item.transform) {
|
||||
item.customRender = getRender(item.transform);
|
||||
// delete item.transform; //排序中有字段比较,对象属性影响结果
|
||||
if (item.transform?.pipe === 'CNY') {
|
||||
item.align = 'right';
|
||||
}
|
||||
}
|
||||
|
||||
if (item.textNumber || isNumber(item.customType)) {
|
||||
const textNumber = item.textNumber || item.customType;
|
||||
item.width = 12 * 2 + 16 * textNumber;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
function formatMoney(s: string) {
|
||||
const n = 2;
|
||||
s = parseFloat((s + '').replace(/[^\d\.-]/g, '')).toFixed(n) + '';
|
||||
const l = s.split('.')[0].split('').reverse(),
|
||||
r = s.split('.')[1];
|
||||
let t = '';
|
||||
for (let index = 0; index < l.length; index++) {
|
||||
t += l[index] + ((index + 1) % 3 == 0 && index + 1 != l.length ? ',' : '');
|
||||
}
|
||||
return '¥' + t.split('').reverse().join('') + '.' + r;
|
||||
}
|
||||
|
||||
//根据transform生成customRender
|
||||
function getRender(transform: RenderTransform) {
|
||||
const { pipe, ...ext } = transform;
|
||||
return (data: any) => {
|
||||
return pipes[pipe](data, {
|
||||
params: {},
|
||||
...ext,
|
||||
});
|
||||
};
|
||||
}
|
||||
const { httpRequest } = useApi();
|
||||
const { getParams } = useParams();
|
||||
|
||||
//约定处理函数
|
||||
const pipes: Record<
|
||||
string,
|
||||
(columnData: ColumnData, { params, api, messageField, statusField }: RenderTransformProps) => any
|
||||
> = {
|
||||
date: ({ text }, { params }) => {
|
||||
const { format = 'YYYY-MM-DD HH:mm' } = params as Recordable;
|
||||
return dateUtil(text).format(format);
|
||||
},
|
||||
CNY: ({ text }) => {
|
||||
const money = text || 0;
|
||||
return <span style="color: #D07C35;"> {formatMoney(money)}</span>;
|
||||
},
|
||||
state: (
|
||||
{ text, record },
|
||||
{ params, api, messageField, statusField = 'showStatus', textField, isShowQuIcon },
|
||||
) => {
|
||||
let statusFieldValue = '';
|
||||
if (isFunction(statusField)) {
|
||||
statusFieldValue = statusField(record);
|
||||
} else if (isString(statusField)) {
|
||||
statusFieldValue = record[statusField];
|
||||
}
|
||||
|
||||
// 优先使用textField 取值 没有则使用text
|
||||
let textFieldValue: string = text;
|
||||
if (isFunction(textField)) {
|
||||
textFieldValue = textField(record);
|
||||
} else if (isString(textField)) {
|
||||
textFieldValue = record[textField];
|
||||
}
|
||||
|
||||
// 点击提示框参数
|
||||
const props = {
|
||||
onClick: () => {
|
||||
if (messageField) {
|
||||
if (isFunction(messageField)) {
|
||||
messageField = messageField(record);
|
||||
}
|
||||
NsModal.error({
|
||||
title: '警告',
|
||||
content: record[messageField as string],
|
||||
});
|
||||
} else if (api) {
|
||||
const requestConfig: HttpRequestConfig = { method: 'get' };
|
||||
|
||||
const requestParams = getParams(record, params);
|
||||
|
||||
httpRequest({ api, params: requestParams, pathParams: record, requestConfig }).then(
|
||||
(res: any) => {
|
||||
NsModal.error({
|
||||
title: '警告',
|
||||
content: res.message,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
};
|
||||
// 问号图标
|
||||
const quIconNode = () => {
|
||||
return (
|
||||
isShowQuIcon &&
|
||||
isShowQuIcon(record) && (
|
||||
<QuestionCircleOutlined
|
||||
{...props}
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
marginLeft: '4px',
|
||||
color: '#666',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// 成功
|
||||
if (
|
||||
statusFieldValue.endsWith('success') ||
|
||||
statusFieldValue.endsWith('available') ||
|
||||
toLower(statusFieldValue).endsWith('active') ||
|
||||
statusFieldValue.endsWith('enabled')
|
||||
) {
|
||||
return (
|
||||
<div class="status-field status-success">
|
||||
<span>{textFieldValue}</span>
|
||||
{quIconNode()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 错误
|
||||
if (
|
||||
statusFieldValue.endsWith('fail') ||
|
||||
statusFieldValue.endsWith('error') ||
|
||||
statusFieldValue.endsWith('failed') ||
|
||||
toLower(statusFieldValue).endsWith('disabled')
|
||||
) {
|
||||
if (isFunction(messageField)) {
|
||||
messageField = messageField(record);
|
||||
}
|
||||
return (
|
||||
<div class="status-field status-fail">
|
||||
<span> {textFieldValue} </span>
|
||||
{(!isShowQuIcon || isShowQuIcon(record)) && record[messageField as string] && (
|
||||
<QuestionCircleOutlined
|
||||
{...props}
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
marginLeft: '4px',
|
||||
color: '#666',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 提醒状态
|
||||
if (statusFieldValue.endsWith('warning')) {
|
||||
return (
|
||||
<div class="status-field status-warning">
|
||||
<span>{textFieldValue}</span>
|
||||
{quIconNode()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 执行中
|
||||
if (statusFieldValue.endsWith('ing')) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<a-spin
|
||||
indicator={
|
||||
<Loading3QuartersOutlined
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
margin: '6px',
|
||||
}}
|
||||
spin={true}></Loading3QuartersOutlined>
|
||||
}
|
||||
/>
|
||||
<a-typography-text>{textFieldValue}</a-typography-text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 提醒状态
|
||||
if (statusFieldValue.endsWith('unknown')) {
|
||||
return (
|
||||
<div class="status-field status-normal">
|
||||
<span>{textFieldValue}</span>
|
||||
{quIconNode()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
// <div class="status-field status-normal">
|
||||
<div>
|
||||
<span>{textFieldValue ? textFieldValue : '-'}</span>
|
||||
{quIconNode()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
79
lib/component/table/table-footer.vue
Normal file
79
lib/component/table/table-footer.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="table-footer">
|
||||
<template v-for="item in getActions" :key="item.name">
|
||||
<ns-button
|
||||
@click="item.finalHandle(data, item.name)"
|
||||
:disabled="item.dynamicDisabled"
|
||||
:type="item.type"
|
||||
>
|
||||
{{ item.label }}
|
||||
</ns-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { Action, useAction } from '/nerv-lib/use/use-action';
|
||||
import NsButton from '../form/button/button.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsTableFooter',
|
||||
components: {
|
||||
NsButton,
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
footerActions: {
|
||||
type: [Array],
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const reload = inject('reload', () => {});
|
||||
const { filterAction, transformAction } = useAction({ reload });
|
||||
const getData = computed(() => {
|
||||
return {
|
||||
...props.data,
|
||||
};
|
||||
});
|
||||
|
||||
const getActions = computed(() => {
|
||||
let actions = cloneDeep(props.footerActions);
|
||||
actions = actions
|
||||
.filter((action: Action) => {
|
||||
return filterAction(action, getData.value);
|
||||
})
|
||||
.map((action: Action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
});
|
||||
|
||||
return { getActions, getData };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-footer {
|
||||
min-width: 800px;
|
||||
user-select: none;
|
||||
margin-bottom: 17px;
|
||||
margin-top: 17px;
|
||||
text-align: right;
|
||||
padding: 0 24px;
|
||||
position: relative;
|
||||
line-height: 30px;
|
||||
color: #121212;
|
||||
|
||||
.ant-btn {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
105
lib/component/table/table-header.vue
Normal file
105
lib/component/table/table-header.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="ns-table-header" v-if="!isEmpty(getActions)">
|
||||
<!-- <div class="ns-table-title" v-if="tableTitle">{{ tableTitle }}</div> -->
|
||||
<div class="ns-table-header-action">
|
||||
<slot name="header" :data="data"></slot>
|
||||
<template v-for="item in getActions" :key="item.name">
|
||||
<ns-button @click="item.finalHandle()" :disabled="item.dynamicDisabled" :type="item.type">
|
||||
<ns-v-node :content="item.label" />
|
||||
</ns-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject } from 'vue';
|
||||
import { cloneDeep, isEmpty } from 'lodash-es';
|
||||
import { Action, useAction } from '/nerv-lib/use/use-action';
|
||||
import NsButton from '../form/button/button.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsTableHeader',
|
||||
components: {
|
||||
NsButton,
|
||||
},
|
||||
props: {
|
||||
searchData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
tableTitle: {
|
||||
type: String,
|
||||
},
|
||||
data: {
|
||||
type: [Array],
|
||||
default: () => [],
|
||||
},
|
||||
headerActions: {
|
||||
type: [Array],
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const reload = inject('reload', () => {});
|
||||
const { filterAction, transformAction } = useAction({ reload });
|
||||
const route = useRoute();
|
||||
const getData = computed(() => {
|
||||
return {
|
||||
...route.query,
|
||||
...route.params,
|
||||
...props.searchData,
|
||||
list: props.data,
|
||||
formModel: props.searchData,
|
||||
};
|
||||
});
|
||||
|
||||
const getActions = computed(() => {
|
||||
let actions = cloneDeep(props.headerActions);
|
||||
actions = actions
|
||||
.filter((action: Action) => {
|
||||
return filterAction(action, getData.value);
|
||||
})
|
||||
.map((action: Action) => {
|
||||
return transformAction(action, getData.value);
|
||||
});
|
||||
return actions;
|
||||
});
|
||||
|
||||
return { getActions, getData, isEmpty };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-table-header {
|
||||
min-width: 500px;
|
||||
user-select: none;
|
||||
// padding: 16px 0;
|
||||
padding-top: 16px;
|
||||
text-align: right;
|
||||
position: relative;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.ns-table-title {
|
||||
text-align: left;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
//font-size: 16px;
|
||||
font-weight: bold;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
margin-left: 6px;
|
||||
}
|
||||
:first-child.ant-btn {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
16
lib/component/table/table-tips.vue
Normal file
16
lib/component/table/table-tips.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsTooltip',
|
||||
});
|
||||
</script>
|
||||
6
lib/component/table/table.d.ts
vendored
Normal file
6
lib/component/table/table.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface RequestParams {
|
||||
search?: object;
|
||||
page?: number;
|
||||
sort?: object;
|
||||
pageSize?: number;
|
||||
}
|
||||
719
lib/component/table/table.vue
Normal file
719
lib/component/table/table.vue
Normal file
@@ -0,0 +1,719 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="ns-table" :class="{ 'ns-table-no-search': !(formConfig?.schemas.length > 0) }">
|
||||
<!-- tabletitle -->
|
||||
<div
|
||||
class="ns-table-title"
|
||||
@click="
|
||||
() => {
|
||||
showBack ? navigateBack() : '';
|
||||
}
|
||||
"
|
||||
v-if="tableTitle">
|
||||
<ns-icon v-if="showBack" class="backIcon" name="left" />{{ tableTitle }}
|
||||
</div>
|
||||
<!-- {{ formConfig }} -->
|
||||
<a-spin :spinning="tableState.loading">
|
||||
<div class="ns-table-search" v-if="!isEmpty(formConfig)">
|
||||
<ns-form
|
||||
ref="formElRef"
|
||||
class="ns-table-form"
|
||||
:showAction="true"
|
||||
v-bind="formConfig"
|
||||
:expand="expand"
|
||||
:showExpand="showExpand"
|
||||
:model="formModel"
|
||||
@finish="formFinish" />
|
||||
</div>
|
||||
<a-row type="flex" class="ns-table-main">
|
||||
<a-col :flex="getTreeWidth" v-if="!isEmpty(treeConfig)">
|
||||
<ns-tree v-if="getTreeData.length" v-bind="getTreeBindValue" @select="treeSelect" />
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<ns-table-header
|
||||
v-if="!isEmpty(headerActions) || tableTitle"
|
||||
:headerActions="headerActions"
|
||||
:searchData="formModel"
|
||||
:tableTitle="tableTitle"
|
||||
:data="tableState.selectedRows">
|
||||
<template #header="data">
|
||||
<slot name="header" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-table-header>
|
||||
<ns-basic-table ref="tableElRef" v-bind="getTableBindValues" :dataSource="tableData">
|
||||
<template #emptyText>
|
||||
<template v-if="tableState.loadError">
|
||||
<div class="ns-table-content">
|
||||
<div class="fetch-error">
|
||||
<p>{{ tableState.loadErrorMessage }}</p>
|
||||
<ns-button type="primary" ghost @click="reload">重新加载</ns-button></div
|
||||
></div
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="tableState.loading"
|
||||
><div class="ns-table-content"></div
|
||||
></template>
|
||||
<template v-else>
|
||||
<div class="ns-table-content"> <a-empty /> </div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
<template v-if="item === 'bodyCell'">
|
||||
<template v-if="data?.column?.textEllipsis">
|
||||
<span
|
||||
class="tool-tips"
|
||||
:style="{ width: `${data.column.textWidth || data.column.width}px` }">
|
||||
<ns-tooltip
|
||||
placement="top"
|
||||
v-if="
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex]
|
||||
">
|
||||
<template #title>
|
||||
<span>{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
<span class="text-ellipsis">{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</ns-tooltip>
|
||||
<span class="text-ellipsis" v-else> - </span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="data.column.dataIndex === 'tableAction'">
|
||||
<ns-table-action
|
||||
:data="data.record"
|
||||
:searchData="formModel"
|
||||
:columnActions="getColumnActions" />
|
||||
</template>
|
||||
<template v-if="data.column.edit">
|
||||
<ns-table-cell
|
||||
:value="data.text"
|
||||
:record="data.record"
|
||||
:column="data.column"
|
||||
:index="data.index" />
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="item === 'footer'">
|
||||
<ns-table-footer :footerActions="footerActions" :data="ediRowData" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="data" v-if="!Object.keys($slots).includes('bodyCell')">
|
||||
<template v-if="data.column.textEllipsis">
|
||||
<span
|
||||
class="tool-tips"
|
||||
:style="{ width: `${data.column.textWidth || data.column.width}px` }">
|
||||
<ns-tooltip
|
||||
placement="top"
|
||||
v-if="
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex]
|
||||
">
|
||||
<template #title>
|
||||
<span>{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
<span class="text-ellipsis">{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</ns-tooltip>
|
||||
<span class="text-ellipsis" v-else> - </span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="data.column.dataIndex === 'tableAction'">
|
||||
<ns-table-action
|
||||
:data="data.record"
|
||||
:searchData="formModel"
|
||||
:columnActions="getColumnActions" />
|
||||
</template>
|
||||
<template v-if="data.column.edit">
|
||||
<ns-table-cell
|
||||
:value="data.text"
|
||||
:record="data.record"
|
||||
:column="data.column"
|
||||
:index="data.index" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template
|
||||
#footer
|
||||
v-if="!Object.keys($slots).includes('footer') && !isEmpty(footerActions)">
|
||||
<ns-table-footer :footerActions="footerActions" :data="ediRowData" />
|
||||
</template>
|
||||
</ns-basic-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, provide, reactive, ref, unref, watch } from 'vue';
|
||||
import { RequestParams } from '/nerv-lib/component/table/table';
|
||||
import {
|
||||
cloneDeep,
|
||||
debounce,
|
||||
throttle,
|
||||
get,
|
||||
isArray,
|
||||
isEmpty,
|
||||
isEqual,
|
||||
isFunction,
|
||||
isObject,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from 'lodash-es';
|
||||
import NsTableAction from './table-action.vue';
|
||||
import NsTableHeader from './table-header.vue';
|
||||
import NsTableFooter from './table-footer.vue';
|
||||
import NsTableCell from './edit/table-cell.vue';
|
||||
import { useParams } from '/nerv-lib/use/use-params';
|
||||
import { transformColumns } from '/nerv-lib/component/table/table-columns';
|
||||
import NsBasicTable from '/nerv-lib/component/table/basic-table.vue';
|
||||
import { tableProps } from '/nerv-lib/component/table/props';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { useApi } from '/nerv-lib/use/use-api';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useTableEdit } from '/nerv-lib/component/table/use-table-edit';
|
||||
import { Form } from 'ant-design-vue';
|
||||
import { stringUtil } from '/nerv-lib/util/string-util';
|
||||
import { useTableRefresh } from '/nerv-lib/component/table/use-table-refresh';
|
||||
import { tableConfig } from '/nerv-base/config/table.config';
|
||||
import { useTableSession } from '/nerv-lib/component/table/use-table-session';
|
||||
import { useTableColumn } from '/nerv-lib/component/table/use-table-column';
|
||||
import { useNavigate } from '/nerv-lib/use/use-navigate';
|
||||
export default defineComponent({
|
||||
name: 'NsTable',
|
||||
components: {
|
||||
NsBasicTable,
|
||||
NsTableAction,
|
||||
NsTableHeader,
|
||||
NsTableFooter,
|
||||
NsTableCell,
|
||||
},
|
||||
props: tableProps,
|
||||
|
||||
emits: ['cellChange', 'update:value', 'dataSourceChange', 'update:dataSource'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const tableElRef = ref(null);
|
||||
const formElRef = ref(null);
|
||||
const dataRef = ref([]);
|
||||
const treeParamsRef = ref({});
|
||||
const formParamsRef = ref({});
|
||||
const orderRef = ref({});
|
||||
const formModel = reactive<Recordable>({});
|
||||
const tableData = ref<Recordable[]>([]);
|
||||
const tableState = reactive({
|
||||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
loading: false,
|
||||
loadError: false,
|
||||
loadErrorMessage: '',
|
||||
loadinterval: 0,
|
||||
});
|
||||
const route = useRoute();
|
||||
const { getColumnActionWidth } = useTableColumn({
|
||||
columnActions: Object.assign({}, tableConfig.columnActions, props.columnActions),
|
||||
});
|
||||
const { navigateBack } = useNavigate();
|
||||
const defaultPageRef = ref(1 - props.pageFieldOffset);
|
||||
|
||||
const { setTableSession } = useTableSession(
|
||||
formModel,
|
||||
formParamsRef,
|
||||
defaultPageRef,
|
||||
treeParamsRef,
|
||||
);
|
||||
|
||||
const { delayFetch } = useTableRefresh({ props, reload });
|
||||
|
||||
watch(
|
||||
[() => props.value, () => props.dataSource],
|
||||
() => {
|
||||
tableData.value = props.value || props.dataSource || [];
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const formItemContext = Form.useInjectFormItemContext();
|
||||
|
||||
const getColumnActions = computed(() => {
|
||||
const { actions } = props.columnActions as any;
|
||||
const _tableConfig = cloneDeep(tableConfig);
|
||||
if (actions) {
|
||||
_tableConfig.columnActions.width = getColumnActionWidth(actions);
|
||||
}
|
||||
return Object.assign(_tableConfig.columnActions, props.columnActions);
|
||||
});
|
||||
|
||||
const getColumns = computed(() => {
|
||||
const columns = transformColumns(cloneDeep(props.columns || []));
|
||||
const { title, width, dataIndex, fixed } = getColumnActions.value;
|
||||
if (props.columnActions) {
|
||||
columns.push({
|
||||
title,
|
||||
width,
|
||||
dataIndex,
|
||||
fixed,
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => tableData.value,
|
||||
(val) => {
|
||||
// console.log(val, tableData.value);
|
||||
|
||||
if (isEqual(val, tableData.value)) return;
|
||||
|
||||
const data = cloneDeep(tableData.value);
|
||||
if (props.editable) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
delete data[key][props.rowKey];
|
||||
});
|
||||
}
|
||||
// emit('update:value', data);
|
||||
emit('dataSourceChange', data);
|
||||
formItemContext.onFieldChange();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
const tableEdit = useTableEdit({
|
||||
dataSource: tableData,
|
||||
columns: getColumns,
|
||||
rowKey: props.rowKey,
|
||||
editable: ref(props.editable),
|
||||
});
|
||||
provide('tableEdit', tableEdit);
|
||||
|
||||
const { getParams } = useParams();
|
||||
|
||||
const rowSelection = computed(() => {
|
||||
const { rowSelection } = props;
|
||||
if (rowSelection === false || rowSelection === null) {
|
||||
return null;
|
||||
}
|
||||
return Object.assign(
|
||||
{
|
||||
fixed: true,
|
||||
columnWidth: 48,
|
||||
preserveSelectedRowKeys: true, // 跨页选中默认不清除选中key
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: (selectedRowKeys: never[], selectedRows: never[]) => {
|
||||
tableState.selectedRowKeys = selectedRowKeys;
|
||||
tableState.selectedRows = selectedRows;
|
||||
},
|
||||
},
|
||||
rowSelection,
|
||||
);
|
||||
});
|
||||
|
||||
const customizeRenderEmpty = computed(() => {
|
||||
return () => '暂无数据';
|
||||
});
|
||||
|
||||
const formFinish = debounce((data: object) => {
|
||||
formParamsRef.value = data;
|
||||
fetch({
|
||||
page: 1,
|
||||
});
|
||||
}, 300);
|
||||
|
||||
function setLoading(loading: boolean) {
|
||||
tableState.loading = loading;
|
||||
}
|
||||
|
||||
const tableChangeEvent = (pagination: Props, filters: [], sorter: any) => {
|
||||
// console.log('params', pagination, filters, sorter);
|
||||
if (sorter?.field) {
|
||||
if (sorter.order) {
|
||||
orderRef.value = {
|
||||
[props.paramsOrderField]: stringUtil.toLine(
|
||||
`${sorter.field} ${sorter.order.replace('end', '')}`,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
orderRef.value = { [props.paramsOrderField]: '' }; //覆盖默认params
|
||||
}
|
||||
fetch({
|
||||
page: pagination?.current || getPagination.value?.current || 1,
|
||||
pageSize: pagination?.pageSize,
|
||||
});
|
||||
} else if (pagination?.current) {
|
||||
fetch({
|
||||
page: pagination?.current,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// pagination
|
||||
const getPagination: Recordable | Boolean = computed(() => {
|
||||
const { pagination } = props;
|
||||
const { getPageParams } = attrs;
|
||||
|
||||
if (pagination) {
|
||||
const current = get(dataRef.value, props.pageField);
|
||||
// console.log(current);
|
||||
function getTotal() {
|
||||
let total = 0;
|
||||
if (isFunction(getPageParams)) {
|
||||
total = getPageParams(dataRef)['total'];
|
||||
} else {
|
||||
total = get(dataRef.value, props.totalField);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
return {
|
||||
showQuickJumper: true,
|
||||
showLessItems: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total: number, range: Array<number>) => {
|
||||
return `显示第${range[0]}到${range[1]}条记录 ,共 ${
|
||||
get(dataRef.value, props.totalField) || total
|
||||
} 条记录`;
|
||||
},
|
||||
...(pagination as Props),
|
||||
total: getTotal(),
|
||||
current: (current >= 0 ? current : 0) + props.pageFieldOffset, // 后端0 开始
|
||||
pageSize: get(dataRef.value, props.sizeField),
|
||||
};
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const getTableBindValues = computed(() => {
|
||||
const { params, dynamicParams } = props;
|
||||
return {
|
||||
...attrs,
|
||||
...props,
|
||||
rowSelection: rowSelection.value,
|
||||
params: dynamicParams
|
||||
? getParams({ ...route.params, ...route.query }, dynamicParams, params)
|
||||
: params || {},
|
||||
columns: getColumns.value,
|
||||
pagination: getPagination.value,
|
||||
onChange: tableChangeEvent,
|
||||
};
|
||||
});
|
||||
|
||||
watch(
|
||||
() => getTableBindValues.value.api,
|
||||
() => {
|
||||
// console.log(getTableBindValues.value.api);
|
||||
|
||||
fetch(); //路由切换导致api切换 导致发送请求
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => getTableBindValues.value.params,
|
||||
// () => {
|
||||
// fetch();
|
||||
// },
|
||||
// {
|
||||
// immediate: true,
|
||||
// },
|
||||
// );
|
||||
|
||||
/**
|
||||
* 请求函数
|
||||
* @param requestParams 主要是传入页面,部分变量闭包处理
|
||||
* @param clearDelay 是否需要清除刷新时间(页面操作之后,自动刷新重新计算)
|
||||
*/
|
||||
|
||||
function fetch(requestParams: RequestParams = {}, clearDelay = true) {
|
||||
clearDelay && delayFetch();
|
||||
if (tableState.loadinterval) {
|
||||
clearTimeout(tableState.loadinterval);
|
||||
}
|
||||
if (tableState.loading) {
|
||||
tableState.loadinterval = setTimeout(() => {
|
||||
fetch(requestParams, clearDelay);
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
const { api, pagination } = props;
|
||||
const { page, pageSize } = requestParams;
|
||||
if (api) {
|
||||
let pageParams: Recordable = {};
|
||||
|
||||
if (pagination !== false) {
|
||||
pageParams = {
|
||||
[props.paramsPageField]: page ? page - props.pageFieldOffset : defaultPageRef.value, // 后端0 开始
|
||||
[props.paramsPageSizeField]:
|
||||
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
||||
};
|
||||
} else {
|
||||
pageParams = {
|
||||
[props.paramsPageField]: defaultPageRef.value, // 后端0 开始
|
||||
[props.paramsPageSizeField]:
|
||||
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
||||
};
|
||||
}
|
||||
const httpParams = {
|
||||
...getTableBindValues.value.params,
|
||||
...pageParams,
|
||||
...formParamsRef.value,
|
||||
...treeParamsRef.value,
|
||||
...orderRef.value,
|
||||
};
|
||||
if (!checkrequiredParams(httpParams)) {
|
||||
console.log('check fail');
|
||||
return;
|
||||
}
|
||||
|
||||
setTableSession(pageParams[props.paramsPageField]);
|
||||
|
||||
clearDelay && setLoading(true);
|
||||
|
||||
const requestConfig: AxiosRequestConfig = { method: 'get' };
|
||||
const { httpRequest } = useApi();
|
||||
httpRequest({
|
||||
api,
|
||||
params: httpParams,
|
||||
pathParams: { ...route.params, ...route.query },
|
||||
requestConfig,
|
||||
})
|
||||
.then((res: any) => {
|
||||
tableState.loadError = false;
|
||||
tableState.loadErrorMessage = '';
|
||||
dataRef.value = res;
|
||||
console.log(props.listField);
|
||||
tableData.value = get(unref(dataRef), props.listField);
|
||||
//saas项目配置
|
||||
if (attrs['getPageParams']) {
|
||||
const getPageParams = attrs['getPageParams'];
|
||||
let realPage = getPageParams(dataRef, pageParams.page)['page'];
|
||||
if (realPage !== pageParams.page) {
|
||||
fetch({ page: realPage });
|
||||
}
|
||||
}
|
||||
emit('update:dataSource', tableData.value);
|
||||
clearDelay && setLoading(false);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
const { response, code, message } = error || {};
|
||||
let errMessage = response?.data?.msg;
|
||||
const err: string = error?.toString?.() ?? '';
|
||||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
||||
errMessage = '接口请求超时,请刷新页面重试!';
|
||||
}
|
||||
if (err?.includes('Network Error')) {
|
||||
errMessage = '网络异常,请检查您的网络连接是否正常!';
|
||||
}
|
||||
|
||||
// console.log(getPagination.value);
|
||||
tableState.loadError = true;
|
||||
tableState.loadErrorMessage = errMessage;
|
||||
clearDelay && setLoading(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测requiredParams是否全部获得数据
|
||||
* @param params
|
||||
*/
|
||||
function checkrequiredParams(params: Recordable) {
|
||||
const { params: dynamicParams } = getTableBindValues.value as any;
|
||||
let { requiredParams } = props;
|
||||
if (requiredParams) {
|
||||
if (requiredParams === true) requiredParams = dynamicParams as any;
|
||||
if (isFunction(requiredParams)) {
|
||||
console.error(
|
||||
'Property dynamicParams of props cannot set to Function when using requiredParams',
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
if (isString(requiredParams)) {
|
||||
if (isUndefined(params[requiredParams])) return false;
|
||||
} else if (isArray(requiredParams)) {
|
||||
for (let i = 0, l = requiredParams.length; i < l; i++) {
|
||||
if (isUndefined(params[requiredParams[i]])) return false;
|
||||
}
|
||||
} else if (isObject(requiredParams)) {
|
||||
const keys = Object.keys(requiredParams);
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
if (isUndefined(params[keys[i]])) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function treeSelect(
|
||||
selectedKeys: never[],
|
||||
e: {
|
||||
selected: boolean;
|
||||
selectedNodes: { props: { dataRef: any } }[];
|
||||
node: any;
|
||||
event: any;
|
||||
},
|
||||
) {
|
||||
// console.log(selectedKeys, e);
|
||||
const { dataRef } = e.selectedNodes[0].props;
|
||||
treeParamsRef.value = getParams(dataRef, props.params);
|
||||
fetch({
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
|
||||
const getTreeData = computed(() => {
|
||||
return props?.treeConfig?.treeData || [];
|
||||
});
|
||||
|
||||
const getTreeWidth = computed(() => {
|
||||
return props?.treeConfig?.width || '300px';
|
||||
});
|
||||
|
||||
const getTreeBindValue = computed(() => ({
|
||||
...props?.treeConfig,
|
||||
}));
|
||||
//todo 异步加载|| 树形接口
|
||||
|
||||
function reload(clearDelay = true) {
|
||||
const pagination = unref(getPagination);
|
||||
fetch(
|
||||
{
|
||||
page: pagination === false ? 1 : pagination.current,
|
||||
},
|
||||
clearDelay,
|
||||
);
|
||||
}
|
||||
|
||||
provide('reload', reload); //提供刷新功能
|
||||
|
||||
return {
|
||||
navigateBack,
|
||||
reload,
|
||||
formElRef,
|
||||
tableElRef,
|
||||
getColumnActions,
|
||||
getTableBindValues,
|
||||
formModel,
|
||||
tableState,
|
||||
isEmpty,
|
||||
formFinish,
|
||||
tableChangeEvent,
|
||||
treeSelect,
|
||||
getTreeBindValue,
|
||||
getTreeWidth,
|
||||
getTreeData,
|
||||
customizeRenderEmpty,
|
||||
tableData,
|
||||
treeParamsRef,
|
||||
formParamsRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.backIcon {
|
||||
cursor: pointer;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.ns-table-title {
|
||||
text-align: left;
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
//font-size: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
user-select: text;
|
||||
padding-left: 16px;
|
||||
background: #fff;
|
||||
width: calc(100% + 32px);
|
||||
margin-left: -16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ns-table-content {
|
||||
// background: #e5ebf0;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-spin-nested-loading > div > .ant-spin) {
|
||||
max-height: none;
|
||||
}
|
||||
.ns-table-search {
|
||||
padding-top: 16px;
|
||||
}
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.ns-table {
|
||||
position: relative;
|
||||
// min-height: 400px;
|
||||
// background: #e5ebf0;
|
||||
.ant-spin-nested-loading {
|
||||
height: 100%;
|
||||
// min-height: 400px;
|
||||
}
|
||||
|
||||
.ns-table-content {
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fetch-error {
|
||||
p {
|
||||
line-height: 40px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
width: 88px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
.tool-tips {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
51
lib/component/table/use-table-column.ts
Normal file
51
lib/component/table/use-table-column.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { isNumber } from 'lodash-es';
|
||||
|
||||
export function useTableColumn({ columnActions }: any) {
|
||||
const { autoCalcWidth, autoMergeAction, textNumber, actionNumber } = columnActions;
|
||||
function getColumnActionWidth(actions: []) {
|
||||
const ACTION_NUM = 2; //显示几列操作
|
||||
if (autoCalcWidth === true) {
|
||||
let labelWidth = 0;
|
||||
let actionsLength: number = actionNumber || actions.length; // 更多时候 边距为修改为3
|
||||
//设置了字数则按字数计算
|
||||
if (isNumber(textNumber) && textNumber > 0) {
|
||||
actionsLength = textNumber;
|
||||
} else {
|
||||
if (actionsLength <= ACTION_NUM || autoMergeAction === false) {
|
||||
for (let i = 0; i < actionsLength; i++) {
|
||||
labelWidth += actions[i].label.length;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < ACTION_NUM - 1; i++) {
|
||||
labelWidth += actions[i].label.length;
|
||||
}
|
||||
labelWidth += ACTION_NUM; // 更多
|
||||
actionsLength = ACTION_NUM; //合并为更多
|
||||
}
|
||||
labelWidth <= ACTION_NUM && (labelWidth = ACTION_NUM);
|
||||
}
|
||||
return 14 * (labelWidth + actionsLength) + 10 + 12;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
|
||||
function transformColumnAction(actions: []) {
|
||||
if (autoMergeAction) {
|
||||
const _actions = [];
|
||||
const actionsLength = actions.length;
|
||||
if (actionsLength > 2) {
|
||||
const moreAction = {
|
||||
label: '更多',
|
||||
openPermission: true,
|
||||
name: 'more',
|
||||
children: [...actions.splice(1)],
|
||||
};
|
||||
_actions.push(actions[0], moreAction);
|
||||
return _actions;
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
return { getColumnActionWidth, transformColumnAction };
|
||||
}
|
||||
275
lib/component/table/use-table-edit.ts
Normal file
275
lib/component/table/use-table-edit.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import { cloneDeep, isUndefined } from 'lodash-es';
|
||||
import {
|
||||
ComputedRef,
|
||||
getCurrentInstance,
|
||||
inject,
|
||||
reactive,
|
||||
ref,
|
||||
Ref,
|
||||
toRaw,
|
||||
UnwrapRef,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import Schema from 'async-validator';
|
||||
import { RuleItem, ValidateError } from 'async-validator/dist-types/interface';
|
||||
|
||||
export interface TableEdit {
|
||||
tableEdit?: Function;
|
||||
tableSave?: Function;
|
||||
tableCancel?: Function;
|
||||
tableDelete?: Function;
|
||||
addRow?: Function;
|
||||
getKey?: Function;
|
||||
validate?: Function;
|
||||
getValidate?: Function;
|
||||
setValidate?: Function;
|
||||
setValue?: Function;
|
||||
getValue?: Function;
|
||||
}
|
||||
|
||||
export interface UseTableEdit {
|
||||
dataSource: Ref<Recordable[]>;
|
||||
columns: ComputedRef<Recordable[]>;
|
||||
rowKey: String;
|
||||
editable: Ref<Boolean>;
|
||||
}
|
||||
|
||||
export function useTableEdit({ dataSource, columns, rowKey, editable }: UseTableEdit) {
|
||||
const instance = getCurrentInstance();
|
||||
const emit = instance?.emit;
|
||||
|
||||
const editableData: UnwrapRef<Recordable> = reactive({}); //编辑关联数据
|
||||
const validateData: UnwrapRef<Recordable> = reactive({}); //验证数据
|
||||
|
||||
let autoKey = 0;
|
||||
|
||||
function getKey(record: Recordable): string {
|
||||
// if (isUndefined(record[rowKey]))
|
||||
// console.error(`rowKey: ${rowKey} not find in ${JSON.stringify(record)}`);
|
||||
return record[rowKey];
|
||||
}
|
||||
|
||||
function getValue(key?: string, field?: string): any {
|
||||
if (!key && !field) {
|
||||
//表格编辑 editableData会附加额外的单元格字段
|
||||
return dataSource.value;
|
||||
} else if (!field) {
|
||||
//行编辑
|
||||
return editableData[key as string];
|
||||
}
|
||||
|
||||
return editableData[key] ? editableData[key][field] : editableData[`${key}_${field}`];
|
||||
// 单元格编辑
|
||||
}
|
||||
|
||||
function addRow(item = {}) {
|
||||
const row = cloneDeep(item);
|
||||
row[rowKey] = ++autoKey;
|
||||
dataSource.value.push(row);
|
||||
console.log('addRow', item);
|
||||
console.log('dataSource.value', dataSource.value);
|
||||
|
||||
tableEdit(row[rowKey]);
|
||||
}
|
||||
|
||||
function setValue(key: string, val: any, field?: string) {
|
||||
if (!field) {
|
||||
editableData[key] = val;
|
||||
} else {
|
||||
editableData[key]
|
||||
? (editableData[key][field] = val)
|
||||
: (editableData[`${key}_${field}`] = val);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteValue(key: string, field?: string) {
|
||||
if (!field) {
|
||||
delete editableData[key];
|
||||
} else {
|
||||
delete editableData[`${key}_${field}`];
|
||||
}
|
||||
}
|
||||
|
||||
function getValidate(key: string, field?: string) {
|
||||
if (!field) {
|
||||
return validateData[key];
|
||||
} else {
|
||||
return validateData[`${key}_${field}`];
|
||||
}
|
||||
}
|
||||
|
||||
function setValidate(key: string, val: any, field?: string) {
|
||||
if (!field) {
|
||||
validateData[key] = val;
|
||||
} else {
|
||||
validateData[`${key}_${field}`] = val;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteValidate(key: string, field?: string) {
|
||||
if (!field) {
|
||||
delete validateData[key];
|
||||
} else {
|
||||
delete validateData[`${key}_${field}`];
|
||||
}
|
||||
}
|
||||
|
||||
const validator: Recordable = {};
|
||||
const rules: RuleItem = inject('rules', ref({})).value;
|
||||
|
||||
if (rules.defaultField?.fields) {
|
||||
Object.keys(rules.defaultField.fields).forEach(function (key) {
|
||||
validator[key] = new Schema({ [key]: rules.defaultField.fields[key] });
|
||||
});
|
||||
}
|
||||
|
||||
validator['ROW'] = new Schema(rules.defaultField?.fields || {});
|
||||
validator['TABLE'] = new Schema({
|
||||
table: rules,
|
||||
});
|
||||
|
||||
const getValidator = (key = 'ROW') => {
|
||||
return validator[key];
|
||||
};
|
||||
/**
|
||||
* 验证行
|
||||
* @param key
|
||||
*/
|
||||
const validate = (key?: string, field?: string) => {
|
||||
if (!key && !field) {
|
||||
return getValidator('TABLE')
|
||||
.validate({ table: dataSource.value })
|
||||
.then(() => {
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(({ errors }: { errors: ValidateError[] }) => {
|
||||
return Promise.reject(new Error(errors[0].message));
|
||||
});
|
||||
} else if (!field) {
|
||||
return getValidator('ROW')
|
||||
.validate(getValue(key))
|
||||
.then(() => {
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(({ errors }: { errors: ValidateError[] }) => {
|
||||
return Promise.reject(new Error(errors[0].message));
|
||||
});
|
||||
} else if (key) {
|
||||
return getValidator(field)?.validate({ [field]: getValue(key, field) }, (errors) => {
|
||||
if (errors) {
|
||||
setValidate(key, errors[0], field);
|
||||
} else {
|
||||
setValidate(key, undefined, field);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑行
|
||||
* @param key
|
||||
*/
|
||||
const tableEdit = (key: string, field?: string) => {
|
||||
const item = dataSource.value.filter((item) => key === item[rowKey as keyof Recordable])[0];
|
||||
|
||||
if (!field) {
|
||||
const validateItem: Recordable = {};
|
||||
Object.keys(item).forEach((field) => (validateItem[field] = undefined));
|
||||
setValidate(key, validateItem);
|
||||
|
||||
// 表格编辑时为引用,否则为深拷贝
|
||||
if (editable.value) {
|
||||
setValue(key, item);
|
||||
} else {
|
||||
setValue(key, cloneDeep(item));
|
||||
}
|
||||
} else {
|
||||
setValue(key, item[field], field);
|
||||
setValidate(key, undefined, field);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存
|
||||
* @param key
|
||||
*/
|
||||
const tableSave = (key: string, field?: string) => {
|
||||
const data = dataSource.value.filter(
|
||||
(item: Recordable) => key === item[rowKey as keyof Recordable]
|
||||
)[0];
|
||||
if (!field) {
|
||||
Object.assign(data, getValue(key));
|
||||
deleteValue(key);
|
||||
deleteValidate(key);
|
||||
} else {
|
||||
data[field] = getValue(key, field);
|
||||
emit?.('cellChange', { field, value: data[field], key, record: toRaw(data) });
|
||||
deleteValue(key, field);
|
||||
deleteValidate(key, field);
|
||||
}
|
||||
};
|
||||
|
||||
const tableDelete = (key: string) => {
|
||||
console.log('tableDelete', key);
|
||||
deleteValue(key);
|
||||
deleteValidate(key);
|
||||
|
||||
dataSource.value.splice(
|
||||
dataSource.value.findIndex((item) => item[rowKey] === key),
|
||||
1
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消编辑
|
||||
* @param key
|
||||
*/
|
||||
const tableCancel = (key: string, field?: string) => {
|
||||
if (field) {
|
||||
deleteValue(key);
|
||||
} else {
|
||||
deleteValue(key, field);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置dataSource111
|
||||
* @param data
|
||||
*/
|
||||
|
||||
watch(
|
||||
() => dataSource.value,
|
||||
(val, prev) => {
|
||||
// console.log(val, prev);
|
||||
if (editable.value) {
|
||||
Object.keys(editableData).forEach((key) => {
|
||||
delete editableData[key];
|
||||
});
|
||||
Object.keys(validateData).forEach((key) => {
|
||||
delete validateData[key];
|
||||
});
|
||||
console.log('dataSource.value', dataSource.value);
|
||||
dataSource.value.forEach((item) => {
|
||||
item[rowKey] = ++autoKey;
|
||||
tableEdit(item[rowKey as keyof Recordable]);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
return {
|
||||
tableEdit,
|
||||
tableSave,
|
||||
tableCancel,
|
||||
tableDelete,
|
||||
addRow,
|
||||
getKey,
|
||||
validate,
|
||||
getValidate,
|
||||
setValidate,
|
||||
setValue,
|
||||
getValue,
|
||||
};
|
||||
}
|
||||
57
lib/component/table/use-table-refresh.ts
Normal file
57
lib/component/table/use-table-refresh.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { onBeforeUnmount } from 'vue';
|
||||
interface UseTableRefresh {
|
||||
reload: Function;
|
||||
props: Recordable;
|
||||
}
|
||||
export function useTableRefresh({ props, reload }: UseTableRefresh) {
|
||||
let refreshInterval: NodeJS.Timer;
|
||||
|
||||
/**
|
||||
* 页面自动刷新函数,第一次触发为请求时候
|
||||
* @param immediate
|
||||
*/
|
||||
function delayFetch(immediate = false) {
|
||||
const { refreshTime } = props;
|
||||
if (refreshTime > 0) {
|
||||
console.log(`页面自动刷新开启,间隔${refreshTime}秒`);
|
||||
refreshInterval && clearInterval(refreshInterval);
|
||||
refreshInterval = setInterval(reload.bind(undefined, false), refreshTime * 1000);
|
||||
immediate && reload.bind(undefined, false)();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除定时器
|
||||
*/
|
||||
function clearRefreshInterval() {
|
||||
const { refreshTime } = props;
|
||||
if (refreshTime > 0) {
|
||||
console.log(`页面自动刷新停止`);
|
||||
refreshInterval && clearInterval(refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面切换事件
|
||||
*/
|
||||
function visibilitychange() {
|
||||
if (document.visibilityState === 'visible') {
|
||||
delayFetch(true);
|
||||
} else {
|
||||
console.log(`转入后台`);
|
||||
clearRefreshInterval();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', visibilitychange);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
console.log(`页面退出`);
|
||||
clearRefreshInterval();
|
||||
document.removeEventListener('visibilitychange', visibilitychange);
|
||||
});
|
||||
|
||||
return {
|
||||
delayFetch,
|
||||
};
|
||||
}
|
||||
42
lib/component/table/use-table-session.ts
Normal file
42
lib/component/table/use-table-session.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Ref } from '@vue/reactivity';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
|
||||
export function useTableSession(formModel: Recordable, formParamsRef: Ref, defaultPageRef: Ref, treeParamsRef?: Ref) {
|
||||
const instance = getCurrentInstance();
|
||||
const { enableTableSession } = instance?.props || {};
|
||||
const route = useRoute();
|
||||
|
||||
const { fullPath, name } = route;
|
||||
const tableSession = JSON.parse(sessionStorage[fullPath] || '{}');
|
||||
|
||||
function initTableSession() {
|
||||
if (!enableTableSession) return;
|
||||
tableSession['name'] = name;
|
||||
if (tableSession['formModel']) {
|
||||
Object.assign(formModel, tableSession['formModel']);
|
||||
}
|
||||
if (tableSession['formParams']) {
|
||||
formParamsRef.value = tableSession['formParams'];
|
||||
}
|
||||
if (tableSession['defaultPage']) {
|
||||
defaultPageRef.value = tableSession['defaultPage'];
|
||||
}
|
||||
if (tableSession['treeParams'] && treeParamsRef) {
|
||||
treeParamsRef.value = tableSession['treeParams'];
|
||||
}
|
||||
}
|
||||
|
||||
function setTableSession(page: number) {
|
||||
if (!enableTableSession) return;
|
||||
tableSession['formModel'] = formModel;
|
||||
tableSession['formParams'] = formParamsRef.value;
|
||||
tableSession['defaultPage'] = page;
|
||||
tableSession['treeParams'] = treeParamsRef?.value;
|
||||
sessionStorage[fullPath] = JSON.stringify(tableSession);
|
||||
}
|
||||
|
||||
initTableSession();
|
||||
|
||||
return { setTableSession };
|
||||
}
|
||||
Reference in New Issue
Block a user