This commit is contained in:
xuziqiang
2024-05-15 17:29:42 +08:00
commit d0155dbe3c
7296 changed files with 1832517 additions and 0 deletions

View 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>

View 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,
});
};
}

View 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>

View 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);

View 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),
};

View 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>

View 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;
}
}

View 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>
);
},
};

View 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>

View 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>

View 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
View File

@@ -0,0 +1,6 @@
export interface RequestParams {
search?: object;
page?: number;
sort?: object;
pageSize?: number;
}

View 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>

View 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 };
}

View 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,
};
}

View 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,
};
}

View 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 };
}