Browse Source

Merge pull request #957 from colinin/backlogg-update

backlogs update
pull/953/merge
yx lin 2 years ago
committed by GitHub
parent
commit
17d34737b2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 30
      apps/vue/.env.development
  2. 55
      apps/vue/src/components/Table/src/BasicTable.vue
  3. 137
      apps/vue/src/components/Table/src/components/AdvancedSearch.vue
  4. 16
      apps/vue/src/components/Table/src/components/EditTableHeaderIcon.vue
  5. 41
      apps/vue/src/components/Table/src/components/HeaderCell.vue
  6. 188
      apps/vue/src/components/Table/src/components/TableAction.vue
  7. 136
      apps/vue/src/components/Table/src/components/TableFooter.vue
  8. 86
      apps/vue/src/components/Table/src/components/TableHeader.vue
  9. 75
      apps/vue/src/components/Table/src/components/TableImg.vue
  10. 54
      apps/vue/src/components/Table/src/components/TableSelectionBar.vue
  11. 59
      apps/vue/src/components/Table/src/components/TableTitle.vue
  12. 96
      apps/vue/src/components/Table/src/components/editable/EditableCell.vue
  13. 8
      apps/vue/src/components/Table/src/components/editable/index.ts
  14. 729
      apps/vue/src/components/Table/src/components/settings/ColumnSetting.vue
  15. 28
      apps/vue/src/components/Table/src/components/settings/FullScreenSetting.vue
  16. 26
      apps/vue/src/components/Table/src/components/settings/RedoSetting.vue
  17. 69
      apps/vue/src/components/Table/src/components/settings/SizeSetting.vue
  18. 41
      apps/vue/src/components/Table/src/components/settings/TableExport.vue
  19. 72
      apps/vue/src/components/Table/src/components/settings/index.vue
  20. 28
      apps/vue/src/components/Table/src/helper.ts
  21. 84
      apps/vue/src/components/Table/src/hooks/useColumns.ts
  22. 55
      apps/vue/src/components/Table/src/hooks/useCustomRow.ts
  23. 90
      apps/vue/src/components/Table/src/hooks/useDataSource.ts
  24. 91
      apps/vue/src/components/Table/src/hooks/useRowSelection.ts
  25. 34
      apps/vue/src/components/Table/src/hooks/useTable.ts
  26. 109
      apps/vue/src/components/Table/src/hooks/useTableExpand.ts
  27. 12
      apps/vue/src/components/Table/src/hooks/useTableFooter.ts
  28. 2
      apps/vue/src/components/Table/src/hooks/useTableForm.ts
  29. 5
      apps/vue/src/components/Table/src/hooks/useTableHeader.ts
  30. 273
      apps/vue/src/components/Table/src/hooks/useTableScroll.ts
  31. 79
      apps/vue/src/components/Table/src/types/table.ts
  32. 3
      apps/vue/src/enums/cacheEnum.ts
  33. 27
      apps/vue/src/hooks/web/useSignalR.ts
  34. 9
      apps/vue/src/settings/designSetting.ts
  35. 126
      apps/vue/src/store/modules/tableSetting.ts
  36. 4
      apps/vue/src/utils/cache/persistent.ts
  37. 112
      apps/vue/src/utils/is.ts
  38. 2
      apps/vue/src/views/account/center/Cloud.vue
  39. 2
      apps/vue/src/views/account/center/FileList.vue
  40. 18
      apps/vue/src/views/oss-management/objects/components/FileList.vue
  41. 16
      apps/vue/src/views/sys/login/TwoFactorLoginForm.vue
  42. 15
      apps/vue/src/views/task-management/background-jobs/components/JobTable.vue
  43. 20
      apps/vue/src/views/webhooks/send-attempts/components/SendAttemptTable.vue
  44. 19
      apps/vue/src/views/webhooks/subscriptions/components/SubscriptionTable.vue
  45. 8
      apps/vue/types/store.d.ts
  46. 30
      aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/LINGYUN/Abp/AspNetCore/HttpOverrides/AbpAspNetCoreHttpOverridesModule.cs
  47. 36
      aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/LINGYUN/Abp/AspNetCore/HttpOverrides/Forwarded/AbpForwardedHeadersOptions.cs
  48. 51
      aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/LINGYUN/Abp/AspNetCore/WebClientInfo/RequestForwardedHeaderWebClientInfoProvider.cs
  49. 67
      aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/Microsoft/AspNetCore/Builder/ForwardedHeadersOptionsExtensions.cs
  50. 3
      aspnet-core/services/LY.MicroService.Applications.Single/Dockerfile
  51. 14
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.Configure.cs
  52. 9
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.cs
  53. 3
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Dockerfile
  54. 3
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/LY.MicroService.AuthServer.HttpApi.Host.csproj
  55. 3
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/appsettings.Development.json
  56. 3
      aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/appsettings.json
  57. 21
      aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs
  58. 8
      aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs
  59. 3
      aspnet-core/services/LY.MicroService.AuthServer/Dockerfile
  60. 1
      aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj
  61. 1
      aspnet-core/services/LY.MicroService.AuthServer/appsettings.Development.json
  62. 3
      aspnet-core/services/LY.MicroService.AuthServer/appsettings.json
  63. 14
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.Configure.cs
  64. 10
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs
  65. 3
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/Dockerfile
  66. 1
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/LY.MicroService.BackendAdmin.HttpApi.Host.csproj
  67. 6
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/appsettings.Development.json
  68. 3
      aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/appsettings.json
  69. 3
      aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/Dockerfile
  70. 14
      aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.Configure.cs
  71. 9
      aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.cs
  72. 1
      aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/LY.MicroService.identityServer.HttpApi.Host.csproj
  73. 3
      aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/appsettings.json
  74. 3
      aspnet-core/services/LY.MicroService.IdentityServer/Dockerfile
  75. 21
      aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.Configure.cs
  76. 8
      aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.cs
  77. 1
      aspnet-core/services/LY.MicroService.IdentityServer/LY.MicroService.IdentityServer.csproj
  78. 3
      aspnet-core/services/LY.MicroService.IdentityServer/appsettings.json
  79. 3
      aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/Dockerfile
  80. 3
      aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LY.MicroService.LocalizationManagement.HttpApi.Host.csproj
  81. 14
      aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.Configure.cs
  82. 190
      aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.cs
  83. 3
      aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/appsettings.Development.json
  84. 3
      aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/appsettings.json
  85. 3
      aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/Dockerfile
  86. 1
      aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/LY.MicroService.PlatformManagement.HttpApi.Host.csproj
  87. 14
      aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.Configure.cs
  88. 11
      aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs
  89. 3
      aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.Development.json
  90. 3
      aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.json
  91. 3
      aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/Dockerfile
  92. 1
      aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/LY.MicroService.RealtimeMessage.HttpApi.Host.csproj
  93. 14
      aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.Configure.cs
  94. 11
      aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.cs
  95. 3
      aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/appsettings.json
  96. 25
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile
  97. 1
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj
  98. 14
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.Configure.cs
  99. 11
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs
  100. 3
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.json

30
apps/vue/.env.development

@ -1,30 +0,0 @@
# Whether to open mock
VITE_USE_MOCK=false
# public path
VITE_PUBLIC_PATH=/
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY=[["/connect","http://127.0.0.1:30000"],["/api","http://127.0.0.1:30000"],["/signalr-hubs","ws://127.0.0.1:30000"]]
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
# Delete console
VITE_DROP_CONSOLE=false
# Basic interface address SPA
VITE_GLOB_API_URL=/api
# File upload address, optional
VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
# Multi-tenancy key
VITE_GLOB_MULTITENANCY_KEY='__tenant'
# STS Connect
VITE_GLOB_AUTHORITY='http://127.0.0.1:30000'
VITE_GLOB_CLIENT_ID='vue-admin-client'
VITE_GLOB_CLIENT_SECRET='1q2w3e*'

55
apps/vue/src/components/Table/src/BasicTable.vue

@ -33,13 +33,17 @@
v-show="getEmptyDataIsShowTable"
@change="handleTableChange"
@resizeColumn="handleResizeColumn"
@expand="handleTableExpand"
>
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #headerCell="{ column }">
<HeaderCell :column="column" />
<slot name="headerCell" v-bind="{ column }">
<HeaderCell :column="column" />
</slot>
</template>
<!-- 增加对antdv3.x兼容 -->
<template #bodyCell="data">
<slot name="bodyCell" v-bind="data || {}"></slot>
</template>
@ -62,19 +66,10 @@
TableActionType,
SizeType,
ColumnChangeParam,
InnerMethods,
} from './types/table';
import {
defineComponent,
ref,
reactive,
computed,
unref,
toRaw,
inject,
watchEffect,
nextTick,
} from 'vue';
import { defineComponent, ref, reactive, computed, unref, toRaw, inject, watchEffect, nextTick } from 'vue';
import { Button, Table } from 'ant-design-vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { useModal } from '/@/components/Modal/index';
@ -209,12 +204,12 @@
emit,
);
function handleTableChange(...args) {
onTableChange.call(undefined, ...args);
emit('change', ...args);
function handleTableChange(pagination: any, filters: any, sorter: any, extra: any) {
onTableChange(pagination, filters, sorter);
emit('change', pagination, filters, sorter);
// useTableonChange
const { onChange } = unref(getProps);
onChange && isFunction(onChange) && onChange.call(undefined, ...args);
onChange && isFunction(onChange) && onChange(pagination, filters, sorter, extra);
}
const {
@ -248,7 +243,7 @@
const { getRowClassName } = useTableStyle(getProps, prefixCls);
const { getExpandOption, expandAll, expandRows, collapseAll } = useTableExpand(
const { getExpandOption, expandAll, expandRows, collapseRows, collapseAll, handleTableExpand } = useTableExpand(
getProps,
tableData,
emit,
@ -265,15 +260,14 @@
},
};
const methods: InnerMethods = {
clearSelectedRowKeys,
getSelectRowKeys,
};
const { getAlertEnabled, getAlertMessage } = useTableAlert(getProps, getRowSelectionRef);
const { getHeaderProps } = useTableHeader(
getProps,
slots,
handlers,
getAlertEnabled,
getAlertMessage,
);
const { getHeaderProps } = useTableHeader(getProps, slots, handlers, methods, getAlertEnabled, getAlertMessage);
const { getFooterProps } = useTableFooter(
getProps,
@ -289,8 +283,9 @@
getFormSlotKeys,
handleSearchInfoChange,
handleAdvanceSearchChange,
handleAdvanceSearchInfoChange,
} = useTableForm(getProps, slots, fetch, getLoading, formActions.setFieldsValue);
handleAdvanceSearchInfoChange
} =
useTableForm(getProps, slots, fetch, getLoading, formActions.setFieldsValue);
const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef);
@ -310,9 +305,9 @@
footer: unref(getFooterProps),
...unref(getExpandOption),
};
if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll');
}
// if (slots.expandedRowRender) {
// propsData = omit(propsData, 'scroll');
// }
propsData = omit(propsData, ['class', 'onChange']);
return propsData;
@ -373,6 +368,7 @@
setCacheColumnsByField,
expandAll,
expandRows,
collapseRows,
collapseAll,
scrollTo,
getSize: () => {
@ -427,6 +423,7 @@
getWrapperClass,
columns: getViewColumns,
handleResizeColumn,
handleTableExpand,
};
},
});

137
apps/vue/src/components/Table/src/components/AdvancedSearch.vue

@ -8,12 +8,8 @@
>
<Card :title="t('component.table.advancedSearch.conditions')">
<template #extra>
<Button @click="resetFields" danger>{{
t('component.table.advancedSearch.clearCondition')
}}</Button>
<Button @click="handleAddField" type="primary" style="margin-left: 20px">{{
t('component.table.advancedSearch.addCondition')
}}</Button>
<Button @click="resetFields" danger>{{ t('component.table.advancedSearch.clearCondition') }}</Button>
<Button @click="handleAddField" type="primary" style="margin-left: 20px;">{{ t('component.table.advancedSearch.addCondition') }}</Button>
</template>
<Table
size="small"
@ -23,9 +19,9 @@
:pagination="false"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'field'">
<template v-if="column.dataIndex==='field'">
<Select
style="width: 100%"
style="width: 100%;"
show-search
v-model:value="record.field"
:options="getAvailableOptions"
@ -33,39 +29,30 @@
@change="(field) => handleFieldChange(field, record)"
/>
</template>
<template v-else-if="column.dataIndex === 'comparison'">
<template v-else-if="column.dataIndex==='comparison'">
<Select
style="width: 100%"
style="width: 100%;"
v-model:value="record.comparison"
:options="getAvailableComparisonOptions(record)"
/>
</template>
<template v-else-if="column.dataIndex === 'value'">
<Input v-if="record.javaScriptType === 'string'" v-model:value="record.value" />
<template v-else-if="column.dataIndex==='value'">
<Input v-if="record.javaScriptType==='string'" v-model:value="record.value" />
<Select
v-else-if="
record.javaScriptType === 'number' && record.options && record.options.length > 0
"
style="width: 100%"
v-else-if="record.javaScriptType==='number' && record.options && record.options.length > 0"
style="width: 100%;"
v-model:value="record.value"
:options="record.options"
:field-names="{
label: 'key',
value: 'value',
value: 'value'
}"
/>
<InputNumber
v-else-if="record.javaScriptType === 'number'"
style="width: 100%"
v-model:value="record.value"
/>
<Switch
v-else-if="record.javaScriptType === 'boolean'"
v-model:checked="record.value"
/>
<InputNumber v-else-if="record.javaScriptType==='number'" style="width: 100%;" v-model:value="record.value" />
<Switch v-else-if="record.javaScriptType==='boolean'" v-model:checked="record.value" />
<DatePicker
v-else-if="record.javaScriptType === 'Date'"
style="width: 100%"
v-else-if="record.javaScriptType==='Date'"
style="width: 100%;"
v-model:value="record.value"
format="YYYY-MM-DD 00:00:00"
value-format="YYYY-MM-DDT00:00:00"
@ -77,10 +64,14 @@
v-model="record.value"
/>
</template>
<template v-else-if="column.dataIndex === 'logic'">
<Select style="width: 100%" v-model:value="record.logic" :options="logicOptions" />
<template v-else-if="column.dataIndex==='logic'">
<Select
style="width: 100%;"
v-model:value="record.logic"
:options="logicOptions"
/>
</template>
<template v-else-if="column.dataIndex === 'actions'">
<template v-else-if="column.dataIndex==='actions'">
<Popconfirm
v-if="formMdel.paramters.length"
:title="t('table.sureToDelete')"
@ -108,18 +99,12 @@
Popconfirm,
Select,
Switch,
Table,
Table
} from 'ant-design-vue';
import { CodeEditorX, MODE } from '/@/components/CodeEditor';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { useI18n } from '/@/hooks/web/useI18n';
import {
DefineParamter,
DynamicLogic,
DynamicComparison,
DynamicQueryable,
DynamicParamter,
} from '../types/advancedSearch';
import { DefineParamter, DynamicLogic, DynamicComparison, DynamicQueryable, DynamicParamter } from '../types/advancedSearch';
import { isNullOrWhiteSpace } from '/@/utils/strings';
import { isFunction } from '/@/utils/is';
import { get } from 'lodash-es';
@ -135,14 +120,14 @@
default: true,
},
defineFieldApi: {
type: Function as PropType<() => Promise<any>>,
type: Function as PropType<() => Promise<any>>
},
defineFieldReplace: {
type: Function as PropType<(response: any) => DefineParamter[]>,
},
listField: {
type: String,
},
}
});
const { t } = useI18n();
const loadingRef = ref(false);
@ -261,26 +246,27 @@
}
//
const defineParams = unref(defineParamsRef);
if (!defineParams.length) return [];
return defineParams.filter((dp) => !formMdel.paramters.some((fp) => fp.field === dp.name));
if (!defineParams.length) return[];
return defineParams.filter(dp => !formMdel.paramters.some(fp => fp.field === dp.name));
});
const getAvailableOptions = computed(() => {
const availableParams = unref(getAvailableParams);
if (!availableParams.length) return [];
return availableParams.map((item) => {
return {
label: item.description,
value: item.name,
children: [],
};
});
if (!availableParams.length) return[];
return availableParams
.map((item) => {
return {
label: item.description,
value: item.name,
children: [],
}
});
});
const getAvailableComparisonOptions = computed(() => {
return (paramter: DynamicParamter) => {
const defineParams = unref(defineParamsRef);
const defineParam = defineParams.find((p) => p.name === paramter.field);
const defineParam = defineParams.find(p => p.name === paramter.field);
if (!defineParam) {
return comparisonOptions;
}
@ -289,16 +275,15 @@
return comparisonOptions;
}
//
return comparisonOptions.filter((c) => availableComparator.includes(c.value));
};
return comparisonOptions
.filter(c => availableComparator.includes(c.value));
}
});
const filterOption = (input: string, option: any) => {
if (isNullOrWhiteSpace(option.label) && isNullOrWhiteSpace(option.value)) return false;
return (
option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
);
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0 ;
};
onMounted(fetch);
@ -310,20 +295,18 @@
if (!useAdvancedSearch) return;
if (!defineFieldApi || !isFunction(defineFieldApi)) return;
setLoading(true);
defineFieldApi()
.then((res) => {
let resultItems: DefineParamter[] = [];
if (defineFieldReplace && isFunction(defineFieldReplace)) {
resultItems = defineFieldReplace(res);
} else {
const isArrayResult = Array.isArray(res);
resultItems = isArrayResult ? res : get(res, listField || 'items');
}
defineParamsRef.value = resultItems;
})
.finally(() => {
setLoading(false);
});
defineFieldApi().then((res) => {
let resultItems: DefineParamter[] = [];
if (defineFieldReplace && isFunction(defineFieldReplace)) {
resultItems = defineFieldReplace(res);
} else {
const isArrayResult = Array.isArray(res);
resultItems = isArrayResult ? res : get(res, listField || 'items');
}
defineParamsRef.value = resultItems;
}).finally(() => {
setLoading(false);
});
}
function handleAddField() {
@ -345,14 +328,14 @@
}
function handleDelField(paramter) {
const index = formMdel.paramters.findIndex((p) => p.field === paramter.field);
const index = formMdel.paramters.findIndex(p => p.field === paramter.field);
formMdel.paramters.splice(index, 1);
emits('change', getSearchInput());
}
function handleFieldChange(field, record) {
const defineParams = unref(defineParamsRef);
const defineParam = defineParams.find((dp) => dp.name === field);
const defineParam = defineParams.find(dp => dp.name === field);
if (defineParam) {
record.field = defineParam.name;
record.javaScriptType = defineParam.javaScriptType;
@ -378,7 +361,7 @@
function getSearchInput() {
const searchInput = {
//
paramters: formMdel.paramters.filter((p) => p.value !== undefined),
paramters: formMdel.paramters.filter(p => p.value !== undefined)
};
return searchInput;
}
@ -390,4 +373,6 @@
defineExpose({ resetFields });
</script>
<style lang="less" scoped></style>
<style lang="less" scoped>
</style>

16
apps/vue/src/components/Table/src/components/EditTableHeaderIcon.vue

@ -1,16 +1,16 @@
<template>
<span>
<span class="edit-header-cell">
<slot></slot>
{{ title }}
<FormOutlined />
</span>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { FormOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name: 'EditTableHeaderIcon',
components: { FormOutlined },
props: { title: { type: String, default: '' } },
//defineOptions({ name: 'EditTableHeaderIcon' });
defineProps({
title: { type: String, default: '' },
});
</script>
</script>

41
apps/vue/src/components/Table/src/components/HeaderCell.vue

@ -1,17 +1,11 @@
<template>
<EditTableHeaderCell v-if="getIsEdit">
{{ getTitle }}
</EditTableHeaderCell>
<span v-else>{{ getTitle }}</span>
<BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
</template>
<script lang="ts">
<script lang="tsx">
import type { PropType } from 'vue';
import type { BasicColumn } from '../types/table';
import { defineComponent, computed } from 'vue';
import BasicHelp from '/@/components/Basic/src/BasicHelp.vue';
import EditTableHeaderCell from './EditTableHeaderIcon.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { ColumnType } from 'ant-design-vue/lib/table/interface';
export default defineComponent({
name: 'TableHeaderCell',
@ -21,18 +15,37 @@
},
props: {
column: {
type: Object as PropType<BasicColumn>,
type: Object as PropType<ColumnType<any>>,
default: () => ({}),
},
},
setup(props) {
const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => !!props.column?.edit);
const getTitle = computed(() => props.column?.customTitle || props.column?.title);
const getHelpMessage = computed(() => props.column?.helpMessage);
const getIsEdit = computed(() => !!(props.column as BasicColumn)?.edit);
const getTitle = computed(() => {
const column = props.column as BasicColumn;
if (typeof column.customHeaderRender === 'function') {
return column.customHeaderRender(column);
}
return column?.customTitle || props.column?.title;
});
const getHelpMessage = computed(() => (props.column as BasicColumn)?.helpMessage);
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
return () => {
return (
<div>
{getIsEdit.value ? (
<EditTableHeaderCell>{getTitle.value}</EditTableHeaderCell>
) : (
<span class="default-header-cell">{getTitle.value}</span>
)}
{getHelpMessage.value && (
<BasicHelp text={getHelpMessage.value} class={`${prefixCls}__help`} />
)}
</div>
);
};
},
});
</script>
@ -45,4 +58,4 @@
color: rgb(0 0 0 / 65%) !important;
}
}
</style>
</style>

188
apps/vue/src/components/Table/src/components/TableAction.vue

@ -2,12 +2,12 @@
<div :class="[prefixCls, getAlign]" @click="onCellClick">
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
<Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)">
<PopConfirmButton v-bind="action">
<PopConfirmButton v-bind="omit(action, 'icon')">
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
<template v-if="action.label">{{ action.label }}</template>
</PopConfirmButton>
</Tooltip>
<PopConfirmButton v-else v-bind="action">
<PopConfirmButton v-else v-bind="omit(action, 'icon')">
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
<template v-if="action.label">{{ action.label }}</template>
</PopConfirmButton>
@ -30,11 +30,11 @@
</Dropdown>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, toRaw, unref } from 'vue';
<script lang="ts" setup>
import { PropType, computed, toRaw, unref } from 'vue';
import { MoreOutlined } from '@ant-design/icons-vue';
import { Divider, Tooltip, TooltipProps } from 'ant-design-vue';
import Icon from '/@/components/Icon/index';
import { Icon } from '/@/components/Icon';
import { ActionItem, TableActionType } from '/@/components/Table';
import { PopConfirmButton } from '/@/components/Button';
import { Dropdown } from '/@/components/Dropdown';
@ -44,108 +44,104 @@
import { isBoolean, isFunction, isString } from '/@/utils/is';
import { propTypes } from '/@/utils/propTypes';
import { ACTION_COLUMN_FLAG } from '../const';
import { omit } from 'lodash-es';
export default defineComponent({
name: 'TableAction',
components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined, Tooltip },
props: {
actions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
dropDownActions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
divider: propTypes.bool.def(true),
outside: propTypes.bool,
stopButtonPropagation: propTypes.bool.def(false),
},
setup(props) {
const { prefixCls } = useDesign('basic-table-action');
let table: Partial<TableActionType> = {};
if (!props.outside) {
table = useTableContext();
}
const { hasPermission } = usePermission();
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
//defineOptions({ name: 'TableAction' });
let isIfShow = true;
const props = defineProps({
actions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
dropDownActions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
divider: propTypes.bool.def(true),
outside: propTypes.bool,
stopButtonPropagation: propTypes.bool.def(false),
});
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
const { prefixCls } = useDesign('basic-table-action');
let table: Partial<TableActionType> = {};
if (!props.outside) {
table = useTableContext();
}
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
})
.map((action) => {
const { popConfirm } = action;
return {
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
type: 'link',
size: 'small',
...action,
...(popConfirm || {}),
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
enable: !!popConfirm,
};
});
});
const { hasPermission } = usePermission();
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
const getDropdownList = computed((): any[] => {
const list = (toRaw(props.dropDownActions) || []).filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
});
return list.map((action, index) => {
const { label, popConfirm } = action;
return {
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
text: label,
divider: index < list.length - 1 ? props.divider : false,
};
});
});
let isIfShow = true;
const getAlign = computed(() => {
const columns = (table as TableActionType)?.getColumns?.() || [];
const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
return actionColumn?.align ?? 'left';
});
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
function getTooltip(data: string | TooltipProps): TooltipProps {
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
})
.map((action) => {
const { popConfirm } = action;
return {
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
placement: 'bottom',
...(isString(data) ? { title: data } : data),
getPopupContainer: () => unref((table as any)?.wrapRef) ?? document.body,
type: 'link',
size: 'small',
...action,
...(popConfirm || {}),
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
enable: !!popConfirm,
};
}
});
});
function onCellClick(e: MouseEvent) {
if (!props.stopButtonPropagation) return;
const path = e.composedPath() as HTMLElement[];
const isInButton = path.find((ele) => {
return ele.tagName?.toUpperCase() === 'BUTTON';
});
isInButton && e.stopPropagation();
}
const getDropdownList = computed((): any[] => {
const list = (toRaw(props.dropDownActions) || []).filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
});
return list.map((action, index) => {
const { label, popConfirm } = action;
return {
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
text: label,
divider: index < list.length - 1 ? props.divider : false,
};
});
});
return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip };
},
const getAlign = computed(() => {
const columns = (table as TableActionType)?.getColumns?.() || [];
const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
return actionColumn?.align ?? 'left';
});
function getTooltip(data: string | TooltipProps): TooltipProps {
return {
getPopupContainer: () => unref((table as any)?.wrapRef) ?? document.body,
placement: 'bottom',
...(isString(data) ? { title: data } : data),
};
}
function onCellClick(e: MouseEvent) {
if (!props.stopButtonPropagation) return;
const path = e.composedPath() as HTMLElement[];
const isInButton = path.find((ele) => {
return ele.tagName?.toUpperCase() === 'BUTTON';
});
isInButton && e.stopPropagation();
}
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-action';
@ -199,4 +195,4 @@
}
}
}
</style>
</style>

136
apps/vue/src/components/Table/src/components/TableFooter.vue

@ -1,94 +1,92 @@
<template>
<Table
v-if="summaryFunc || summaryData"
v-if="!!props.summaryFunc || props.summaryData"
:showHeader="false"
:bordered="false"
:pagination="false"
:dataSource="getDataSource"
:rowKey="(r) => r[rowKey]"
:rowKey="props.rowKey"
:columns="getColumns"
tableLayout="fixed"
:scroll="scroll"
:scroll="props.scroll"
/>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, unref, computed, toRaw } from 'vue';
<script lang="ts" setup>
import { unref, computed, toRaw } from 'vue';
import { Table } from 'ant-design-vue';
import { cloneDeep } from 'lodash-es';
import { isFunction } from '/@/utils/is';
import type { BasicColumn } from '../types/table';
import type { BasicColumn, BasicTableProps } from '../types/table';
import { INDEX_COLUMN_FLAG } from '../const';
import { propTypes } from '/@/utils/propTypes';
import { useTableContext } from '../hooks/useTableContext';
import { ColumnType } from 'ant-design-vue/es/table/interface';
import { parseRowKey } from '../helper';
//defineOptions({ name: 'BasicTableFooter' });
const props = withDefaults(
defineProps<{
summaryFunc?: Fn | null;
summaryData?: Recordable[] | null;
scroll?: BasicTableProps['scroll'];
rowKey?: BasicTableProps['rowKey'];
}>(),
{
summaryFunc: null,
summaryData: null,
rowKey: '',
},
);
const SUMMARY_ROW_KEY = '_row';
const SUMMARY_INDEX_KEY = '_index';
export default defineComponent({
name: 'BasicTableFooter',
components: { Table },
props: {
summaryFunc: {
type: Function as PropType<Fn>,
},
summaryData: {
type: Array as PropType<Recordable[]>,
},
scroll: {
type: Object as PropType<Recordable>,
},
rowKey: propTypes.string.def('key'),
},
setup(props) {
const table = useTableContext();
const table = useTableContext();
const getDataSource = computed((): Recordable[] => {
const { summaryFunc, summaryData } = props;
if (summaryData?.length) {
summaryData.forEach((item, i) => (item[props.rowKey] = `${i}`));
return summaryData;
}
if (!isFunction(summaryFunc)) {
return [];
}
let dataSource = toRaw(unref(table.getDataSource()));
dataSource = summaryFunc(dataSource);
dataSource.forEach((item, i) => {
item[props.rowKey] = `${i}`;
});
return dataSource;
const getDataSource = computed((): Recordable[] => {
if (props.summaryData?.length) {
props.summaryData.forEach((item, i) => {
item[parseRowKey(props.rowKey, item)] = `${i}`;
});
return props.summaryData;
}
if (!isFunction(props.summaryFunc)) {
return [];
}
let dataSource = toRaw(unref(table.getDataSource()));
dataSource = props.summaryFunc(dataSource);
dataSource.forEach((item, i) => {
item[parseRowKey(props.rowKey, item)] = `${i}`;
});
return dataSource;
});
const getColumns = computed(() => {
const dataSource = unref(getDataSource);
const columns: BasicColumn[] = cloneDeep(table.getColumns());
const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
const getColumns = computed(() => {
const dataSource = unref(getDataSource);
const columns: BasicColumn[] = cloneDeep(table.getColumns());
const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
if (index !== -1) {
if (hasIndexSummary) {
columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
columns[index].ellipsis = false;
} else {
Reflect.deleteProperty(columns[index], 'customRender');
}
}
if (index !== -1) {
if (hasIndexSummary) {
columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
columns[index].ellipsis = false;
} else {
Reflect.deleteProperty(columns[index], 'customRender');
}
}
if (table.getRowSelection() && hasRowSummary) {
const isFixed = columns.some((col) => col.fixed === 'left');
columns.unshift({
width: 60,
title: 'selection',
key: 'selectionKey',
align: 'center',
...(isFixed ? { fixed: 'left' } : {}),
customRender: ({ record }) => record[SUMMARY_ROW_KEY],
});
}
return columns;
if (table.getRowSelection() && hasRowSummary) {
const isFixed = columns.some((col) => col.fixed === 'left');
columns.unshift({
width: 60,
title: 'selection',
key: 'selectionKey',
align: 'center',
...(isFixed ? { fixed: 'left' } : {}),
customRender: ({ record }) => record[SUMMARY_ROW_KEY],
});
return { getColumns, getDataSource };
},
}
return columns as unknown as ColumnType[];
});
</script>
</script>

86
apps/vue/src/components/Table/src/components/TableHeader.vue

@ -3,6 +3,9 @@
<div v-if="$slots.headerTop" style="margin: 5px">
<slot name="headerTop"></slot>
</div>
<div v-if="showSelectionBar" style="margin: 5px">
<TableSelectionBar :clearSelectedRowKeys="props.clearSelectedRowKeys!" :count="props.count" />
</div>
<div class="flex items-center">
<slot name="tableTitle" v-if="$slots.tableTitle"></slot>
<TableTitle
@ -13,76 +16,67 @@
<div :class="`${prefixCls}__toolbar`">
<slot name="toolbar"></slot>
<Divider type="vertical" v-if="$slots.toolbar && showTableSetting" />
<TableSetting
<TableSettingComponent
:setting="tableSetting"
v-if="showTableSetting"
@columns-change="handleColumnChange"
/>
</div>
</div>
<div v-if="showTableAlert" :class="`${prefixCls}__alert`">
<TableAlert :message="tableAlertMessage" @de-select="$emit('de-select')" />
</div>
</div>
</template>
<script lang="ts">
import type { TableSetting, ColumnChangeParam } from '../types/table';
<script lang="ts" setup>
import type { TableSetting, ColumnChangeParam, TableActionType } from '../types/table';
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import { Divider } from 'ant-design-vue';
import TableSettingComponent from './settings/index.vue';
import TableTitle from './TableTitle.vue';
import TableAlert from './TableAlert.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import TableSelectionBar from '../components/TableSelectionBar.vue';
//defineOptions({ name: 'BasicTableHeader' });
export default defineComponent({
name: 'BasicTableHeader',
components: {
Divider,
TableAlert,
TableTitle,
TableSetting: TableSettingComponent,
const props = defineProps({
title: {
type: [Function, String] as PropType<string | ((data) => string)>,
},
props: {
title: {
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
},
tableSetting: {
type: Object as PropType<TableSetting>,
},
showTableSetting: {
type: Boolean,
},
titleHelpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
showTableAlert: {
type: Boolean,
default: false,
},
tableAlertMessage: {
type: String,
default: '',
},
tableSetting: {
type: Object as PropType<TableSetting>,
},
emits: ['columns-change', 'de-select'],
setup(_, { emit }) {
const { prefixCls } = useDesign('basic-table-header');
function handleColumnChange(data: ColumnChangeParam[]) {
emit('columns-change', data);
}
return { prefixCls, handleColumnChange };
showTableSetting: {
type: Boolean,
},
titleHelpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
//
clearSelectedRowKeys: {
type: Function as PropType<TableActionType['clearSelectedRowKeys']>,
},
count: {
type: Number,
default: 0,
},
showSelectionBar: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['columns-change']);
const { prefixCls } = useDesign('basic-table-header');
function handleColumnChange(data: ColumnChangeParam[]) {
emit('columns-change', data);
}
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header';
.@{prefix-cls} {
&__toolbar {
flex: 1;
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
@ -91,4 +85,4 @@
}
}
}
</style>
</style>

75
apps/vue/src/components/Table/src/components/TableImg.vue

@ -7,67 +7,64 @@
>
<Badge :count="!showBadge || imgList.length == 1 ? 0 : imgList.length" v-if="simpleShow">
<div class="img-div">
<PreviewGroup>
<Image.PreviewGroup>
<template v-for="(img, index) in imgList" :key="img">
<AImage
<Image
:width="size"
:style="{
display: index === 0 ? '' : 'none !important',
}"
:src="srcPrefix + img"
:fallback="fallback"
/>
</template>
</PreviewGroup>
</Image.PreviewGroup>
</div>
</Badge>
<PreviewGroup v-else>
<Image.PreviewGroup v-else>
<template v-for="(img, index) in imgList" :key="img">
<AImage
<Image
:width="size"
:style="{ marginLeft: index === 0 ? 0 : margin + 'px' }"
:src="srcPrefix + img"
:fallback="fallback"
/>
</template>
</PreviewGroup>
</Image.PreviewGroup>
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import type { CSSProperties } from 'vue';
import { defineComponent, computed } from 'vue';
import { computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { Image, Badge } from 'ant-design-vue';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'TableImage',
components: { AImage: Image, PreviewGroup: Image.PreviewGroup, Badge },
props: {
imgList: propTypes.arrayOf(propTypes.string),
size: propTypes.number.def(40),
//
simpleShow: propTypes.bool,
// badge
showBadge: propTypes.bool.def(true),
//
margin: propTypes.number.def(4),
// srcimgList
srcPrefix: propTypes.string.def(''),
// fallback,
fallback: propTypes.string.def(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==',
),
},
setup(props) {
const getWrapStyle = computed((): CSSProperties => {
const { size } = props;
const s = `${size}px`;
return { height: s, width: s };
});
//defineOptions({ name: 'TableImage' });
const { prefixCls } = useDesign('basic-table-img');
return { prefixCls, getWrapStyle };
},
const props = defineProps({
imgList: propTypes.arrayOf(propTypes.string),
size: propTypes.number.def(40),
//
simpleShow: propTypes.bool,
// badge
showBadge: propTypes.bool.def(true),
//
margin: propTypes.number.def(4),
// srcimgList
srcPrefix: propTypes.string.def(''),
// fallback,
fallback: propTypes.string.def(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==',
),
});
const getWrapStyle = computed((): CSSProperties => {
const { size } = props;
const s = `${size}px`;
return { height: s, width: s };
});
const { prefixCls } = useDesign('basic-table-img');
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-img';
@ -84,6 +81,10 @@
.img-div {
display: inline-grid;
> .ant-image:nth-of-type(n + 2) {
display: none;
}
}
}
</style>
</style>

54
apps/vue/src/components/Table/src/components/TableSelectionBar.vue

@ -0,0 +1,54 @@
<template>
<a-alert type="info" showIcon :class="[prefixCls]">
<template #message>
<span v-if="props.count > 0">
{{ t('component.table.selectionBarTips', { count: props.count }) }}
</span>
<span v-else>
{{ t('component.table.selectionBarEmpty') }}
</span>
<a-button type="link" @click="clearSelectedRowKeys" size="small" v-show="props.count > 0">
{{ t('component.table.selectionBarClear') }}
</a-button>
</template>
</a-alert>
</template>
<script lang="ts" setup>
import { useI18n } from '/@/hooks/web/useI18n';
import { useDesign } from '/@/hooks/web/useDesign';
import type { TableActionType } from '../types/table';
import { Alert as AAlert } from 'ant-design-vue';
const { t } = useI18n();
const { prefixCls } = useDesign('table-select-bar');
//defineOptions({ name: 'TableSelectBar' });
const props = withDefaults(
defineProps<{
count?: number;
//
clearSelectedRowKeys: TableActionType['clearSelectedRowKeys'];
}>(),
{
count: () => 0,
},
);
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-table-select-bar';
.@{prefix-cls} {
flex-grow: 1;
padding: 2px 8px;
:deep(.ant-btn-link) {
height: 20px;
line-height: 20px;
}
}
</style>

59
apps/vue/src/components/Table/src/components/TableTitle.vue

@ -3,43 +3,38 @@
{{ getTitle }}
</BasicTitle>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import { BasicTitle } from '/@/components/Basic/index';
<script lang="ts" setup>
import { computed, PropType } from 'vue';
import { BasicTitle } from '/@/components/Basic';
import { useDesign } from '/@/hooks/web/useDesign';
import { isFunction } from '/@/utils/is';
export default defineComponent({
name: 'BasicTableTitle',
components: { BasicTitle },
props: {
title: {
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
},
getSelectRows: {
type: Function as PropType<() => Recordable[]>,
},
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
},
//defineOptions({ name: 'BasicTableTitle' });
const props = defineProps({
title: {
type: [Function, String] as PropType<string | ((data) => string)>,
},
getSelectRows: {
type: Function as PropType<() => any[]>,
},
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
},
setup(props) {
const { prefixCls } = useDesign('basic-table-title');
});
const getTitle = computed(() => {
const { title, getSelectRows = () => {} } = props;
let tit = title;
const { prefixCls } = useDesign('basic-table-title');
if (isFunction(title)) {
tit = title({
selectRows: getSelectRows(),
});
}
return tit;
});
const getTitle = computed(() => {
const { title, getSelectRows = () => {} } = props;
let tit = title;
return { getTitle, prefixCls };
},
if (isFunction(title)) {
tit = title({
selectRows: getSelectRows(),
});
}
return tit;
});
</script>
<style lang="less">
@ -47,7 +42,7 @@
.@{prefix-cls} {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
}
</style>
</style>

96
apps/vue/src/components/Table/src/components/editable/EditableCell.vue

@ -2,7 +2,6 @@
import type { CSSProperties, PropType } from 'vue';
import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue';
import type { BasicColumn } from '../../types/table';
import type { EditRecordRow } from './index';
import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue';
import { CellComponent } from './CellComponent';
@ -14,9 +13,10 @@
import { propTypes } from '/@/utils/propTypes';
import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is';
import { createPlaceholderMessage } from './helper';
import { omit, pick, set } from 'lodash-es';
import { pick, set } from 'lodash-es';
import { treeToList } from '/@/utils/helper/treeHelper';
import { Spin } from 'ant-design-vue';
import { parseRowKey } from '../../helper';
export default defineComponent({
name: 'EditableCell',
@ -26,11 +26,13 @@
},
props: {
value: {
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
type: [String, Number, Boolean, Object] as PropType<
string | number | boolean | Record<string, any>
>,
default: '',
},
record: {
type: Object as PropType<EditRecordRow>,
type: Object as any,
},
column: {
type: Object as PropType<BasicColumn>,
@ -44,7 +46,7 @@
const elRef = ref();
const ruleVisible = ref(false);
const ruleMessage = ref('');
const optionsRef = ref<LabelValueOptions>([]);
const optionsRef = ref([]);
const currentValueRef = ref<any>(props.value);
const defaultValueRef = ref<any>(props.value);
const spinning = ref<boolean>(false);
@ -65,13 +67,23 @@
const getComponentProps = computed(() => {
const isCheckValue = unref(getIsCheckComp);
let compProps = props.column?.editComponentProps ?? ({} as any);
const { checkedValue, unCheckedValue } = compProps;
const valueField = isCheckValue ? 'checked' : 'value';
const val = unref(currentValueRef);
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
let value = val;
if (isCheckValue) {
if (typeof checkedValue !== 'undefined') {
value = val === checkedValue ? checkedValue : unCheckedValue;
} else if (typeof unCheckedValue !== 'undefined') {
value = val === unCheckedValue ? unCheckedValue : checkedValue;
} else {
value = isNumber(val) || isBoolean(val) ? val : !!val;
}
}
let compProps = props.column?.editComponentProps ?? {};
const { record, column, index } = props;
if (isFunction(compProps)) {
@ -83,7 +95,7 @@
delete compProps.onChange;
const component = unref(getComponent);
const apiSelectProps: Recordable = {};
const apiSelectProps: Record<string, any> = {};
if (component === 'ApiSelect') {
apiSelectProps.cache = true;
}
@ -93,8 +105,9 @@
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
placeholder: createPlaceholderMessage(unref(getComponent)),
...apiSelectProps,
...omit(compProps, 'onChange'),
...compProps,
[valueField]: value,
disabled: unref(getDisable),
} as any;
});
function upEditDynamicDisabled(record, column, value) {
@ -112,7 +125,7 @@
}
if (isFunction(editDynamicDisabled)) {
const { record } = props;
disabled = editDynamicDisabled({ record });
disabled = editDynamicDisabled({ record, currentValue: currentValueRef.value });
}
return disabled;
});
@ -130,8 +143,7 @@
return value;
}
const options: LabelValueOptions =
unref(getComponentProps)?.options ?? (unref(optionsRef) || []);
const options = unref(getComponentProps)?.options ?? (unref(optionsRef) || []);
const option = options.find((item) => `${item.value}` === `${value}`);
return option?.label ?? value;
@ -157,7 +169,7 @@
});
watchEffect(() => {
//defaultValueRef.value = props.value;
// defaultValueRef.value = props.value;
currentValueRef.value = props.value;
});
@ -168,8 +180,9 @@
}
});
function handleEdit() {
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
function handleEdit(e) {
e.stopPropagation();
if (unref(getRowEditable) || unref(props.column?.editRow) || unref(getDisable)) return;
ruleMessage.value = '';
isEdit.value = true;
nextTick(() => {
@ -183,11 +196,11 @@
if (!e) {
currentValueRef.value = e;
} else if (component === 'Checkbox') {
currentValueRef.value = (e as ChangeEvent).target.checked;
currentValueRef.value = e.target.checked;
} else if (component === 'Switch') {
currentValueRef.value = e;
} else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
currentValueRef.value = e.target.value;
} else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) {
currentValueRef.value = e;
}
@ -215,8 +228,8 @@
return false;
}
if (isFunction(editRule)) {
const res = await editRule(currentValue, record as Recordable);
if (!!res) {
const res = await editRule(currentValue, record);
if (res) {
ruleMessage.value = res;
ruleVisible.value = true;
return false;
@ -247,17 +260,20 @@
if (!record.editable) {
const { getBindValues } = table;
const { beforeEditSubmit, columns } = unref(getBindValues);
const { beforeEditSubmit, columns, rowKey } = unref(getBindValues);
const rowKeyParsed = parseRowKey(rowKey, record);
if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
spinning.value = true;
const keys: string[] = columns
.map((_column) => _column.dataIndex)
.filter((field) => !!field) as string[];
let result: any = true;
try {
result = await beforeEditSubmit({
record: pick(record, keys),
record: pick(record, [rowKeyParsed, ...keys]),
index,
key: dataKey as string,
value,
@ -272,8 +288,8 @@
}
}
}
set(record, dataKey, value);
defaultValueRef.value = value;
//const record = await table.updateTableData(index, dataKey, value);
needEmit && table.emit?.('edit-end', { record, index, key: dataKey, value });
isEdit.value = false;
@ -315,25 +331,25 @@
}
// only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) {
function handleOptionsChange(options) {
const { replaceFields } = unref(getComponentProps);
const component = unref(getComponent);
if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {};
let listOptions: Recordable[] = treeToList(options, { children });
let listOptions = treeToList(options, { children });
listOptions = listOptions.map((item) => {
return {
label: item[title],
value: item[value],
};
});
optionsRef.value = listOptions as LabelValueOptions;
optionsRef.value = listOptions;
} else {
optionsRef.value = options;
}
}
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle) {
if (props.record) {
/* eslint-disable */
isArray(props.record[cbs])
@ -378,7 +394,6 @@
elRef,
getComponent,
getRule,
getDisable,
onClickOutside,
ruleMessage,
getRuleVisible,
@ -391,6 +406,7 @@
handleEnter,
handleSubmitClick,
spinning,
getDisable,
};
},
render() {
@ -408,14 +424,21 @@
record: this.record as Recordable,
column: this.column,
index: this.index,
currentValue: this.currentValueRef,
})
: this.getValues ?? '\u00A0'}
</div>
{!this.column.editRow && <FormOutlined class={`${this.prefixCls}__normal-icon`} />}
{!this.column.editRow && !this.getDisable && (
<FormOutlined class={`${this.prefixCls}__normal-icon`} />
)}
</div>
{this.isEdit && (
<Spin spinning={this.spinning}>
<div class={`${this.prefixCls}__wrapper`} v-click-outside={this.onClickOutside}>
<Spin spinning={this.spinning} onClick={(e) => e.stopPropagation()}>
<div
class={`${this.prefixCls}__wrapper`}
v-click-outside={this.onClickOutside}
onClick={(e) => e.stopPropagation()}
>
<CellComponent
{...this.getComponentProps}
component={this.getComponent}
@ -476,13 +499,14 @@
.edit-cell-rule-popover {
.ant-popover-inner-content {
padding: 4px 8px;
color: @error-color;
// border: 1px solid @error-color;
border-radius: 2px;
color: @error-color;
}
}
.@{prefix-cls} {
position: relative;
min-height: 24px; //hover
&__wrapper {
display: flex;
@ -506,20 +530,20 @@
.ellipsis-cell {
.cell-content {
overflow-wrap: break-word;
word-break: break-word;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-word;
white-space: nowrap;
overflow-wrap: break-word;
}
}
&__normal {
&-icon {
display: none;
position: absolute;
top: 4px;
right: 0;
display: none;
width: 20px;
cursor: pointer;
}
@ -531,4 +555,4 @@
}
}
}
</style>
</style>

8
apps/vue/src/components/Table/src/components/editable/index.ts

@ -1,6 +1,6 @@
import type { BasicColumn } from '/@/components/Table/src/types/table';
import { h, Ref } from 'vue';
import { h, Ref, toRaw } from 'vue';
import EditableCell from './EditableCell.vue';
import { isArray } from '/@/utils/is';
@ -13,7 +13,7 @@ interface Params {
export function renderEditCell(column: BasicColumn) {
return ({ text: value, record, index }: Params) => {
record.onValid = async () => {
toRaw(record).onValid = async () => {
if (isArray(record?.validCbs)) {
const validFns = (record?.validCbs || []).map((fn) => fn());
const res = await Promise.all(validFns);
@ -23,7 +23,7 @@ export function renderEditCell(column: BasicColumn) {
}
};
record.onEdit = async (edit: boolean, submit = false) => {
toRaw(record).onEdit = async (edit: boolean, submit = false) => {
if (!submit) {
record.editable = edit;
}
@ -65,4 +65,4 @@ export type EditRecordRow<T = Recordable> = Partial<
validCbs: Fn[];
editValueRefs: Recordable<Ref>;
} & T
>;
>;

729
apps/vue/src/components/Table/src/components/settings/ColumnSetting.vue

@ -6,37 +6,38 @@
<Popover
placement="bottomLeft"
trigger="click"
@visible-change="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`"
@open-change="onOpenChange"
:overlayClassName="`${prefixCls}__column-list`"
:getPopupContainer="getPopupContainer"
>
<template #title>
<div :class="`${prefixCls}__popover-title`">
<Checkbox
:indeterminate="indeterminate"
v-model:checked="state.checkAll"
@change="onCheckAllChange"
v-model:checked="isColumnAllSelected"
@change="onColumnAllSelectChange"
>
{{ t('component.table.settingColumnShow') }}
</Checkbox>
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
<Checkbox v-model:checked="isIndexColumnShow" @change="onIndexColumnShowChange">
{{ t('component.table.settingIndexColumnShow') }}
</Checkbox>
<!-- 设置了 rowSelection 才出现 -->
<Checkbox
v-model:checked="checkSelect"
@change="handleSelectCheckChange"
:disabled="!defaultRowSelection"
v-model:checked="isRowSelectionShow"
@change="onRowSelectionShowChange"
v-if="defaultIsRowSelectionShow"
>
{{ t('component.table.settingSelectColumnShow') }}
</Checkbox>
<Checkbox v-model:checked="checkDrag" @change="handleDragChange">
<Checkbox v-model:checked="isAllowResizeColumn" @change="onColumnAllowResizeChange">
{{ t('component.table.settingDragColumnShow') }}
</Checkbox>
<a-button size="small" type="link" @click="reset">
<a-button size="small" type="link" @click="onReset">
{{ t('common.resetText') }}
</a-button>
</div>
@ -44,12 +45,12 @@
<template #content>
<ScrollContainer>
<CheckboxGroup v-model:value="state.checkedList" @change="onChange" ref="columnListRef">
<template v-for="item in plainOptions" :key="item.value">
<div :class="`${prefixCls}__check-item`" v-if="!('ifShow' in item && !item.ifShow)">
<Checkbox.Group v-model:value="columnCheckedOptions" ref="columnOptionsRef">
<template v-for="opt in columnOptions" :key="opt.value">
<div :class="`${prefixCls}__check-item`" :data-no="opt.value">
<DragOutlined class="table-column-drag-icon" />
<Checkbox :value="item.value">
{{ item.label }}
<Checkbox :value="opt.value">
{{ opt.label }}
</Checkbox>
<Tooltip
@ -65,11 +66,11 @@
:class="[
`${prefixCls}__fixed-left`,
{
active: item.fixed === 'left',
disabled: !state.checkedList.includes(item.value),
active: opt.fixed === 'left',
disabled: opt.value ? !columnCheckedOptions.includes(opt.value) : true,
},
]"
@click="handleColumnFixed(item, 'left')"
@click="onColumnFixedChange(opt, 'left')"
/>
</Tooltip>
<Divider type="vertical" />
@ -86,16 +87,16 @@
:class="[
`${prefixCls}__fixed-right`,
{
active: item.fixed === 'right',
disabled: !state.checkedList.includes(item.value),
active: opt.fixed === 'right',
disabled: opt.value ? !columnCheckedOptions.includes(opt.value) : true,
},
]"
@click="handleColumnFixed(item, 'right')"
@click="onColumnFixedChange(opt, 'right')"
/>
</Tooltip>
</div>
</template>
</CheckboxGroup>
</Checkbox.Group>
</ScrollContainer>
</template>
<SettingOutlined />
@ -103,8 +104,8 @@
</Tooltip>
</template>
<script lang="ts" setup>
import type { BasicColumn, ColumnChangeParam } from '../../types/table';
import { useAttrs, ref, reactive, watchEffect, nextTick, unref, computed } from 'vue';
import type { BasicColumn, ColumnOptionsType, ColumnChangeParam } from '../../types/table';
import { ref, nextTick, unref, computed, useAttrs, watch, onMounted } from 'vue';
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
@ -113,269 +114,526 @@
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
import { useDesign } from '/@/hooks/web/useDesign';
// import { useSortable } from '/@/hooks/web/useSortable';
import { isFunction, isNullAndUnDef, isNumber } from '/@/utils/is';
import { isFunction, isNil, isNumber } from '/@/utils/is';
import { getPopupContainer as getParentContainer } from '/@/utils';
import { cloneDeep, omit } from 'lodash-es';
import Sortablejs from 'sortablejs';
import type Sortable from 'sortablejs';
import { INDEX_COLUMN_FLAG } from '/@/components/Table/src/const';
interface State {
checkAll: boolean;
isInit?: boolean;
checkedList: string[];
defaultCheckList: string[];
}
//
import { useTableSettingStore } from '/@/store/modules/tableSetting';
import { useRoute } from 'vue-router';
import { TableRowSelection } from '/@/components/Table/src/types/table';
interface Options {
label: string;
value: string;
fixed?: boolean | 'left' | 'right';
}
const tableSettingStore = useTableSettingStore();
// defineOptions({ name: 'ColumnSetting' });
const emit = defineEmits(['columns-change']);
const CheckboxGroup = Checkbox.Group;
const emits = defineEmits(['columns-change']);
const route = useRoute();
const { t } = useI18n();
const { prefixCls } = useDesign('basic-column-setting');
const attrs = useAttrs();
const table = useTableContext();
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys');
let inited = false;
const cachePlainOptions = ref<Options[]>([]);
const plainOptions = ref<Options[] | any>([]);
const plainSortOptions = ref<Options[]>([]);
const columnListRef = ref<ComponentRef>(null);
const state = reactive<State>({
checkAll: true,
checkedList: [],
defaultCheckList: [],
const props = withDefaults(
defineProps<{
/**
* 是否缓存列的设置
*/
cache?: boolean;
}>(),
{
cache: () => false,
},
);
const getPopupContainer = () => {
return isFunction(attrs.getPopupContainer) ? attrs.getPopupContainer() : getParentContainer();
};
//
let isRestored = false;
let isInnerChange = false;
//
const columnOptions = ref<ColumnOptionsType[]>([]);
const columnOptionsRef = ref(null);
//
const columnCheckedOptions = ref<string[]>([]);
//
watch(columnCheckedOptions, () => {
//
if (isRestored) {
//
columnOptions.value
.filter((o) => columnCheckedOptions.value.includes(o.value))
.forEach((o) => {
o.column.defaultHidden = false;
});
//
columnOptions.value
.filter((o) => !columnCheckedOptions.value.includes(o.value))
.forEach((o) => {
o.column.defaultHidden = true;
o.fixed = undefined;
});
//
isColumnAllSelectedUpdate();
//
tableColumnsUpdate();
//
props.cache && columnOptionsSave();
}
});
const checkIndex = ref(false);
const checkSelect = ref(false);
const checkDrag = ref(false);
const { prefixCls } = useDesign('basic-column-setting');
//
const isColumnAllSelected = ref<boolean>(false);
const onColumnAllSelectChange = () => {
if (columnCheckedOptions.value.length < columnOptions.value.length) {
columnCheckedOptions.value = columnOptions.value.map((o) => o.value);
} else {
columnCheckedOptions.value = [];
}
};
const getValues = computed(() => {
return unref(table?.getBindValues) || {};
//
const indeterminate = computed(() => {
return (
columnCheckedOptions.value.length > 0 &&
columnCheckedOptions.value.length < columnOptions.value.length
);
});
watchEffect(() => {
setTimeout(() => {
const columns = table.getColumns();
if (columns.length && !state.isInit) {
init();
//
const isIndexColumnShow = ref<boolean>(false);
//
const onIndexColumnShowChange = (e: CheckboxChangeEvent) => {
// showIndexColumn
showIndexColumnUpdate(e.target.checked);
// showIndexColumn
props.cache &&
typeof route.name === 'string' &&
tableSettingStore.setShowIndexColumn(route.name, e.target.checked);
};
//
const isRowSelectionShow = ref<boolean>(false);
//
const onRowSelectionShowChange = (e: CheckboxChangeEvent) => {
// showRowSelection
showRowSelectionUpdate(e.target.checked);
// showRowSelection
props.cache &&
typeof route.name === 'string' &&
tableSettingStore.setShowRowSelection(route.name, e.target.checked);
};
//
const isAllowResizeColumn = ref<boolean>(false);
//
const onColumnAllowResizeChange = (e: CheckboxChangeEvent) => {
const columns = getTableColumns();
columns.forEach((col) => {
if (isNumber(col.width)) {
col.resizable = e.target.checked;
}
}, 10);
});
watchEffect(() => {
const values = unref(getValues);
checkIndex.value = !!values.showIndexColumn;
checkSelect.value = !!values.rowSelection;
});
function getColumns() {
const ret: Options[] = [];
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
ret.push({
label: (item.title as string) || (item.customTitle as string),
value: (item.dataIndex || item.title) as string,
...item,
});
});
return ret;
props.cache &&
typeof route.name === 'string' &&
tableSettingStore.setAllowResizeColumn(route.name, e.target.checked);
}
function init() {
const columns = getColumns();
//
const columnOptionsSave = () => {
if (typeof route.name === 'string') {
// name keykey
tableSettingStore.setColumns(route.name, columnOptions.value);
}
};
//
const onReset = () => {
//
isIndexColumnShow.value = defaultIsIndexColumnShow;
//
onIndexColumnShowChange({
target: { checked: defaultIsIndexColumnShow },
} as CheckboxChangeEvent);
//
isRowSelectionShow.value = defaultIsRowSelectionShow;
//
onRowSelectionShowChange({
target: { checked: defaultIsRowSelectionShow },
} as CheckboxChangeEvent);
//
columnOptions.value = cloneDeep(defaultColumnOptions);
//
formUpdate();
};
// fixed
const onColumnFixedChange = (opt: ColumnOptionsType, type: 'left' | 'right') => {
if (type === 'left') {
if (!opt.fixed || opt.fixed === 'right') {
opt.fixed = 'left';
} else {
opt.fixed = undefined;
}
} else if (type === 'right') {
if (!opt.fixed || opt.fixed === 'left') {
opt.fixed = 'right';
} else {
opt.fixed = undefined;
}
}
const checkList = table
.getColumns({ ignoreAction: true, ignoreIndex: true })
.map((item) => {
if (item.defaultHidden) {
return '';
}
return item.dataIndex || item.title;
})
.filter(Boolean) as string[];
if (!plainOptions.value.length) {
plainOptions.value = columns;
plainSortOptions.value = columns;
cachePlainOptions.value = columns;
state.defaultCheckList = checkList;
} else {
// const fixedColumns = columns.filter((item) =>
// Reflect.has(item, 'fixed')
// ) as BasicColumn[];
unref(plainOptions).forEach((item: BasicColumn) => {
const findItem = columns.find((col: BasicColumn) => col.dataIndex === item.dataIndex);
if (findItem) {
item.fixed = findItem.fixed;
//
tableColumnsUpdate();
//
props.cache && columnOptionsSave();
};
// 沿
const sortableFix = async () => {
// Sortablejsbugel appendchildNodechildNode
//
if (columnOptionsRef.value) {
const el = (columnOptionsRef.value as InstanceType<typeof Checkbox.Group>).$el;
Array.from(el.children).forEach((item) => el.removeChild(item));
}
await nextTick();
};
//
const columnIfShow = (column?: Partial<Omit<BasicColumn, 'children'>>) => {
if (column) {
if ('ifShow' in column) {
if (typeof column.ifShow === 'boolean') {
return column.ifShow;
} else if (column.ifShow) {
return column.ifShow(column);
}
});
}
return true;
}
return false;
};
//
const getTableColumns = () => {
return table
.getColumns({ ignoreIndex: true, ignoreAction: true })
.filter((col) => columnIfShow(col));
};
//
const tableColumnsSet = (columns: BasicColumn[]) => {
isInnerChange = true;
table?.setColumns(columns);
// 沿
const columnChangeParams: ColumnChangeParam[] = columns.map((col) => ({
dataIndex: col.dataIndex ? col.dataIndex.toString() : '',
fixed: col.fixed,
visible: !col.defaultHidden,
}));
emit('columns-change', columnChangeParams);
};
//
const tableColumnsUpdate = () => {
//
const columns = cloneDeep(table.getColumns());
// fixed
let count = columns.filter(
(o) => o.flag !== INDEX_COLUMN_FLAG && (o.fixed === 'left' || o.fixed === true),
).length;
//
if (isIndexColumnShow.value) {
count++;
}
state.isInit = true;
state.checkedList = checkList;
}
// checkAll change
function onCheckAllChange(e: CheckboxChangeEvent) {
const checkList = plainOptions.value.map((item) => item.value);
if (e.target.checked) {
state.checkedList = checkList;
setColumns(checkList);
} else {
state.checkedList = [];
setColumns([]);
// columnOptions table.getColumns()
for (const opt of columnOptions.value) {
const colIdx = columns.findIndex((o) => o.dataIndex === opt.value);
//
if (colIdx > -1) {
const target = columns[colIdx];
target.defaultHidden = opt.column?.defaultHidden;
target.fixed = opt.fixed;
columns.splice(colIdx, 1);
columns.splice(count++, 0, target); //
}
}
}
const indeterminate = computed(() => {
const len = plainOptions.value.length;
let checkedLen = state.checkedList.length;
unref(checkIndex) && checkedLen--;
return checkedLen > 0 && checkedLen < len;
});
// action
const actionIndex = columns.findIndex((o) => o.dataIndex === 'action');
if (actionIndex > -1) {
const actionCol = columns.splice(actionIndex, 1);
columns.push(actionCol[0]);
}
// Trigger when check/uncheck a column
function onChange(checkedList: string[]) {
const len = plainSortOptions.value.length;
state.checkAll = checkedList.length === len;
const sortList = unref(plainSortOptions).map((item) => item.value);
checkedList.sort((prev, next) => {
return sortList.indexOf(prev) - sortList.indexOf(next);
});
setColumns(checkedList);
}
//
tableColumnsSet(columns);
};
let sortable: Sortable;
let sortableOrder: string[] = [];
// reset columns
function reset() {
state.checkedList = [...state.defaultCheckList];
state.checkAll = true;
plainOptions.value = unref(cachePlainOptions);
plainSortOptions.value = unref(cachePlainOptions);
setColumns(table.getCacheColumns());
sortable.sort(sortableOrder);
}
//
const onOpenChange = async () => {
await nextTick();
// Open the pop-up window for drag and drop initialization
function handleVisibleChange() {
if (inited) return;
nextTick(() => {
const columnListEl = unref(columnListRef);
if (!columnListEl) return;
const el = columnListEl.$el as any;
if (!el) return;
// Drag and drop sort
sortable = Sortablejs.create(unref(el), {
if (columnOptionsRef.value) {
//
const el = (columnOptionsRef.value as InstanceType<typeof Checkbox.Group>).$el;
Sortablejs.create(unref(el), {
animation: 500,
delay: 400,
delayOnTouchOnly: true,
handle: '.table-column-drag-icon ',
dataIdAttr: 'data-no',
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
if (isNil(oldIndex) || isNil(newIndex) || oldIndex === newIndex) {
return;
}
// Sort column
const columns = cloneDeep(plainSortOptions.value);
const options = cloneDeep(columnOptions.value);
//
if (oldIndex > newIndex) {
columns.splice(newIndex, 0, columns[oldIndex]);
columns.splice(oldIndex + 1, 1);
options.splice(newIndex, 0, options[oldIndex]);
options.splice(oldIndex + 1, 1);
} else {
columns.splice(newIndex + 1, 0, columns[oldIndex]);
columns.splice(oldIndex, 1);
options.splice(newIndex + 1, 0, options[oldIndex]);
options.splice(oldIndex, 1);
}
plainSortOptions.value = columns;
// fix: ColumnSettingbug (#1931)
// https://github.com/vbenjs/vue-vben-admin/commit/50468e9581c93e95df21447bec30b6148541c46b
setColumns(
columns
.map((col: Options) => col.value)
.filter((value: string) => state.checkedList.includes(value)),
);
//
columnOptions.value = options;
//
tableColumnsUpdate();
//
props.cache && columnOptionsSave();
},
});
// order
sortableOrder = sortable.toArray();
inited = true;
});
}
}
};
// removepush
const diff = () => {
if (typeof route.name === 'string') {
let cache = tableSettingStore.getColumns(route.name);
if (cache) {
// valuelabel
if (
JSON.stringify(columnOptions.value.map((o) => ({ value: o.value, label: o.label }))) !==
JSON.stringify(cache.map((o) => ({ value: o.value, label: o.label })))
) {
const map = columnOptions.value.reduce((map, item) => {
map[item.value] = item.label;
return map;
}, {});
if (Array.isArray(cache)) {
// remove
cache = cache.filter((o) => map[o.value]);
// label
cache.forEach((o) => {
o.label = map[o.value];
});
const cacheKeys = cache.map((o) => o.value);
// push
cache = cache.concat(columnOptions.value.filter((o) => !cacheKeys.includes(o.value)));
//
tableSettingStore.setColumns(route.name, cache);
}
}
}
}
};
//
const restore = () => {
if (typeof route.name === 'string') {
const isIndexColumnShowCache = tableSettingStore.getShowIndexColumn(route.name);
//
if (typeof isIndexColumnShowCache === 'boolean') {
isIndexColumnShow.value = defaultIsIndexColumnShow && isIndexColumnShowCache;
}
// Control whether the serial number column is displayed
function handleIndexCheckChange(e: CheckboxChangeEvent) {
const isRowSelectionShowCache = tableSettingStore.getShowRowSelection(route.name);
//
if (typeof isRowSelectionShowCache === 'boolean') {
isRowSelectionShow.value = defaultIsRowSelectionShow && isRowSelectionShowCache;
}
const allowResizeColumnCache = tableSettingStore.getAllowResizeColumn(route.name);
//
if (typeof allowResizeColumnCache === 'boolean') {
isAllowResizeColumn.value = defaultIsAllowResizeColumn && allowResizeColumnCache;
}
}
//
onIndexColumnShowChange({
target: { checked: isIndexColumnShow.value },
} as CheckboxChangeEvent);
//
onRowSelectionShowChange({
target: { checked: isRowSelectionShow.value },
} as CheckboxChangeEvent);
if (typeof route.name === 'string') {
const cache = tableSettingStore.getColumns(route.name);
//
if (Array.isArray(cache)) {
columnOptions.value = cache;
}
}
};
//
const columnCheckedOptionsUpdate = () => {
columnCheckedOptions.value = columnOptions.value
.filter((o) => !o.column?.defaultHidden)
.map((o) => o.value);
};
//
const isColumnAllSelectedUpdate = () => {
isColumnAllSelected.value = columnOptions.value.length === columnCheckedOptions.value.length;
};
// showIndexColumn
const showIndexColumnUpdate = (showIndexColumn) => {
isInnerChange = true;
table.setProps({
showIndexColumn: e.target.checked,
showIndexColumn,
});
}
// Control whether the check box is displayed
function handleSelectCheckChange(e: CheckboxChangeEvent) {
};
// rowSelection
const showRowSelectionUpdate = (showRowSelection) => {
isInnerChange = true;
table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined,
rowSelection: showRowSelection
? {
...omit(defaultRowSelection, ['selectedRowKeys']),
fixed: true,
}
: undefined,
});
}
function handleDragChange(e: CheckboxChangeEvent) {
const columns = getColumns() as BasicColumn[];
columns.forEach((col) => {
if (isNumber(col.width)) {
col.resizable = e.target.checked;
};
//
const formUpdate = () => {
//
columnCheckedOptionsUpdate();
//
isColumnAllSelectedUpdate();
// showIndexColumn
showIndexColumnUpdate(isIndexColumnShow.value);
// showRowSelection
showRowSelectionUpdate(isRowSelectionShow.value);
//
tableColumnsUpdate();
};
//
let defaultIsIndexColumnShow: boolean = false;
let defaultIsRowSelectionShow: boolean = false;
let defaultIsAllowResizeColumn: boolean = false;
let defaultRowSelection: TableRowSelection<Recordable<any>>;
let defaultColumnOptions: ColumnOptionsType[] = [];
const init = async () => {
if (!isRestored) {
//
const columns = getTableColumns();
// 沿
table.setCacheColumns?.(columns);
//
const options: ColumnOptionsType[] = [];
for (const col of columns) {
// string
options.push({
label:
typeof col.title === 'string'
? col.title
: col.customTitle === 'string'
? col.customTitle
: '',
value:
typeof col.dataIndex === 'string'
? col.dataIndex
: col.title === 'string'
? col.title
: '',
column: {
defaultHidden: col.defaultHidden,
},
fixed: col.fixed,
});
}
});
setColumns(columns);
}
function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
if (!state.checkedList.includes(item.dataIndex as string)) return;
//
defaultIsIndexColumnShow = table.getBindValues.value.showIndexColumn || false;
defaultRowSelection = table.getRowSelection();
defaultIsRowSelectionShow = !!defaultRowSelection; // rowSelection
defaultColumnOptions = options;
const columns = getColumns().filter((c: BasicColumn) =>
state.checkedList.includes(c.dataIndex as string),
) as BasicColumn[];
const isFixed = item.fixed === fixed ? false : fixed;
const index = columns.findIndex((col) => col.dataIndex === item.dataIndex);
if (index !== -1) {
columns[index].fixed = isFixed;
}
item.fixed = isFixed;
//
isIndexColumnShow.value = defaultIsIndexColumnShow;
isRowSelectionShow.value = defaultIsRowSelectionShow;
columnOptions.value = cloneDeep(options);
if (isFixed && !item.width) {
item.width = 100;
}
table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed });
setColumns(columns);
}
// removepush
props.cache && diff();
function setColumns(columns: BasicColumn[] | string[]) {
table.setColumns(columns);
const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
const visible =
columns.findIndex(
(c: BasicColumn | string) =>
c === col.value || (typeof c !== 'string' && c.dataIndex === col.value),
) !== -1;
return { dataIndex: col.value, fixed: col.fixed, visible };
});
//
props.cache && restore();
emits('columns-change', data);
}
//
formUpdate();
function getPopupContainer() {
return isFunction(attrs.getPopupContainer) ? attrs.getPopupContainer() : getParentContainer();
}
isRestored = true;
}
};
//
const once = async () => {
//
await sortableFix();
init();
};
once();
//
const getColumns = computed(() => {
return table?.getColumns();
});
const getValues = computed(() => {
return table?.getBindValues;
});
onMounted(() => {
watch([getColumns, getValues], () => {
if (!isInnerChange) {
isRestored = false;
console.log('onMounted isRestored');
init();
} else {
isInnerChange = false;
}
});
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-column-setting';
@ -387,8 +645,8 @@
.@{prefix-cls} {
&__popover-title {
position: relative;
display: flex;
position: relative;
align-items: center;
justify-content: space-between;
}
@ -428,7 +686,7 @@
transform: rotate(180deg);
}
&__cloumn-list {
&__column-list {
svg {
width: 1em !important;
height: 1em !important;
@ -442,6 +700,7 @@
}
.ant-checkbox-group {
display: inline-block;
width: 100%;
min-width: 260px;
// flex-wrap: wrap;
@ -452,4 +711,4 @@
}
}
}
</style>
</style>

28
apps/vue/src/components/Table/src/components/settings/FullScreenSetting.vue

@ -7,32 +7,16 @@
<FullscreenExitOutlined @click="toggle" v-else />
</Tooltip>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { Tooltip } from 'ant-design-vue';
import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
import { useFullscreen } from '@vueuse/core';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
export default defineComponent({
name: 'FullScreenSetting',
components: {
FullscreenExitOutlined,
FullscreenOutlined,
Tooltip,
},
// defineOptions({ name: 'FullScreenSetting' });
setup() {
const table = useTableContext();
const { t } = useI18n();
const { toggle, isFullscreen } = useFullscreen(table.wrapRef);
return {
toggle,
isFullscreen,
t,
};
},
});
</script>
const table = useTableContext();
const { t } = useI18n();
const { toggle, isFullscreen } = useFullscreen(table.wrapRef);
</script>

26
apps/vue/src/components/Table/src/components/settings/RedoSetting.vue

@ -6,28 +6,18 @@
<RedoOutlined @click="redo" />
</Tooltip>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script lang="ts" setup>
import { Tooltip } from 'ant-design-vue';
import { RedoOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
export default defineComponent({
name: 'RedoSetting',
components: {
RedoOutlined,
Tooltip,
},
setup() {
const table = useTableContext();
const { t } = useI18n();
// defineOptions({ name: 'RedoSetting' });
function redo() {
table.reload();
}
const table = useTableContext();
const { t } = useI18n();
return { redo, t };
},
});
</script>
function redo() {
table.reload();
}
</script>

69
apps/vue/src/components/Table/src/components/settings/SizeSetting.vue

@ -8,57 +8,54 @@
<ColumnHeightOutlined />
<template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
<MenuItem key="default">
<Menu.Item key="default">
<span>{{ t('component.table.settingDensDefault') }}</span>
</MenuItem>
<MenuItem key="middle">
</Menu.Item>
<Menu.Item key="middle">
<span>{{ t('component.table.settingDensMiddle') }}</span>
</MenuItem>
<MenuItem key="small">
</Menu.Item>
<Menu.Item key="small">
<span>{{ t('component.table.settingDensSmall') }}</span>
</MenuItem>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</Tooltip>
</template>
<script lang="ts">
<script lang="ts" setup>
import type { SizeType } from '../../types/table';
import { defineComponent, ref } from 'vue';
import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
import { ref, onMounted } from 'vue';
import { Tooltip, Dropdown, Menu, type MenuProps } from 'ant-design-vue';
import { ColumnHeightOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
import { getPopupContainer } from '/@/utils';
export default defineComponent({
name: 'SizeSetting',
components: {
ColumnHeightOutlined,
Tooltip,
Dropdown,
Menu,
MenuItem: Menu.Item,
},
setup() {
const table = useTableContext();
const { t } = useI18n();
import { useTableSettingStore } from '/@/store/modules/tableSetting';
const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
const tableSettingStore = useTableSettingStore();
function handleTitleClick({ key }: { key: SizeType }) {
selectedKeysRef.value = [key];
table.setProps({
size: key,
});
}
//defineOptions({ name: 'SizeSetting' });
return {
handleTitleClick,
selectedKeysRef,
getPopupContainer,
t,
};
},
const table = useTableContext();
const { t } = useI18n();
const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
const handleTitleClick: MenuProps['onClick'] = ({ key }) => {
selectedKeysRef.value = [key as SizeType];
tableSettingStore.setTableSize(key as SizeType);
table.setProps({
size: key as SizeType,
});
};
onMounted(() => {
selectedKeysRef.value = [tableSettingStore.getTableSize];
table.setProps({
size: selectedKeysRef.value[0],
});
});
</script>
</script>

41
apps/vue/src/components/Table/src/components/settings/TableExport.vue

@ -4,7 +4,7 @@
<span>{{ t('common.export') }}</span>
</template>
<DownloadOutlined @click="exportData" />
<ExpExcelModal @register="registerModal" @success="handleExport" />
<ExpExcelModal @register="registerModal" @success="handleExport" />
</Tooltip>
</template>
<script lang="ts" setup>
@ -15,6 +15,7 @@
import { ExpExcelModal, jsonToSheetXlsx } from '/@/components/Excel';
import { useModal } from '/@/components/Modal';
import { isString } from '/@/utils/is';
import { BasicColumn } from '../../types/table';
const table = useTableContext();
const { t } = useI18n();
@ -33,32 +34,38 @@
}
}
function fillDataRows(columns: BasicColumn[], data: any, rows: any[]) {
const row: {[key:string]: string} = {};
columns.forEach((col) => {
const colName = String(col.dataIndex);
if (Reflect.has(data, colName)) {
row[colName] = data[colName];
}
});
if (Object.keys(row).length > 0) {
rows.push(row);
}
if (Reflect.has(data, 'children') && Array.isArray(data.children)) {
data.children.forEach((d) => {
fillDataRows(columns, d, rows);
});
}
}
function exportDataToExcel(options) {
const dataSource = table.getDataSource();
//
const columns = table
.getColumns()
const columns = table.getColumns()
.filter((col) => !col.flag || col.flag === 'DEFAULT')
.filter((col) => isString(col.title) && isString(col.dataIndex));
//
const header: { [key: string]: string } = {};
const header: {[key:string]: string} = {};
columns.forEach((col) => {
header[String(col.dataIndex)] = String(col.title);
});
//
const rows: { [key: string]: string }[] = [];
dataSource.forEach((data) => {
const row: { [key: string]: string } = {};
columns.forEach((col) => {
const colName = String(col.dataIndex);
if (Reflect.has(data, colName)) {
row[colName] = data[colName];
}
});
if (Object.keys(row).length > 0) {
rows.push(row);
}
});
const rows: {[key:string]: string}[] = [];
dataSource.forEach((data) => fillDataRows(columns, data, rows));
// excel
jsonToSheetXlsx({
data: rows,

72
apps/vue/src/components/Table/src/components/settings/index.vue

@ -7,64 +7,54 @@
v-if="getSetting.setting"
@columns-change="handleColumnChange"
:getPopupContainer="getTableContainer"
:cache="getSetting.settingCache"
/>
<FullScreenSetting v-if="getSetting.fullScreen" :getPopupContainer="getTableContainer" />
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import type { PropType } from 'vue';
import type { TableSetting, ColumnChangeParam } from '../../types/table';
import { defineComponent, computed, unref } from 'vue';
import { computed, unref } from 'vue';
import ColumnSetting from './ColumnSetting.vue';
import TableExport from './TableExport.vue';
import SizeSetting from './SizeSetting.vue';
import RedoSetting from './RedoSetting.vue';
import FullScreenSetting from './FullScreenSetting.vue';
import TableExport from './TableExport.vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
export default defineComponent({
name: 'TableSetting',
components: {
ColumnSetting,
SizeSetting,
RedoSetting,
FullScreenSetting,
TableExport,
},
props: {
setting: {
type: Object as PropType<TableSetting>,
default: () => ({}),
},
},
emits: ['columns-change'],
setup(props, { emit }) {
const { t } = useI18n();
const table = useTableContext();
//defineOptions({ name: 'TableSetting' });
const getSetting = computed((): TableSetting => {
return {
redo: true,
size: true,
setting: true,
fullScreen: false,
export: false,
...props.setting,
};
});
const props = defineProps({
setting: {
type: Object as PropType<TableSetting>,
default: () => ({}),
},
});
function handleColumnChange(data: ColumnChangeParam[]) {
emit('columns-change', data);
}
const emit = defineEmits(['columns-change']);
function getTableContainer() {
return table ? unref(table.wrapRef) : document.body;
}
const table = useTableContext();
return { getSetting, t, handleColumnChange, getTableContainer };
},
const getSetting = computed((): TableSetting => {
return {
redo: true,
size: true,
setting: true,
export: false,
settingCache: false,
fullScreen: false,
...props.setting,
};
});
function handleColumnChange(data: ColumnChangeParam[]) {
emit('columns-change', data);
}
function getTableContainer() {
return table ? unref(table.wrapRef) : document.body;
}
</script>
<style lang="less">
.table-settings {

28
apps/vue/src/components/Table/src/helper.ts

@ -0,0 +1,28 @@
import { ROW_KEY } from './const';
import type { BasicTableProps } from './types/table';
export function parseRowKey<RecordType = any>(
rowKey: BasicTableProps['rowKey'],
record: RecordType,
autoCreateKey?: boolean,
): number | string {
if (autoCreateKey) {
return ROW_KEY;
} else {
if (typeof rowKey === 'string') {
return rowKey;
} else if (rowKey) {
return rowKey(record);
} else {
return ROW_KEY;
}
}
}
export function parseRowKeyValue<RecordType = any>(
rowKey: BasicTableProps['rowKey'],
record: RecordType,
autoCreateKey?: boolean,
): number | string {
return record[parseRowKey(rowKey, record, autoCreateKey)];
}

84
apps/vue/src/components/Table/src/hooks/useColumns.ts

@ -9,13 +9,14 @@ import { isArray, isBoolean, isFunction, isMap, isString } from '/@/utils/is';
import { cloneDeep, isEqual } from 'lodash-es';
import { formatToDate } from '/@/utils/dateUtil';
import { ACTION_COLUMN_FLAG, DEFAULT_ALIGN, INDEX_COLUMN_FLAG, PAGE_SIZE } from '../const';
import { ColumnType } from 'ant-design-vue/es/table';
function handleItem(item: BasicColumn, ellipsis: boolean) {
const { key, dataIndex, children } = item;
item.align = item.align || DEFAULT_ALIGN;
if (ellipsis) {
if (!key) {
item.key = dataIndex;
item.key = typeof dataIndex == 'object' ? dataIndex.join('-') : dataIndex;
}
if (!isBoolean(item.ellipsis)) {
Object.assign(item, {
@ -65,7 +66,7 @@ function handleIndexColumn(
columns.unshift({
flag: INDEX_COLUMN_FLAG,
width: 50,
width: 60,
title: t('component.table.index'),
align: 'center',
customRender: ({ index }) => {
@ -146,31 +147,36 @@ export function useColumns(
const getViewColumns = computed(() => {
const viewColumns = sortFixedColumn(unref(getColumnsRef));
const mapFn = (column) => {
const { slots, customRender, format, edit, editRow, flag } = column;
if (!slots || !slots?.title) {
column.customTitle = column.title;
}
const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
if (!customRender && format && !edit && !isDefaultAction) {
column.customRender = ({ text, record, index }) => {
return formatCell(text, format, record, index);
};
}
// edit table
if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column);
}
return reactive(column);
};
const columns = cloneDeep(viewColumns);
return columns
.filter((column) => {
return hasPermission(column.auth) && isIfShow(column);
})
.filter((column) => hasPermission(column.auth) && isIfShow(column))
.map((column) => {
const { slots, customRender, format, edit, editRow, flag } = column;
if (!slots || !slots?.title) {
// column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title;
Reflect.deleteProperty(column, 'title');
}
const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
if (!customRender && format && !edit && !isDefaultAction) {
column.customRender = ({ text, record, index }) => {
return formatCell(text, format, record, index);
};
// Support table multiple header editable
if (column.children?.length) {
column.children = column.children.map(mapFn);
}
// edit table
if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column);
}
return reactive(column);
return mapFn(column);
});
});
@ -253,14 +259,26 @@ export function useColumns(
function getCacheColumns() {
return cacheColumns;
}
function setCacheColumns(columns: BasicColumn[]) {
if (!isArray(columns)) return;
cacheColumns = columns.filter((item) => !item.flag);
}
/**
*
*/
function setColumnWidth(w: number, col: ColumnType<BasicColumn>) {
col.width = w;
}
return {
getColumnsRef,
getCacheColumns,
getColumns,
setColumns,
setColumnWidth,
getViewColumns,
setCacheColumnsByField,
setCacheColumns,
};
}
@ -279,9 +297,23 @@ function sortFixedColumn(columns: BasicColumn[]) {
}
defColumns.push(column);
}
return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(
(item) => !item.defaultHidden,
);
// 筛选逻辑
const filterFunc = (item) => !item.defaultHidden;
// 筛选首层显示列(1级表头)
const viewColumns = [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(filterFunc);
// 筛选>=2级表头(深度优先)
const list = [...viewColumns];
while (list.length) {
const current = list[0];
if (Array.isArray(current.children)) {
current.children = current.children.filter(filterFunc);
list.shift();
list.unshift(...current.children);
} else {
list.shift();
}
}
return viewColumns;
}
// format cell
@ -314,4 +346,4 @@ export function formatCell(text: string, format: CellFormat, record: Recordable,
} catch (error) {
return text;
}
}
}

55
apps/vue/src/components/Table/src/hooks/useCustomRow.ts

@ -1,34 +1,18 @@
import type { ComputedRef } from 'vue';
import type { BasicTableProps } from '../types/table';
import { unref } from 'vue';
import { ROW_KEY } from '../const';
import { isString, isFunction } from '/@/utils/is';
import type { Key } from 'ant-design-vue/lib/table/interface';
import { parseRowKeyValue } from '../helper';
interface Options {
setSelectedRowKeys: (keys: string[]) => void;
getSelectRowKeys: () => string[];
setSelectedRowKeys: (keyValues: Key[]) => void;
getSelectRowKeys: () => Key[];
clearSelectedRowKeys: () => void;
emit: EmitType;
getAutoCreateKey: ComputedRef<boolean | undefined>;
}
function getKey(
record: Recordable,
rowKey: string | ((record: Record<string, any>) => string) | undefined,
autoCreateKey?: boolean,
) {
if (!rowKey || autoCreateKey) {
return record[ROW_KEY];
}
if (isString(rowKey)) {
return record[rowKey];
}
if (isFunction(rowKey)) {
return record[rowKey(record)];
}
return null;
}
export function useCustomRow(
propsRef: ComputedRef<BasicTableProps>,
{ setSelectedRowKeys, getSelectRowKeys, getAutoCreateKey, clearSelectedRowKeys, emit }: Options,
@ -40,37 +24,38 @@ export function useCustomRow(
function handleClick() {
const { rowSelection, rowKey, clickToRowSelect } = unref(propsRef);
if (!rowSelection || !clickToRowSelect) return;
const keys = getSelectRowKeys() || [];
const key = getKey(record, rowKey, unref(getAutoCreateKey));
if (!key) return;
const keyValues = getSelectRowKeys() || [];
const keyValue = parseRowKeyValue(rowKey, record, unref(getAutoCreateKey));
if (!keyValue) return;
const isCheckbox = rowSelection.type === 'checkbox';
if (isCheckbox) {
// 找到tr
const tr: HTMLElement = (e as MouseEvent)
const tr = (e as MouseEvent)
.composedPath?.()
.find((dom: HTMLElement) => dom.tagName === 'TR') as HTMLElement;
.find((dom) => (dom as HTMLElement).tagName === 'TR') as HTMLElement;
if (!tr) return;
// 找到Checkbox,检查是否为disabled
const checkBox = tr.querySelector('input[type=checkbox]');
if (!checkBox || checkBox.hasAttribute('disabled')) return;
if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key]);
if (!keyValues.includes(keyValue)) {
keyValues.push(keyValue);
setSelectedRowKeys(keyValues);
return;
}
const keyIndex = keys.findIndex((item) => item === key);
keys.splice(keyIndex, 1);
setSelectedRowKeys(keys);
const keyIndex = keyValues.findIndex((item) => item === keyValue);
keyValues.splice(keyIndex, 1);
setSelectedRowKeys(keyValues);
return;
}
const isRadio = rowSelection.type === 'radio';
if (isRadio) {
if (!keys.includes(key)) {
if (keys.length) {
if (!keyValues.includes(keyValue)) {
if (keyValues.length) {
clearSelectedRowKeys();
}
setSelectedRowKeys([key]);
setSelectedRowKeys([keyValue]);
return;
}
clearSelectedRowKeys();
@ -97,4 +82,4 @@ export function useCustomRow(
return {
customRow,
};
}
}

90
apps/vue/src/components/Table/src/hooks/useDataSource.ts

@ -16,6 +16,8 @@ import { buildUUID } from '/@/utils/uuid';
import { isFunction, isBoolean, isObject } from '/@/utils/is';
import { get, cloneDeep, merge } from 'lodash-es';
import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const';
import { parseRowKeyValue } from '../helper';
import type { Key } from 'ant-design-vue/lib/table/interface';
interface ActionType {
getPaginationInfo: ComputedRef<boolean | PaginationProps>;
@ -69,10 +71,7 @@ export function useDataSource(
filters: Partial<Recordable<string[]>>,
sorter: SorterResult,
) {
const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef);
if (clearSelectOnPageChange) {
clearSelectedRowKeys();
}
const { sortFn, filterFn } = unref(propsRef);
setPagination(pagination);
const params: Recordable = {};
@ -138,7 +137,7 @@ export function useDataSource(
return unref(dataSourceRef);
});
async function updateTableData(index: number, key: string, value: any) {
async function updateTableData(index: number, key: Key, value: any) {
const record = dataSourceRef.value[index];
if (record) {
dataSourceRef.value[index][key] = value;
@ -146,11 +145,8 @@ export function useDataSource(
return dataSourceRef.value[index];
}
function updateTableDataRecord(
rowKey: string | number,
record: Recordable,
): Recordable | undefined {
const row = findTableDataRecord(rowKey);
function updateTableDataRecord(keyValue: Key, record: Recordable): Recordable | undefined {
const row = findTableDataRecord(keyValue);
if (row) {
for (const field in row) {
@ -160,34 +156,28 @@ export function useDataSource(
}
}
function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
function deleteTableDataRecord(keyValues: Key | Key[]) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const rowKeyName = unref(getRowKey);
if (!rowKeyName) return;
const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
const delKeyValues = !Array.isArray(keyValues) ? [keyValues] : keyValues;
function deleteRow(data?: Recordable<any>, key?: string | number | string[] | number[]) {
const row: { index: number; data: [] } = findRow(data, key);
function deleteRow(data, keyValue) {
const row: { index: number; data: [] } = findRow(data, keyValue);
if (row === null || row.index === -1) {
return;
}
row.data.splice(row.index, 1);
function findRow(data, key) {
function findRow(data, keyValue) {
if (data === null || data === undefined) {
return null;
}
for (let i = 0; i < data.length; i++) {
const row = data[i];
let targetKeyName: string = rowKeyName as string;
if (isFunction(rowKeyName)) {
targetKeyName = rowKeyName(row);
}
if (row[targetKeyName] === key) {
if (parseRowKeyValue(unref(getRowKey), row) === keyValue) {
return { index: i, data };
}
if (row.children?.length > 0) {
const result = findRow(row.children, key);
const result = findRow(row.children, keyValue);
if (result != null) {
return result;
}
@ -197,16 +187,19 @@ export function useDataSource(
}
}
for (const key of rowKeys) {
deleteRow(dataSourceRef.value, key);
deleteRow(unref(propsRef).dataSource, key);
for (const keyValue of delKeyValues) {
deleteRow(dataSourceRef.value, keyValue);
deleteRow(unref(propsRef).dataSource, keyValue);
}
setPagination({
total: unref(propsRef).dataSource?.length,
});
}
function insertTableDataRecord(record: Recordable | Recordable[], index: number): Recordable[] | undefined {
function insertTableDataRecord(
record: Recordable | Recordable[],
index?: number,
): Recordable[] | undefined {
// if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
index = index ?? dataSourceRef.value?.length;
const _record = isObject(record) ? [record as Recordable] : (record as Recordable[]);
@ -214,40 +207,22 @@ export function useDataSource(
return unref(dataSourceRef);
}
function findTableDataRecord(rowKey: string | number) {
function findTableDataRecord(keyValue: Key) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const rowKeyName = unref(getRowKey);
if (!rowKeyName) return;
const { childrenColumnName = 'children' } = unref(propsRef);
const findRow = (array: any[]) => {
let ret;
array.some(function iter(r) {
if (typeof rowKeyName === 'function') {
if ((rowKeyName(r) as string) === rowKey) {
ret = r;
return true;
}
} else {
if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) {
ret = r;
return true;
}
if (parseRowKeyValue(unref(getRowKey), r) === keyValue) {
ret = r;
return true;
}
return r[childrenColumnName] && r[childrenColumnName].some(iter);
});
return ret;
};
// const row = dataSourceRef.value.find(r => {
// if (typeof rowKeyName === 'function') {
// return (rowKeyName(r) as string) === rowKey
// } else {
// return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey
// }
// })
return findRow(dataSourceRef.value);
}
@ -257,12 +232,13 @@ export function useDataSource(
searchInfo,
defSort,
fetchSetting,
useSearchForm,
beforeFetch,
beforeResponse,
afterFetch,
useSearchForm,
pagination,
advancedSearchConfig,
clearSelectOnPageChange,
} = unref(propsRef);
let fetchApi = api;
// 高级查询条件支持
@ -275,6 +251,9 @@ export function useDataSource(
}
if (!fetchApi || !isFunction(fetchApi)) return;
try {
if (clearSelectOnPageChange) {
clearSelectedRowKeys();
}
setLoading(true);
const { pageField, sizeField, listField, totalField } = Object.assign(
{},
@ -310,7 +289,6 @@ export function useDataSource(
}
let res = await fetchApi(params);
// 增加用户自定义的返回数据处理函数
if (beforeResponse && isFunction(beforeResponse)) {
res = beforeResponse(res);
@ -321,10 +299,10 @@ export function useDataSource(
const isArrayResult = Array.isArray(res);
let resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
const resultTotal: number = isArrayResult ? res.length : Number(get(res, totalField));
const resultTotal: number = isArrayResult ? res.length : get(res, totalField);
// 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行
if (resultTotal) {
if (Number(resultTotal)) {
const currentTotalPage = Math.ceil(resultTotal / pageSize);
if (current > currentTotalPage) {
setPagination({
@ -362,8 +340,8 @@ export function useDataSource(
}
}
function setTableData(values: any[]) {
dataSourceRef.value = values;
function setTableData<T = Recordable>(values: T[]) {
dataSourceRef.value = values as Recordable[];
}
function getDataSource<T = Recordable>() {
@ -400,4 +378,4 @@ export function useDataSource(
findTableDataRecord,
handleTableChange,
};
}
}

91
apps/vue/src/components/Table/src/hooks/useRowSelection.ts

@ -4,13 +4,15 @@ import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from '
import { ROW_KEY } from '../const';
import { omit } from 'lodash-es';
import { findNodeAll } from '/@/utils/helper/treeHelper';
import type { Key } from 'ant-design-vue/lib/table/interface';
import { parseRowKey, parseRowKeyValue } from '../helper';
export function useRowSelection(
propsRef: ComputedRef<BasicTableProps>,
tableData: Ref<Recordable[]>,
emit: EmitType,
) {
const selectedRowKeysRef = ref<string[]>([]);
const selectedRowKeysRef = ref<Key[]>([]);
const selectedRowRef = ref<Recordable[]>([]);
const getRowSelectionRef = computed((): TableRowSelection | null => {
@ -21,8 +23,55 @@ export function useRowSelection(
return {
selectedRowKeys: unref(selectedRowKeysRef),
onChange: (selectedRowKeys: string[]) => {
setSelectedRowKeys(selectedRowKeys);
onChange: (selectedRowKeys: Key[], selectedRows: any[], isClickCustomRow?: boolean) => {
if (isClickCustomRow) {
// 点击行触发
// 维持外部定义的 onChange 回调
rowSelection.onChange?.(selectedRowKeys, selectedRows);
} else {
// 点击 checkbox/radiobox 触发
// 取出【当前页】所有 keyValues
const currentPageKeys = tableData.value.map((o) => parseRowKeyValue(unref(getRowKey), o));
// 从【所有分页】已选的 keyValues,且属于【当前页】的部分
for (const selectedKey of selectedRowKeysRef.value.filter((k) =>
currentPageKeys.includes(k),
)) {
// 判断是否已经不存在于【当前页】
if (selectedRowKeys.findIndex((k) => k === selectedKey) < 0) {
// 不存在 = 取消勾选
const removeIndex = selectedRowKeysRef.value.findIndex((k) => k === selectedKey);
if (removeIndex > -1) {
// 取消勾选
selectedRowKeysRef.value.splice(removeIndex, 1);
selectedRowRef.value.splice(removeIndex, 1);
}
}
}
// 存在于【当前页】,但不存在于【所有分页】,则认为是新增的
for (const selectedKey of selectedRowKeys) {
const existIndex = selectedRowKeysRef.value.findIndex((k) => k === selectedKey);
if (existIndex < 0) {
// 新增勾选
selectedRowKeysRef.value.push(selectedKey);
const record = selectedRows.find(
(o) => parseRowKeyValue(unref(getRowKey), o) === selectedKey,
);
if (record) {
selectedRowRef.value.push(record);
}
}
}
// 赋值调整过的值
setSelectedRowKeys(selectedRowKeysRef.value);
// 维持外部定义的onChange回调
rowSelection.onChange?.(selectedRowKeysRef.value, selectedRowRef.value);
}
},
...omit(rowSelection, ['onChange']),
};
@ -30,7 +79,7 @@ export function useRowSelection(
watch(
() => unref(propsRef).rowSelection?.selectedRowKeys,
(v: string[]) => {
(v?: Key[]) => {
setSelectedRowKeys(v);
},
);
@ -42,7 +91,7 @@ export function useRowSelection(
const { rowSelection } = unref(propsRef);
if (rowSelection) {
const { onChange } = rowSelection;
if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows());
if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows(), true);
}
emit('selection-change', {
keys: getSelectRowKeys(),
@ -62,25 +111,39 @@ export function useRowSelection(
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
function setSelectedRowKeys(rowKeys: string[]) {
selectedRowKeysRef.value = rowKeys;
function setSelectedRowKeys(keyValues?: Key[]) {
selectedRowKeysRef.value = keyValues || [];
const rows = toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef)));
const allSelectedRows = findNodeAll(
toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))),
(item) => rowKeys?.includes(item[unref(getRowKey) as string]),
rows,
(item) => keyValues?.includes(parseRowKeyValue(unref(getRowKey), item)),
{
children: propsRef.value.childrenColumnName ?? 'children',
},
);
const trueSelectedRows: any[] = [];
rowKeys?.forEach((key: string) => {
const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key);
found && trueSelectedRows.push(found);
keyValues?.forEach((keyValue: Key) => {
const found = allSelectedRows.find(
(item) => parseRowKeyValue(unref(getRowKey), item) === keyValue,
);
if (found) {
trueSelectedRows.push(found);
} else {
// 跨页的时候,非本页数据无法得到,暂如此处理
// tableData or selectedRowRef 总有数据
if (rows[0]) {
trueSelectedRows.push({ [parseRowKey(unref(getRowKey), rows[0])]: keyValue });
}
}
});
selectedRowRef.value = trueSelectedRows;
}
function setSelectedRows(rows: Recordable[]) {
selectedRowRef.value = rows;
selectedRowKeysRef.value = selectedRowRef.value.map((o) =>
parseRowKeyValue(unref(getRowKey), o),
);
}
function clearSelectedRowKeys() {
@ -88,7 +151,7 @@ export function useRowSelection(
selectedRowKeysRef.value = [];
}
function deleteSelectRowByKey(key: string) {
function deleteSelectRowByKey(key: Key) {
const selectedRowKeys = unref(selectedRowKeysRef);
const index = selectedRowKeys.findIndex((item) => item === key);
if (index !== -1) {
@ -119,4 +182,4 @@ export function useRowSelection(
deleteSelectRowByKey,
setSelectedRows,
};
}
}

34
apps/vue/src/components/Table/src/hooks/useTable.ts

@ -7,6 +7,7 @@ import { getDynamicProps } from '/@/utils';
import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
import type { Key } from 'ant-design-vue/lib/table/interface';
type Props = Partial<DynamicProps<BasicTableProps>>;
@ -92,7 +93,7 @@ export function useTable(tableProps?: Props): [
const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
return toRaw(columns);
},
setColumns: (columns: BasicColumn[]) => {
setColumns: (columns: BasicColumn[] | string[]) => {
getTableInstance().setColumns(columns);
},
setTableData: (values: any[]) => {
@ -101,8 +102,8 @@ export function useTable(tableProps?: Props): [
setPagination: (info: Partial<PaginationProps>) => {
return getTableInstance().setPagination(info);
},
deleteSelectRowByKey: (key: string) => {
getTableInstance().deleteSelectRowByKey(key);
deleteSelectRowByKey: (keyValue: Key) => {
getTableInstance().deleteSelectRowByKey(keyValue);
},
getSelectRowKeys: () => {
return toRaw(getTableInstance().getSelectRowKeys());
@ -113,8 +114,8 @@ export function useTable(tableProps?: Props): [
clearSelectedRowKeys: () => {
getTableInstance().clearSelectedRowKeys();
},
setSelectedRowKeys: (keys: string[] | number[]) => {
getTableInstance().setSelectedRowKeys(keys);
setSelectedRowKeys: (keyValues: Key[]) => {
getTableInstance().setSelectedRowKeys(keyValues);
},
getPaginationRef: () => {
return getTableInstance().getPaginationRef();
@ -125,17 +126,17 @@ export function useTable(tableProps?: Props): [
updateTableData: (index: number, key: string, value: any) => {
return getTableInstance().updateTableData(index, key, value);
},
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
return getTableInstance().deleteTableDataRecord(rowKey);
deleteTableDataRecord: (keyValues: Key | Key[]) => {
return getTableInstance().deleteTableDataRecord(keyValues);
},
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
return getTableInstance().insertTableDataRecord(record, index);
},
updateTableDataRecord: (rowKey: string | number, record: Recordable) => {
return getTableInstance().updateTableDataRecord(rowKey, record);
updateTableDataRecord: (keyValue: Key, record: Recordable) => {
return getTableInstance().updateTableDataRecord(keyValue, record);
},
findTableDataRecord: (rowKey: string | number) => {
return getTableInstance().findTableDataRecord(rowKey);
findTableDataRecord: (keyValue: Key) => {
return getTableInstance().findTableDataRecord(keyValue);
},
getRowSelection: () => {
return toRaw(getTableInstance().getRowSelection());
@ -155,16 +156,19 @@ export function useTable(tableProps?: Props): [
expandAll: () => {
getTableInstance().expandAll();
},
expandRows: (keys: string[]) => {
getTableInstance().expandRows(keys);
},
collapseAll: () => {
getTableInstance().collapseAll();
},
expandRows: (keyValues: Key[]) => {
getTableInstance().expandRows(keyValues);
},
collapseRows: (keyValues: Key[]) => {
getTableInstance().collapseRows(keyValues);
},
scrollTo: (pos: string) => {
getTableInstance().scrollTo(pos);
},
};
return [register, methods];
}
}

109
apps/vue/src/components/Table/src/hooks/useTableExpand.ts

@ -1,14 +1,16 @@
import type { ComputedRef, Ref } from 'vue';
import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, toRaw } from 'vue';
import { computed, unref, ref, toRaw, nextTick } from 'vue';
import { ROW_KEY } from '../const';
import { parseRowKeyValue } from '../helper';
import type { Key } from 'ant-design-vue/lib/table/interface';
export function useTableExpand(
propsRef: ComputedRef<BasicTableProps>,
tableData: Ref<Recordable[]>,
emit: EmitType,
) {
const expandedRowKeys = ref<string[]>([]);
const expandedRowKeys = ref<Key[]>([]);
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
@ -20,46 +22,111 @@ export function useTableExpand(
});
const getExpandOption = computed(() => {
const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return {};
const { isTreeTable, expandRowByClick } = unref(propsRef);
if (!isTreeTable && !expandRowByClick) return {};
return {
expandedRowKeys: unref(expandedRowKeys),
onExpandedRowsChange: (keys: string[]) => {
expandedRowKeys.value = keys;
emit('expanded-rows-change', keys);
onExpandedRowsChange: (keyValues: string[]) => {
expandedRowKeys.value = keyValues;
emit('expanded-rows-change', keyValues);
},
};
});
function expandAll() {
const keys = getAllKeys();
expandedRowKeys.value = keys;
const keyValues = getAllKeys();
expandedRowKeys.value = keyValues;
}
function expandRows(keys: string[]) {
function collapseAll() {
expandedRowKeys.value = [];
}
function expandRows(keyValues: Key[]) {
// use row ID expands the specified table row
const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return;
expandedRowKeys.value = [...expandedRowKeys.value, ...keys];
const { isTreeTable, expandRowByClick } = unref(propsRef);
if (!isTreeTable && !expandRowByClick) return;
expandedRowKeys.value = [...expandedRowKeys.value, ...keyValues];
}
function collapseRows(keyValues: Key[]) {
// use row ID collapses the specified table row
const { isTreeTable, expandRowByClick } = unref(propsRef);
if (!isTreeTable && !expandRowByClick) return;
expandedRowKeys.value = unref(expandedRowKeys).filter(
(keyValue) => !keyValues.includes(keyValue),
);
}
function getAllKeys(data?: Recordable[]) {
const keys: string[] = [];
const keyValues: Array<number | string> = [];
const { childrenColumnName } = unref(propsRef);
toRaw(data || unref(tableData)).forEach((item) => {
keys.push(item[unref(getRowKey) as string]);
keyValues.push(parseRowKeyValue(unref(getRowKey), item));
const children = item[childrenColumnName || 'children'];
if (children?.length) {
keys.push(...getAllKeys(children));
keyValues.push(...getAllKeys(children));
}
});
return keys;
return keyValues;
}
function collapseAll() {
expandedRowKeys.value = [];
// 获取展开路径 keyValues
function getKeyPaths(
records: Recordable[],
childrenColumnName: string,
keyValue: Key,
paths: Array<Key>,
): boolean {
if (
records.findIndex((record) => parseRowKeyValue(unref(getRowKey), record) === keyValue) > -1
) {
paths.push(keyValue);
return true;
} else {
for (const record of records) {
const children = record[childrenColumnName];
if (Array.isArray(children) && getKeyPaths(children, childrenColumnName, keyValue, paths)) {
paths.push(parseRowKeyValue(unref(getRowKey), record));
return true;
}
}
}
return false;
}
// 手风琴展开
function expandRowAccordion(keyValue: Key) {
const { childrenColumnName } = unref(propsRef);
const paths: Array<Key> = [];
getKeyPaths(tableData.value, childrenColumnName || 'children', keyValue, paths);
expandedRowKeys.value = paths;
}
// 监听展开事件,用于支持手风琴展开效果
function handleTableExpand(expanded, record) {
// 手风琴开关
// isTreeTable 或 expandRowByClick 时支持
// 展开操作
if (
propsRef.value.accordion &&
(propsRef.value.isTreeTable || propsRef.value.expandRowByClick) &&
expanded
) {
nextTick(() => {
expandRowAccordion(parseRowKeyValue(unref(getRowKey), record));
});
}
}
return { getExpandOption, expandAll, expandRows, collapseAll };
}
return {
getExpandOption,
expandAll,
collapseAll,
expandRows,
collapseRows,
expandRowAccordion,
handleTableExpand,
};
}

12
apps/vue/src/components/Table/src/hooks/useTableFooter.ts

@ -6,11 +6,7 @@ import { useEventListener } from '/@/hooks/event/useEventListener';
export function useTableFooter(
propsRef: ComputedRef<BasicTableProps>,
scrollRef: ComputedRef<{
x: string | number | true;
y: string | number | null;
scrollToFirstRowOnChange: boolean;
}>,
scrollRef: ComputedRef<BasicTableProps['scroll']>,
tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable>,
) {
@ -36,13 +32,13 @@ export function useTableFooter(
nextTick(() => {
const tableEl = unref(tableElRef);
if (!tableEl) return;
const bodyDom = tableEl.$el.querySelector('.ant-table-content');
const bodyDom = tableEl.$el.querySelector(' .ant-table-content, .ant-table-body');
useEventListener({
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-content',
'.ant-table-footer .ant-table-container [class^="ant-table-"]',
) as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
@ -53,4 +49,4 @@ export function useTableFooter(
});
}
return { getFooterProps };
}
}

2
apps/vue/src/components/Table/src/hooks/useTableForm.ts

@ -9,7 +9,7 @@ import { isFunction } from '/@/utils/is';
export function useTableForm(
propsRef: ComputedRef<BasicTableProps>,
slots: Slots,
fetch: (opt?: FetchParams | undefined) => Promise<Recordable<any>[] | undefined | void>,
fetch: (opt?: FetchParams) => Promise<Recordable<any>[] | undefined>,
getLoading: ComputedRef<boolean | undefined>,
setFieldsValue: (values: Recordable) => Promise<void>,
) {

5
apps/vue/src/components/Table/src/hooks/useTableHeader.ts

@ -1,5 +1,5 @@
import type { ComputedRef, Slots } from 'vue';
import type { BasicTableProps, InnerHandlers } from '../types/table';
import type { BasicTableProps, InnerHandlers, InnerMethods } from '../types/table';
import { unref, computed, h } from 'vue';
import TableHeader from '../components/TableHeader.vue';
import { isString } from '/@/utils/is';
@ -9,6 +9,7 @@ export function useTableHeader(
propsRef: ComputedRef<BasicTableProps>,
slots: Slots,
handlers: InnerHandlers,
methods: InnerMethods,
useTableAlert?: ComputedRef<Boolean>,
tableAlertMessage?: ComputedRef<String>,
) {
@ -33,6 +34,8 @@ export function useTableHeader(
showTableAlert: unref(useTableAlert),
tableAlertMessage: unref(tableAlertMessage),
onColumnsChange: handlers.onColumnsChange,
clearSelectedRowKeys: methods.clearSelectedRowKeys,
count: methods.getSelectRowKeys().length,
onDeSelect: handlers.deSelect,
} as Recordable,
{

273
apps/vue/src/components/Table/src/hooks/useTableScroll.ts

@ -1,12 +1,20 @@
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table';
import { Ref, ComputedRef, ref } from 'vue';
import { computed, unref, nextTick, watch } from 'vue';
import { Ref, ComputedRef, ref, computed, unref, nextTick, watch } from 'vue';
import { getViewportOffset } from '/@/utils/domUtils';
import { isBoolean } from '/@/utils/is';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { useModalContext } from '/@/components/Modal';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import { useDebounceFn } from '@vueuse/core';
import { useModalContext } from '/@/components/Modal';
import { useDebounceFn, promiseTimeout } from '@vueuse/core';
import {
footerHeight as layoutFooterHeight,
layoutMultipleHeadePlaceholderTime,
} from '/@/settings/designSetting';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
const { getShowFooter, getFullContent } = useRootSetting();
export function useTableScroll(
propsRef: ComputedRef<BasicTableProps>,
@ -29,7 +37,7 @@ export function useTableScroll(
});
watch(
() => [unref(getCanResize), unref(getDataSourceRef)?.length],
() => [unref(getCanResize), unref(getDataSourceRef)?.length, unref(getShowFooter)],
() => {
debounceRedoHeight();
},
@ -38,6 +46,18 @@ export function useTableScroll(
},
);
watch(
() => [unref(getFullContent)],
async () => {
// 等待动画结束后200毫秒
await promiseTimeout(layoutMultipleHeadePlaceholderTime * 1000 + 200);
debounceRedoHeight();
},
{
flush: 'post',
},
);
function redoHeight() {
nextTick(() => {
calcTableHeight();
@ -55,22 +75,13 @@ export function useTableScroll(
let footerEl: HTMLElement | null;
let bodyEl: HTMLElement | null;
async function calcTableHeight() {
const { resizeHeightOffset, pagination, maxHeight, isCanResizeParent, useSearchForm } =
unref(propsRef);
const tableData = unref(getDataSourceRef);
const table = unref(tableElRef);
if (!table) return;
const tableEl: Element = table.$el;
if (!tableEl) return;
if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body');
if (!bodyEl) return;
}
/**
* table wrapper padding
* @description .vben-basic-table .ant-table-wrapper
*/
const tableWrapperPadding = 6;
function handleScrollBar(bodyEl: HTMLElement, tableEl: Element) {
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
@ -87,35 +98,41 @@ export function useTableScroll(
} else {
!tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x');
}
}
bodyEl!.style.height = 'unset';
if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return;
await nextTick();
// Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
const headEl = tableEl.querySelector('.ant-table-thead ');
/**
*
* @param tableEl table element
* @returns number
*/
function caclPaginationHeight(tableEl: Element): number {
const { pagination } = unref(propsRef);
if (!headEl) return;
// Table height from bottom height-custom offset
let paddingHeight = 32;
// Pager height
let paginationHeight = 2;
let paginationHeight = 0;
if (!isBoolean(pagination)) {
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
// 从 Dom 获取
if (!paginationEl) {
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
}
if (paginationEl) {
// 分页 margin-top
const paginationElMarginTop = parseInt(getComputedStyle(paginationEl).marginTop);
// 分页高度
const offsetHeight = paginationEl.offsetHeight;
paginationHeight += offsetHeight || 0;
paginationHeight = offsetHeight + paginationElMarginTop;
} else {
// TODO First fix 24
paginationHeight += 24;
// 找不到分页组件,缺省给予默认分页 margin-top + 高度
paginationHeight = 10 + 24;
}
} else {
paginationHeight = -8;
// 不显示分页,pagination 为 false 的时候
paginationHeight = 0;
}
return paginationHeight;
}
function caclFooterHeight(tableEl: Element): number {
const { pagination } = unref(propsRef);
let footerHeight = 0;
if (!isBoolean(pagination)) {
if (!footerEl) {
@ -125,48 +142,172 @@ export function useTableScroll(
footerHeight += offsetHeight || 0;
}
}
return footerHeight;
}
function calcHeaderHeight(headEl: Element): number {
let headerHeight = 0;
if (headEl) {
headerHeight = (headEl as HTMLElement).offsetHeight;
}
return headerHeight;
}
/**
* body底部的总高度
* @param tableEl table element
* @param headEl table element
* @returns number
*/
function calcBottomAndPaddingHeight(tableEl: Element, headEl: Element) {
const { isCanResizeParent } = unref(propsRef);
let bottomIncludeBody = 0;
if (unref(wrapRef) && isCanResizeParent) {
const tablePadding = 12;
const formMargin = 16;
let paginationMargin = 10;
// 继承父元素高度
const wrapHeight = unref(wrapRef)?.offsetHeight ?? 0;
let formHeight = unref(formRef)?.$el.offsetHeight ?? 0;
if (formHeight) {
formHeight += formMargin;
}
if (isBoolean(pagination) && !pagination) {
paginationMargin = 0;
// 来自于 .vben-basic-table-form-container .ant-form 以及 .vben-basic-table-form-container
formHeight += 16 + 16 * 2;
}
if (isBoolean(useSearchForm) && !useSearchForm) {
paddingHeight = 0;
}
const headerCellHeight =
(tableEl.querySelector('.ant-table-title') as HTMLElement)?.offsetHeight ?? 0;
console.log(wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin);
bottomIncludeBody =
wrapHeight - formHeight - headerCellHeight - tablePadding - paginationMargin;
bottomIncludeBody = wrapHeight - tableWrapperPadding - formHeight;
} else {
// Table height from bottom
// 缺省 wrapRef 情况下
bottomIncludeBody = getViewportOffset(headEl).bottomIncludeBody;
}
let height =
return bottomIncludeBody;
}
/**
* table modal modal
* @param tableEl table element
* @returns number
*/
function calcModalHeight(tableEl: Element) {
// 找一下 table 是否在 modal 内,获得 modal、wrap、footer,并考虑 fullscreen 的情况
let modalEl: Nullable<HTMLElement> = null;
let modalWrapEl: Nullable<HTMLElement> = null;
let modalFooterEl: Nullable<HTMLElement> = null;
let modalElIterator: HTMLElement = tableEl.parentElement!;
let modalIsFullscreen = false;
while (modalElIterator !== document.body) {
if (modalElIterator.classList.contains('ant-modal')) {
modalEl = modalElIterator;
modalWrapEl = modalEl.parentElement;
modalFooterEl = modalElIterator.querySelector('.ant-modal-content>.ant-modal-footer');
modalIsFullscreen = modalWrapEl?.classList.contains('fullscreen-modal') ?? false;
break;
}
modalElIterator = modalElIterator.parentElement!;
}
if (modalEl) {
// table 在 modal 内
// modal top
const { top: modalTop = 0 } = modalEl ? getViewportOffset(modalEl) : {};
// 来自于 .ant-modal,非全屏为 24,全屏为 0
const modalBottom = modalIsFullscreen ? 0 : 24;
// modal footer 高度
const modalFooterHeight = modalFooterEl?.offsetHeight ?? 0;
// modal footer 边距,来自于 .ant-modal .ant-modal-footer
const modalFooterMarginTop = modalFooterEl
? modalIsFullscreen
? 0
: parseInt(getComputedStyle(modalFooterEl).marginTop)
: 0;
// 来自于 .ant-modal .ant-modal-body > .scrollbar
const modalScrollBarHeight = 14;
return (
(modalTop > modalBottom ? modalTop : modalBottom) +
modalFooterHeight +
modalFooterMarginTop +
modalScrollBarHeight
);
}
// table 不住 modal 内
return 0;
}
/**
*
* @returns number
*/
function getMarginPaddingHeight() {
const { isCanResizeParent } = unref(propsRef);
if (unref(wrapRef) && isCanResizeParent) {
// 继承父元素高度
return tableWrapperPadding;
}
return (
tableWrapperPadding + 16 // 来自于 .vben-basic-table-form-container 或是 .p-4
);
}
async function calcTableHeight() {
const { resizeHeightOffset, maxHeight } = unref(propsRef);
const tableData = unref(getDataSourceRef);
const table = unref(tableElRef);
if (!table) return;
const tableEl: Element = table.$el;
if (!tableEl) return;
if (!bodyEl) {
bodyEl = tableEl.querySelector('.ant-table-body');
if (!bodyEl) return;
}
handleScrollBar(bodyEl, tableEl);
bodyEl!.style.height = 'unset';
if (!unref(getCanResize) || !unref(tableData) || tableData.length === 0) return;
await nextTick();
// Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
const headEl = tableEl.querySelector('.ant-table-thead ');
if (!headEl) return;
const paginationHeight = caclPaginationHeight(tableEl);
const footerHeight = caclFooterHeight(tableEl);
const headerHeight = calcHeaderHeight(headEl);
const bottomIncludeBody = calcBottomAndPaddingHeight(tableEl, headEl);
const modalHeight = calcModalHeight(tableEl);
const marginPaddingHeight = getMarginPaddingHeight();
// Math.floor 宁愿小1px,也不溢出
let height = Math.floor(
bottomIncludeBody -
(resizeHeightOffset || 0) -
paddingHeight -
paginationHeight -
footerHeight -
headerHeight;
(resizeHeightOffset || 0) -
paginationHeight -
footerHeight -
headerHeight -
// 弹窗(如果有)相关高度
modalHeight -
// 页面 footer 高度(非弹窗的时候)
(getShowFooter.value && modalHeight <= 0 ? layoutFooterHeight : 0) -
// 样式间距高度
marginPaddingHeight -
// 预留非整数高度溢出(如实际高度为100.5,offsetHeight 的值为101)
1,
);
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
setHeight(height);
@ -193,7 +334,9 @@ export function useTableScroll(
columns.forEach((item) => {
width += Number.parseFloat(item.width as string) || 0;
});
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
const unsetWidthColumns = columns.filter(
(item) => !Reflect.has(item, 'width') && item.ifShow !== false,
);
const len = unsetWidthColumns.length;
if (len !== 0) {
@ -213,8 +356,8 @@ export function useTableScroll(
y: canResize ? tableHeight : null,
scrollToFirstRowOnChange: false,
...scroll,
};
} as BasicTableProps['scroll'];
});
return { getScrollRef, redoHeight };
}
}

79
apps/vue/src/components/Table/src/types/table.ts

@ -1,13 +1,20 @@
import type { VNodeChild } from 'vue';
import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form';
import type { ColumnProps } from 'ant-design-vue/lib/table';
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type {
TableRowSelection as ITableRowSelection,
Key,
} from 'ant-design-vue/lib/table/interface';
import type { AdvanceSearchProps } from './advancedSearch';
import type { ColumnProps } from 'ant-design-vue/lib/table';
import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes';
import { RoleEnum } from '/@/enums/roleEnum';
import { FixedType } from 'ant-design-vue/es/vc-table/interface';
import AntDesignVueTable from 'ant-design-vue/es/table';
export declare type SortOrder = 'ascend' | 'descend';
@ -18,9 +25,12 @@ export interface TableCurrentDataSource<T = Recordable> {
export interface TableRowSelection<T = any> extends ITableRowSelection {
/**
* Callback executed when selected rows change
* @type Function
* @param selectedRowKeys keyValues
* @param selectedRows records
* @param isClickCustomRow checkbox/radiobox
* @returns void
*/
onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any;
onChange?: (selectedRowKeys: Key[], selectedRows: T[], isClickCustomRow?: boolean) => void;
/**
* Callback executed when select/deselect one row
@ -38,7 +48,7 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
* Callback executed when row selection is inverted
* @type Function
*/
onSelectInvert?: (selectedRows: string[] | number[]) => any;
onSelectInvert?: (selectedRows: Key[]) => any;
}
export interface TableCustomRecord<T> {
@ -84,22 +94,23 @@ export interface GetColumnsParams {
export type SizeType = 'default' | 'middle' | 'small' | 'large';
export interface TableActionType {
reload: (opt?: FetchParams) => Promise<Recordable<any>[] | undefined | void>;
reload: (opt?: FetchParams) => Promise<Recordable<any>[] | undefined>;
setSelectedRows: (rows: Recordable[]) => void;
getSelectRows: <T = Recordable>() => T[];
clearSelectedRowKeys: () => void;
expandAll: () => void;
expandRows: (keys: string[] | number[]) => void;
collapseAll: () => void;
expandRows: (keyValues: Key[]) => void;
collapseRows: (keyValues: Key[]) => void;
scrollTo: (pos: string) => void; // pos: id | "top" | "bottom"
getSelectRowKeys: () => string[];
deleteSelectRowByKey: (key: string) => void;
getSelectRowKeys: () => Key[];
deleteSelectRowByKey: (keyValue: Key) => void;
setPagination: (info: Partial<PaginationProps>) => void;
setTableData: <T = Recordable>(values: T[]) => void;
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void;
updateTableDataRecord: (keyValue: Key, record: Recordable) => Recordable | void;
deleteTableDataRecord: (keyValues: Key | Key[]) => void;
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => Recordable[] | void;
findTableDataRecord: (rowKey: string | number) => Recordable | void;
findTableDataRecord: (keyValue: Key) => Recordable | void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void;
getDataSource: <T = Recordable>() => T[];
@ -107,7 +118,7 @@ export interface TableActionType {
setLoading: (loading: boolean) => void;
setProps: (props: Partial<BasicTableProps>) => void;
redoHeight: () => void;
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
setSelectedRowKeys: (keyValues: Key[]) => void;
getPaginationRef: () => PaginationProps | boolean;
getSize: () => SizeType;
getRowSelection: () => TableRowSelection<Recordable>;
@ -117,6 +128,7 @@ export interface TableActionType {
setShowPagination: (show: boolean) => Promise<void>;
getShowPagination: () => boolean;
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
setCacheColumns?: (columns: BasicColumn[]) => void;
}
export interface FetchSetting {
@ -133,15 +145,17 @@ export interface FetchSetting {
export interface TableSetting {
redo?: boolean;
size?: boolean;
export?: boolean;
setting?: boolean;
settingCache?: boolean;
fullScreen?: boolean;
export?: boolean;
}
export interface BasicTableProps<T = any> {
// 点击行选中
clickToRowSelect?: boolean;
isTreeTable?: boolean;
accordion?: boolean; // isTreeTable 或 expandRowByClick 时支持
// 自定义排序方法
sortFn?: (sortInfo: SorterResult) => any;
// 排序方法
@ -187,7 +201,7 @@ export interface BasicTableProps<T = any> {
useSearchForm?: boolean;
// 表单配置
formConfig?: Partial<FormProps>;
// 高级查询配置
// 高级查询配置
advancedSearchConfig?: Partial<AdvanceSearchProps>;
// 使用已选择提示
useSelectedAlert?: boolean;
@ -210,7 +224,7 @@ export interface BasicTableProps<T = any> {
// 在分页改变的时候清空选项
clearSelectOnPageChange?: boolean;
//
rowKey?: string | ((record: Recordable) => string);
rowKey?: InstanceType<typeof AntDesignVueTable>['$props']['rowKey'];
// 数据
dataSource?: Recordable[];
// 标题右侧提示
@ -312,13 +326,19 @@ export interface BasicTableProps<T = any> {
*/
rowSelection?: TableRowSelection;
/**
* Show table selection bar
* @type boolean
*/
showSelectionBar?: boolean;
/**
* Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area.
* It is recommended to set a number for x, if you want to set it to true,
* you need to add style .ant-table td { white-space: nowrap; }.
* @type object
*/
scroll?: { x?: string | number | true; y?: string | number };
scroll?: InstanceType<typeof AntDesignVueTable>['$props']['scroll'];
/**
* Whether to show table header
@ -385,7 +405,7 @@ export interface BasicTableProps<T = any> {
beforeEditSubmit?: (data: {
record: Recordable;
index: number;
key: string | number;
key: Key;
value: any;
}) => Promise<any>;
@ -404,7 +424,7 @@ export interface BasicTableProps<T = any> {
* @param expanded
* @param record
*/
onExpand?: (expande: boolean, record: T) => void;
onExpand?: (expanded: boolean, record: T) => void;
/**
* Callback executed when the expanded rows change
@ -437,11 +457,13 @@ export interface BasicColumn extends ColumnProps<Recordable> {
slots?: Recordable;
// 自定义header渲染
customHeaderRender?: (column: BasicColumn) => string | VNodeChild | JSX.Element;
// Whether to hide the column by default, it can be displayed in the column configuration
defaultHidden?: boolean;
// Help text for table column header
helpMessage?: string | string[];
helpMessage?: string | string[] | VNodeChild | JSX.Element;
format?: CellFormat;
@ -471,6 +493,7 @@ export interface BasicColumn extends ColumnProps<Recordable> {
record: Recordable;
column: BasicColumn;
index: number;
currentValue: string | number | boolean | Recordable;
}) => VNodeChild | JSX.Element;
// 动态 Disabled
editDynamicDisabled?: boolean | ((record: Recordable) => boolean);
@ -486,3 +509,19 @@ export interface InnerHandlers {
onColumnsChange: (data: ColumnChangeParam[]) => void;
deSelect: () => void;
}
export interface InnerMethods {
clearSelectedRowKeys: TableActionType['clearSelectedRowKeys'];
getSelectRowKeys: TableActionType['getSelectRowKeys'];
}
export interface ColumnOptionsType {
value: string;
label: string;
//
column: {
defaultHidden?: boolean;
};
//
fixed?: FixedType;
}

3
apps/vue/src/enums/cacheEnum.ts

@ -30,6 +30,9 @@ export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__';
// base global session key
export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__';
// table 列设置
export const TABLE_SETTING_KEY = 'TABLE__SETTING__KEY__';
export enum CacheTypeEnum {
SESSION,
LOCAL,

27
apps/vue/src/hooks/web/useSignalR.ts

@ -10,7 +10,7 @@ import { useUserStoreWithOut } from '/@/store/modules/user';
import mitt from '/@/utils/mitt';
interface UseSignalR {
interface SignalROptions {
serverUrl: string;
autoStart?: boolean;
useAccessToken?: boolean;
@ -18,18 +18,16 @@ interface UseSignalR {
nextRetryDelayInMilliseconds?: number;
}
export function useSignalR({
serverUrl,
autoStart = false,
useAccessToken = true,
automaticReconnect = true,
nextRetryDelayInMilliseconds = 60000,
}: UseSignalR) {
interface UseSignalR {
lazyInit?: boolean;
}
export function useSignalR(options: UseSignalR & SignalROptions) {
const emitter = mitt();
let connection: HubConnection | null = null;
onMounted(() => {
_initlizaConnection();
!options.lazyInit && init(options);
});
onUnmounted(() => {
@ -38,7 +36,13 @@ export function useSignalR({
}
});
function _initlizaConnection() {
function init({
serverUrl,
autoStart = false,
useAccessToken = true,
automaticReconnect = true,
nextRetryDelayInMilliseconds = 60000,
}: SignalROptions) {
const httpOptions: IHttpConnectionOptions = {};
if (useAccessToken) {
const userStore = useUserStoreWithOut();
@ -49,7 +53,7 @@ export function useSignalR({
var connectionBuilder = new HubConnectionBuilder()
.withUrl(serverUrl, httpOptions)
.configureLogging(LogLevel.Warning);
if (automaticReconnect) {
if (automaticReconnect && nextRetryDelayInMilliseconds) {
connectionBuilder.withAutomaticReconnect({
nextRetryDelayInMilliseconds: () => nextRetryDelayInMilliseconds,
});
@ -123,6 +127,7 @@ export function useSignalR({
return {
on,
off,
init,
onclose,
beforeStart,
onStart,

9
apps/vue/src/settings/designSetting.ts

@ -2,8 +2,17 @@ import { ThemeEnum } from '../enums/appEnum';
export const prefixCls = 'vben';
export const multipleTabHeight = 30;
export const darkMode = ThemeEnum.LIGHT;
// 页脚固定高度
export const footerHeight = 75;
// .@{namespace}-layout-multiple-header__placeholder
// 全屏页头动画时长
export const layoutMultipleHeadePlaceholderTime = 0.6;
// app theme preset color
export const APP_PRESET_COLOR_LIST: string[] = [
'#0960bd',

126
apps/vue/src/store/modules/tableSetting.ts

@ -0,0 +1,126 @@
import { defineStore } from 'pinia';
import { TABLE_SETTING_KEY } from '/@/enums/cacheEnum';
import { Persistent } from '/@/utils/cache/persistent';
import type { TableSetting } from '/#/store';
import type { SizeType, ColumnOptionsType } from '/@/components/Table/src/types/table';
interface TableSettingState {
setting: Nullable<Partial<TableSetting>>;
}
export const useTableSettingStore = defineStore({
id: 'table-setting',
state: (): TableSettingState => ({
setting: Persistent.getLocal(TABLE_SETTING_KEY),
}),
getters: {
getTableSetting(state): Nullable<Partial<TableSetting>> {
return state.setting;
},
//
getTableSize(state) {
return state.setting?.size || 'middle';
},
//
getShowIndexColumn(state) {
return (routerName: string) => {
return state.setting?.showIndexColumn?.[routerName];
};
},
//
getShowRowSelection(state) {
return (routerName: string) => {
return state.setting?.showRowSelection?.[routerName];
};
},
//
getAllowResizeColumn(state) {
return (routerName: string) => {
return state.setting?.allowResizeColumn?.[routerName];
};
},
//
getColumns(state) {
return (routerName: string) => {
return state.setting?.columns && state.setting?.columns[routerName]
? state.setting?.columns[routerName]
: null;
};
},
},
actions: {
setTableSetting(setting: Partial<TableSetting>) {
this.setting = Object.assign({}, this.setting, setting);
Persistent.setLocal(TABLE_SETTING_KEY, this.setting, true);
},
resetTableSetting() {
Persistent.removeLocal(TABLE_SETTING_KEY, true);
this.setting = null;
},
//
setTableSize(size: SizeType) {
this.setTableSetting(
Object.assign({}, this.setting, {
size,
}),
);
},
//
setShowIndexColumn(routerName: string, show: boolean) {
this.setTableSetting(
Object.assign({}, this.setting, {
showIndexColumn: {
...this.setting?.showIndexColumn,
[routerName]: show,
},
}),
);
},
//
setAllowResizeColumn(routerName: string, resize: boolean) {
this.setTableSetting(
Object.assign({}, this.setting, {
allowResizeColumn: {
...this.setting?.allowResizeColumn,
[routerName]: resize,
},
}),
);
},
//
setShowRowSelection(routerName: string, show: boolean) {
this.setTableSetting(
Object.assign({}, this.setting, {
showRowSelection: {
...this.setting?.showRowSelection,
[routerName]: show,
},
}),
);
},
//
setColumns(routerName: string, columns: Array<ColumnOptionsType>) {
this.setTableSetting(
Object.assign({}, this.setting, {
columns: {
...this.setting?.columns,
[routerName]: columns,
},
}),
);
},
clearColumns(routerName: string) {
this.setTableSetting(
Object.assign({}, this.setting, {
columns: {
...this.setting?.columns,
[routerName]: undefined,
},
}),
);
},
},
});

4
apps/vue/src/utils/cache/persistent.ts

@ -1,4 +1,4 @@
import type { LockInfo, UserInfo } from '/#/store';
import type { LockInfo, UserInfo, TableSetting } from '/#/store';
import type { ProjectConfig } from '/#/config';
import type { RouteLocationNormalized } from 'vue-router';
@ -13,6 +13,7 @@ import {
APP_LOCAL_CACHE_KEY,
APP_SESSION_CACHE_KEY,
MULTIPLE_TABS_KEY,
TABLE_SETTING_KEY,
} from '/@/enums/cacheEnum';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
import { toRaw } from 'vue';
@ -25,6 +26,7 @@ interface BasicStore {
[LOCK_INFO_KEY]: LockInfo;
[PROJ_CFG_KEY]: ProjectConfig;
[MULTIPLE_TABS_KEY]: RouteLocationNormalized[];
[TABLE_SETTING_KEY]: Partial<TableSetting>;
}
type LocalStore = BasicStore;

112
apps/vue/src/utils/is.ts

@ -1,3 +1,41 @@
import { isNull, isFunction } from 'lodash-es';
export { isNull, isFunction };
export {
isArguments,
isArrayBuffer,
isArrayLike,
isArrayLikeObject,
isBuffer,
isBoolean,
isDate,
isElement,
isEmpty,
isEqual,
isEqualWith,
isError,
isFinite,
isLength,
isMap,
isMatch,
isMatchWith,
isNative,
isNil,
isNumber,
isObjectLike,
isPlainObject,
isRegExp,
isSafeInteger,
isSet,
isString,
isSymbol,
isTypedArray,
isUndefined,
isWeakMap,
isWeakSet,
} from 'lodash-es';
const toString = Object.prototype.toString;
export function is(val: unknown, type: string) {
@ -16,28 +54,8 @@ export function isObject(val: any): val is Record<any, any> {
return val !== null && is(val, 'Object');
}
export function isEmpty<T = unknown>(val: T): val is T {
if (isArray(val) || isString(val)) {
return val.length === 0;
}
if (val instanceof Map || val instanceof Set) {
return val.size === 0;
}
if (isObject(val)) {
return Object.keys(val).length === 0;
}
return false;
}
export function isDate(val: unknown): val is Date {
return is(val, 'Date');
}
export function isNull(val: unknown): val is null {
return val === null;
export function isRegMatch(reg: RegExp, val: string) {
return reg.test(val);
}
export function isNullAndUnDef(val: unknown): val is null | undefined {
@ -48,30 +66,10 @@ export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val);
}
export function isNumber(val: unknown): val is number {
return is(val, 'Number');
}
export function isPromise<T = any>(val: unknown): val is Promise<T> {
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch);
}
export function isString(val: unknown): val is string {
return is(val, 'String');
}
export function isFunction(val: unknown): val is Function {
return typeof val === 'function';
}
export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean');
}
export function isRegExp(val: unknown): val is RegExp {
return is(val, 'RegExp');
}
export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val);
}
@ -80,47 +78,35 @@ export function isWindow(val: any): val is Window {
return typeof window !== 'undefined' && is(val, 'Window');
}
export function isElement(val: unknown): val is Element {
return isObject(val) && !!val.tagName;
}
export function isMap(val: unknown): val is Map<any, any> {
return is(val, 'Map');
}
export const isServer = typeof window === 'undefined';
export const isClient = !isServer;
export function isMatch(reg: RegExp, val: string) {
return reg.test(val);
}
export function isEmail(val: string) {
const reg = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
return isMatch(reg, val);
return isRegMatch(reg, val);
}
export function isPhone(val: string) {
const reg = /^(13[0-9]|14[5|7]|15[0-9]|17[0-9]|18[0-9]|19[0-9])\d{8}$/;
return isMatch(reg, val);
return isRegMatch(reg, val);
}
export function isUrl(path: string): boolean {
const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
return isMatch(reg, path);
return isRegMatch(reg, path);
}
export function isSortUrl(path: string) {
const reg1 = /^[a-zA-Z]+:\d{1,5}$/;
const reg2 = /^([a-zA-Z\-\d+]+\.){1,}[a-z\-\d]+:\d{1,5}$/;
return isMatch(reg1, path) || isMatch(reg2, path);
return isRegMatch(reg1, path) || isRegMatch(reg2, path);
}
export function isIpPort(path: string): boolean {
const reg =
/^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])){3}:([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/;
return isMatch(reg, path);
return isRegMatch(reg, path);
}
export function isDigit(val: string) {
@ -141,18 +127,18 @@ export function isLetterOrDigit(val: string) {
}
function _isDigit(char: string) {
return isMatch(/[0-9]/g, char);
return isRegMatch(/[0-9]/g, char);
}
function _isLower(char: string) {
return isMatch(/[a-z]/g, char);
return isRegMatch(/[a-z]/g, char);
}
function _isUpper(char: string) {
return isMatch(/[A-Z]/g, char);
return isRegMatch(/[A-Z]/g, char);
}
function _isLetterOrDigit(char: string) {
return isMatch(/[^ A-Za-z0-9_]/g, char);
return isRegMatch(/[^ A-Za-z0-9_]/g, char);
}

2
apps/vue/src/views/account/center/Cloud.vue

@ -32,7 +32,7 @@
import { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { usePermission } from '/@/hooks/web/usePermission';
import { OssObject } from '/@/api/oss-management/model/ossModel';
import { OssObject } from '/@/api/oss-management/objects/model';
import FileList from './FileList.vue';
import ShareList from './ShareList.vue';

2
apps/vue/src/views/account/center/FileList.vue

@ -42,7 +42,7 @@
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { getDataColumns } from './data';
import { OssObject } from '/@/api/oss-management/model/ossModel';
import { OssObject } from '/@/api/oss-management/objects/model';
import { getList as getPrivates } from '/@/api/oss-management/files/private';
import { getList as getPublices } from '/@/api/oss-management/files/public';
import { generateOssUrl, deleteObject } from '/@/api/oss-management/objects';

18
apps/vue/src/views/oss-management/objects/components/FileList.vue

@ -1,6 +1,6 @@
<template>
<div class="file-list-wrap">
<BasicTable @register="registerTable">
<BasicTable @register="registerTable" @selection-change="handleSelectRows">
<template #toolbar>
<Button
v-if="hasPermission('AbpOssManagement.OssObject.Create')"
@ -54,7 +54,7 @@
</template>
<script lang="ts" setup>
import { computed, watch, nextTick } from 'vue';
import { computed, watch, nextTick, ref } from 'vue';
import { Button } from 'ant-design-vue';
import { useModal } from '/@/components/Modal';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
@ -88,7 +88,7 @@
const { L } = useLocalization(['AbpOssManagement', 'AbpUi']);
const [registerUploadModal, { openModal: openUploadModal }] = useModal();
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const [registerTable, { reload, getSelectRowKeys, setTableData, setPagination }] = useTable({
const [registerTable, { reload, setTableData, setPagination }] = useTable({
rowKey: 'name',
title: L('DisplayName:OssObject'),
columns: getDataColumns(),
@ -119,8 +119,9 @@
dataIndex: 'action',
},
});
const selectionKeys = ref<string[]>([]);
const selectCount = computed(() => {
return getSelectRowKeys().length;
return selectionKeys.value.length;
});
watch(
@ -147,6 +148,10 @@
},
);
function handleSelectRows(e: { keys: string[] }) {
selectionKeys.value = e.keys;
}
function beforeFetch(request: any) {
request.bucket = props.bucket;
request.prefix = props.path;
@ -180,15 +185,14 @@
onOk: async () => {
const bucket = props.bucket;
const path = props.path;
const objects = getSelectRowKeys();
await bulkDeleteObject({
bucket: bucket,
path: path,
objects: objects,
objects: selectionKeys.value,
});
createMessage.success(L('SuccessfullyDeleted'));
await reload();
emits('oss:delete', bucket, path, objects);
emits('oss:delete', bucket, path, selectionKeys.value);
},
});
}

16
apps/vue/src/views/sys/login/TwoFactorLoginForm.vue

@ -23,7 +23,7 @@
</FormItem>
<template v-if="formData.type === 'Email'">
<FormItem
name="email"
name="emailAddress"
class="enter-x"
:label="L('DisplayName:EmailAddress')"
:rules="[
@ -43,7 +43,7 @@
size="large"
class="fix-auto-fill"
autocomplete="off"
v-model:value="formData.email"
v-model:value="formData.emailAddress"
/>
</FormItem>
<FormItem name="code" class="enter-x" :label="L('DisplayName:EmailVerifyCode')" required>
@ -52,7 +52,7 @@
class="fix-auto-fill"
autocomplete="off"
v-model:value="formData.code"
:sendCodeApi="() => handleSendCode('email', sendEmailSigninCode)"
:sendCodeApi="() => handleSendCode('emailAddress', sendEmailSigninCode)"
/>
</FormItem>
</template>
@ -78,7 +78,7 @@
size="large"
class="fix-auto-fill"
autocomplete="off"
v-model:value="formData.email"
v-model:value="formData.emailAddress"
/>
</FormItem>
<FormItem name="code" class="enter-x" :label="L('DisplayName:SmsVerifyCode')" required>
@ -152,7 +152,7 @@
const formData = reactive({
type: '',
email: '',
emailAddress: '',
phoneNumber: '',
code: '',
});
@ -166,14 +166,16 @@
function handleResetValidate() {
formRef.value?.clearValidate();
formData.code = '';
formData.email = '';
formData.emailAddress = '';
formData.phoneNumber = '';
}
function handleSendCode(field: string, sendCodeApi: (...args) => Promise<void>) {
return validFormFields([field])
.then(() => {
return sendCodeApi(formData[field])
return sendCodeApi({
[field]: formData[field],
})
.then(() => {
return Promise.resolve(true);
})

15
apps/vue/src/views/task-management/background-jobs/components/JobTable.vue

@ -149,7 +149,7 @@
const { L } = useLocalization(['TaskManagement', 'AbpUi']);
const { hasPermission } = usePermission();
const [registerModal, { openModal }] = useModal();
const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
rowKey: 'id',
title: L('BackgroundJobs'),
columns: getDataColumns(),
@ -182,7 +182,7 @@
});
const selectedRowKeys = ref<string[]>([]);
const isMultiSelected = computed(() => {
return selectedRowKeys.value.length > 0;
return selectedRowKeys.value.length;
});
function handleSelectChange(keys) {
@ -223,24 +223,21 @@
}
function handleStart() {
const selectKeys = getSelectRowKeys();
bulkStart(selectKeys).then(() => {
bulkStart(selectedRowKeys.value).then(() => {
createMessage.success(L('Successful'));
reloadTable();
});
}
function handleStop() {
const selectKeys = getSelectRowKeys();
bulkStop(selectKeys).then(() => {
bulkStop(selectedRowKeys.value).then(() => {
createMessage.success(L('Successful'));
reloadTable();
});
}
function handleDeleteMany() {
const selectKeys = getSelectRowKeys();
if (selectKeys.length <= 0) {
if (selectedRowKeys.value.length <= 0) {
return;
}
createConfirm({
@ -249,7 +246,7 @@
content: L('MultipleSelectJobsWillBeDeletedMessage'),
okCancel: true,
onOk: () => {
return bulkDelete(selectKeys).then(() => {
return bulkDelete(selectedRowKeys.value).then(() => {
reloadTable();
});
},

20
apps/vue/src/views/webhooks/send-attempts/components/SendAttemptTable.vue

@ -1,6 +1,6 @@
<template>
<div class="content">
<BasicTable @register="registerTable">
<BasicTable @register="registerTable" @selection-change="handleSelectionChange">
<template #toolbar>
<Button
v-if="isManyRecordSelected"
@ -59,7 +59,7 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { Button, Tag } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
@ -78,10 +78,11 @@
} from '/@/api/webhooks/send-attempts';
import SendAttemptModal from './SendAttemptModal.vue';
const selectionKeys = ref<string[]>([]);
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['WebhooksManagement', 'AbpUi']);
const [registerModal, { openModal }] = useModal();
const [registerTable, { reload, setLoading, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
const [registerTable, { reload, setLoading, clearSelectedRowKeys }] = useTable({
rowKey: 'id',
title: L('SendAttempts'),
columns: getDataColumns(),
@ -107,10 +108,13 @@
},
});
const isManyRecordSelected = computed(() => {
const selectedKeys = getSelectRowKeys();
return selectedKeys.length > 0;
return selectionKeys.value.length;
});
function handleSelectionChange(e: { keys: string[] }) {
selectionKeys.value = e.keys;
}
function handleEdit(record) {
openModal(true, record);
}
@ -122,10 +126,9 @@
content: L('ItemWillBeDeletedMessageWithFormat', { 0: L('SelectedItems') }),
okCancel: true,
onOk: () => {
const selectKeys = getSelectRowKeys();
setLoading(true);
return DeleteManyAsyncByInput({
recordIds: selectKeys,
recordIds: selectionKeys.value,
})
.then(() => {
createMessage.success(L('SuccessfullyDeleted'));
@ -188,10 +191,9 @@
content: L('ItemWillBeResendMessageWithFormat', { 0: L('SelectedItems') }),
okCancel: true,
onOk: () => {
const selectKeys = getSelectRowKeys();
setLoading(true);
return ResendManyAsyncByInput({
recordIds: selectKeys,
recordIds: selectionKeys.value,
})
.then(() => {
createMessage.success(L('Successful'));

19
apps/vue/src/views/webhooks/subscriptions/components/SubscriptionTable.vue

@ -1,6 +1,6 @@
<template>
<div class="content">
<BasicTable @register="registerTable">
<BasicTable @register="registerTable" @selection-change="handleSelectionChange">
<template #toolbar>
<Button v-auth="['AbpWebhooks.Subscriptions.Create']" type="primary" @click="handleAddNew">
{{ L('Subscriptions:AddNew') }}
@ -49,7 +49,7 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { Button, Tag } from 'ant-design-vue';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { useMessage } from '/@/hooks/web/useMessage';
@ -69,7 +69,7 @@
const { createConfirm, createMessage } = useMessage();
const { L } = useLocalization(['WebhooksManagement', 'AbpUi']);
const [registerModal, { openModal }] = useModal();
const [registerTable, { reload, setLoading, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
const [registerTable, { reload, setLoading, clearSelectedRowKeys }] = useTable({
rowKey: 'id',
title: L('Subscriptions'),
columns: getDataColumns(),
@ -94,11 +94,15 @@
type: 'checkbox',
},
});
const deleteManyEnabled = computed(() => {
const selectKeys = getSelectRowKeys();
return selectKeys.length > 0;
const selectionKeys = ref<string[]>([]);
const deleteManyEnabled = computed(() => {
return selectionKeys.value.length;
});
function handleSelectionChange(e: { keys: string[] }) {
selectionKeys.value = e.keys;
}
function handleAddNew() {
openModal(true, { id: null });
}
@ -114,10 +118,9 @@
content: L('ItemWillBeDeletedMessageWithFormat', { 0: L('SelectedItems') }),
okCancel: true,
onOk: () => {
const selectKeys = getSelectRowKeys();
setLoading(true);
return DeleteManyAsyncByInput({
recordIds: selectKeys,
recordIds: selectionKeys.value,
})
.then(() => {
createMessage.success(L('SuccessfullyDeleted'));

8
apps/vue/types/store.d.ts

@ -50,3 +50,11 @@ export interface BeforeMiniState {
menuMode?: MenuModeEnum;
menuType?: MenuTypeEnum;
}
export interface TableSetting {
size: Nullable<SizeType>;
showIndexColumn: Recordable<Nullable<boolean>>;
columns: Recordable<Nullable<Array<ColumnOptionsType>>>;
showRowSelection: Recordable<Nullable<boolean>>;
allowResizeColumn: Recordable<Nullable<boolean>>;
}

30
aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/LINGYUN/Abp/AspNetCore/HttpOverrides/AbpAspNetCoreHttpOverridesModule.cs

@ -1,25 +1,27 @@
using LINGYUN.Abp.AspNetCore.HttpOverrides.Forwarded;
using LINGYUN.Abp.AspNetCore.WebClientInfo;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Collections.Generic;
using Volo.Abp.AspNetCore;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.AspNetCore.HttpOverrides
namespace LINGYUN.Abp.AspNetCore.HttpOverrides;
[DependsOn(typeof(AbpAspNetCoreModule))]
public class AbpAspNetCoreHttpOverridesModule : AbpModule
{
public class AbpAspNetCoreHttpOverridesModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var configuration = context.Services.GetConfiguration();
var forwardOptions = new AbpForwardedHeadersOptions();
configuration.GetSection("Forwarded:Headers").Bind(forwardOptions);
context.Services.ExecutePreConfiguredActions(forwardOptions);
Configure<ForwardedHeadersOptions>(options =>
{
configuration.GetSection("Forwarded").Bind(options);
});
Configure<ForwardedHeadersOptions>(options =>
{
options.Configure(forwardOptions);
});
}
context.Services.Replace(ServiceDescriptor.Transient<IWebClientInfoProvider, RequestForwardedHeaderWebClientInfoProvider>());
}
}

36
aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/LINGYUN/Abp/AspNetCore/HttpOverrides/Forwarded/AbpForwardedHeadersOptions.cs

@ -1,36 +0,0 @@
using Microsoft.AspNetCore.HttpOverrides;
using System.Collections.Generic;
namespace LINGYUN.Abp.AspNetCore.HttpOverrides.Forwarded
{
public class AbpForwardedHeadersOptions
{
public string ForwardedForHeaderName { get; set; }
public string ForwardedHostHeaderName { get; set; }
public string ForwardedProtoHeaderName { get; set; }
public string OriginalForHeaderName { get; set; }
public string OriginalHostHeaderName { get; set; }
public string OriginalProtoHeaderName { get; set; }
public ForwardedHeaders ForwardedHeaders { get; set; }
public int? ForwardLimit { get; set; }
public bool RequireHeaderSymmetry { get; set; }
public List<string> AllowedHosts { get; set; }
public List<string> KnownNetworks { get; set; }
public List<string> KnownProxies { get; set; }
public AbpForwardedHeadersOptions()
{
ForwardedForHeaderName = ForwardedHeadersDefaults.XForwardedForHeaderName;
ForwardedHostHeaderName = ForwardedHeadersDefaults.XForwardedHostHeaderName;
ForwardedProtoHeaderName = ForwardedHeadersDefaults.XForwardedProtoHeaderName;
OriginalForHeaderName = ForwardedHeadersDefaults.XOriginalForHeaderName;
OriginalHostHeaderName = ForwardedHeadersDefaults.XOriginalHostHeaderName;
OriginalProtoHeaderName = ForwardedHeadersDefaults.XOriginalProtoHeaderName;
ForwardedHeaders = ForwardedHeaders.None;
ForwardLimit = 1;
RequireHeaderSymmetry = false;
AllowedHosts = new List<string>();
KnownProxies = new List<string>();
KnownNetworks = new List<string>();
}
}
}

51
aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/LINGYUN/Abp/AspNetCore/WebClientInfo/RequestForwardedHeaderWebClientInfoProvider.cs

@ -1,43 +1,42 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using System;
using System.Linq;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.AspNetCore.WebClientInfo
namespace LINGYUN.Abp.AspNetCore.WebClientInfo;
public class RequestForwardedHeaderWebClientInfoProvider : HttpContextWebClientInfoProvider
{
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
[ExposeServices(
typeof(IWebClientInfoProvider),
typeof(HttpContextWebClientInfoProvider))]
public class RequestForwardedHeaderWebClientInfoProvider : HttpContextWebClientInfoProvider
protected ForwardedHeadersOptions Options { get; }
public RequestForwardedHeaderWebClientInfoProvider(
ILogger<HttpContextWebClientInfoProvider> logger,
IOptions<ForwardedHeadersOptions> options,
IHttpContextAccessor httpContextAccessor)
: base(logger, httpContextAccessor)
{
protected ForwardedHeadersOptions Options { get; }
public RequestForwardedHeaderWebClientInfoProvider(
ILogger<HttpContextWebClientInfoProvider> logger,
IOptions<ForwardedHeadersOptions> options,
IHttpContextAccessor httpContextAccessor)
: base(logger, httpContextAccessor)
{
Options = options.Value;
}
Options = options.Value;
}
protected override string GetClientIpAddress()
protected override string GetClientIpAddress()
{
string forwardedForHeader = null;
var requestHeaders = HttpContextAccessor.HttpContext?.Request?.Headers;
if (requestHeaders != null &&
Options.ForwardedHeaders.HasFlag(ForwardedHeaders.XForwardedFor) &&
requestHeaders.TryGetValue(Options.ForwardedForHeaderName, out StringValues headers) == true)
{
IHeaderDictionary requestHeaders = HttpContextAccessor.HttpContext?.Request?.Headers;
if (requestHeaders != null &&
requestHeaders.TryGetValue(Options.ForwardedForHeaderName, out StringValues headers) == true)
var headerStr = headers.ToString();
if (!headerStr.IsNullOrWhiteSpace())
{
if (!StringValues.IsNullOrEmpty(headers))
{
return headers.Last();
}
// 原始客户端IP永远是第一个
forwardedForHeader = headerStr.Split(",").First();
}
return base.GetClientIpAddress();
}
return forwardedForHeader.IsNullOrWhiteSpace() ? base.GetClientIpAddress() : forwardedForHeader;
}
}

67
aspnet-core/framework/common/LINGYUN.Abp.AspNetCore.HttpOverrides/Microsoft/AspNetCore/Builder/ForwardedHeadersOptionsExtensions.cs

@ -1,67 +0,0 @@
using LINGYUN.Abp.AspNetCore.HttpOverrides.Forwarded;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
namespace Microsoft.AspNetCore.Builder
{
public static class ForwardedHeadersOptionsExtensions
{
public static void Configure(
this ForwardedHeadersOptions options,
AbpForwardedHeadersOptions abpOptions)
{
options.ForwardedForHeaderName = abpOptions.ForwardedForHeaderName;
options.ForwardedHeaders = abpOptions.ForwardedHeaders;
options.ForwardedHostHeaderName = abpOptions.ForwardedHostHeaderName;
options.ForwardedProtoHeaderName = abpOptions.ForwardedProtoHeaderName;
options.ForwardLimit = abpOptions.ForwardLimit;
options.OriginalForHeaderName = abpOptions.OriginalForHeaderName;
options.OriginalHostHeaderName = abpOptions.OriginalHostHeaderName;
options.OriginalProtoHeaderName = abpOptions.OriginalProtoHeaderName;
options.RequireHeaderSymmetry = abpOptions.RequireHeaderSymmetry;
if (abpOptions.AllowedHosts.Any())
{
options.AllowedHosts = abpOptions.AllowedHosts;
}
AddProxies(options.KnownProxies, abpOptions.KnownProxies);
if (abpOptions.KnownNetworks.Any())
{
options.KnownNetworks.Clear();
foreach (var proxy in abpOptions.KnownNetworks)
{
// 192.168.1.0/24
var spiltProxies = proxy.Split("/");
if (spiltProxies.Length != 2)
{
continue;
}
if (int.TryParse(spiltProxies[1], out int prefixLength) &&
IPAddress.TryParse(spiltProxies[0], out IPAddress prefixIpAddress))
{
options.KnownNetworks.Add(new IPNetwork(prefixIpAddress, prefixLength));
}
}
}
}
private static void AddProxies(IList<IPAddress> addresses, IList<string> proxiesAddress)
{
if (proxiesAddress.Any())
{
addresses.Clear();
foreach (var proxy in proxiesAddress)
{
if (IPAddress.TryParse(proxy, out IPAddress iPAddress))
{
addresses.Add(iPAddress);
}
}
}
}
}
}

3
aspnet-core/services/LY.MicroService.Applications.Single/Dockerfile

@ -12,4 +12,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.Applications.Single.dll"]

14
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.Configure.cs

@ -8,9 +8,11 @@ using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -295,12 +297,22 @@ public partial class AuthServerHttpApiHostModule
});
}
private void ConfigureMvc()
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureVirtualFileSystem()

9
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/AuthServerHttpApiHostModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Account;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
using LINGYUN.Abp.Authorization.OrganizationUnits;
@ -59,6 +60,7 @@ namespace LY.MicroService.AuthServer;
typeof(AbpAliyunSmsModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpLocalizationCultureMapModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpAutofacModule)
)]
public partial class AuthServerHttpApiHostModule : AbpModule
@ -79,7 +81,6 @@ public partial class AuthServerHttpApiHostModule : AbpModule
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureMvc();
ConfigureIdentity();
ConfigureDbContext();
ConfigureLocalization();
@ -94,6 +95,7 @@ public partial class AuthServerHttpApiHostModule : AbpModule
ConfigureSwagger(context.Services);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
@ -103,10 +105,7 @@ public partial class AuthServerHttpApiHostModule : AbpModule
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
// 本地化
app.UseMapRequestLocalization();
// http调用链

3
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.AuthServer.HttpApi.Host.dll"]

3
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/LY.MicroService.AuthServer.HttpApi.Host.csproj

@ -33,7 +33,7 @@
<PackageReference Include="Serilog.Enrichers.Process" />
<PackageReference Include="Serilog.Enrichers.Thread" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Elasticsearch"/>
<PackageReference Include="Serilog.Sinks.Elasticsearch" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Volo.Abp.AspNetCore.Serilog" />
@ -54,6 +54,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\authorization\LINGYUN.Abp.Authorization.OrganizationUnits\LINGYUN.Abp.Authorization.OrganizationUnits.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.ExceptionHandling.Emailing\LINGYUN.Abp.ExceptionHandling.Emailing.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Sms.Aliyun\LINGYUN.Abp.Sms.Aliyun.csproj" />

3
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/appsettings.Development.json

@ -51,7 +51,8 @@
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpFeatureManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpLocalizationManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None"
"AbpLocalizationManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpTextTemplating": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None"
},
"CAP": {
"EventBus": {

3
aspnet-core/services/LY.MicroService.AuthServer.HttpApi.Host/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"ForwardedHeaders": "XForwardedFor,XForwardedProto"
},
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",
"InitVectorBytes": "s83ng0abvd02js84",

21
aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.Configure.cs

@ -4,11 +4,13 @@ using LINGYUN.Abp.Serilog.Enrichers.Application;
using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -26,6 +28,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Volo.Abp.Account.Localization;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Auditing;
using Volo.Abp.Caching;
using Volo.Abp.EntityFrameworkCore;
@ -175,6 +178,24 @@ public partial class AuthServerModule
}
}
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureDbContext()
{
Configure<AbpDbContextOptions>(options =>

8
aspnet-core/services/LY.MicroService.AuthServer/AuthServerModule.cs

@ -1,5 +1,6 @@
using DotNetCore.CAP;
using LINGYUN.Abp.Account;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
using LINGYUN.Abp.Authentication.QQ;
using LINGYUN.Abp.Authentication.WeChat;
@ -77,6 +78,7 @@ namespace LY.MicroService.AuthServer;
typeof(AbpDataDbMigratorModule),
typeof(AbpAuditLoggingElasticsearchModule), // 放在 AbpIdentity 模块之后,避免被覆盖
typeof(AbpLocalizationCultureMapModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpCAPEventBusModule),
typeof(AbpAliyunSmsModule)
)]
@ -114,6 +116,7 @@ public partial class AuthServerModule : AbpModule
ConfigureAuditing(configuration);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
@ -126,10 +129,7 @@ public partial class AuthServerModule : AbpModule
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
app.UseMapRequestLocalization();
if (env.IsDevelopment())
{

3
aspnet-core/services/LY.MicroService.AuthServer/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.AuthServer.dll"]

1
aspnet-core/services/LY.MicroService.AuthServer/LY.MicroService.AuthServer.csproj

@ -59,6 +59,7 @@
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\authentication\LINGYUN.Abp.Authentication.QQ\LINGYUN.Abp.Authentication.QQ.csproj" />
<ProjectReference Include="..\..\framework\authentication\LINGYUN.Abp.Authentication.WeChat\LINGYUN.Abp.Authentication.WeChat.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Sms.Aliyun\LINGYUN.Abp.Sms.Aliyun.csproj" />

1
aspnet-core/services/LY.MicroService.AuthServer/appsettings.Development.json

@ -52,6 +52,7 @@
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpFeatureManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpTextTemplating": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None",
"AppPlatform": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None"
},
"CAP": {

3
aspnet-core/services/LY.MicroService.AuthServer/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"ForwardedHeaders": "XForwardedFor,XForwardedProto"
},
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",
"InitVectorBytes": "s83ng0abvd02js84",

14
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.Configure.cs

@ -10,8 +10,10 @@ using LINGYUN.Abp.TextTemplating;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -297,12 +299,22 @@ public partial class BackendAdminHttpApiHostModule
}
}
private void ConfigureMvc()
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureVirtualFileSystem()

10
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/BackendAdminHttpApiHostModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Aliyun.SettingManagement;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.Auditing;
@ -35,7 +36,6 @@ using LINGYUN.Abp.WxPusher.SettingManagement;
using LY.MicroService.BackendAdmin.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Volo.Abp;
@ -115,6 +115,7 @@ namespace LY.MicroService.BackendAdmin;
typeof(AbpLocalizationCultureMapModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpAutofacModule)
)]
public partial class BackendAdminHttpApiHostModule : AbpModule
@ -133,7 +134,6 @@ public partial class BackendAdminHttpApiHostModule : AbpModule
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureMvc();
ConfigureDbContext();
ConfigureLocalization();
ConfigureExceptionHandling();
@ -150,6 +150,7 @@ public partial class BackendAdminHttpApiHostModule : AbpModule
ConfigureSwagger(context.Services);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
@ -160,10 +161,7 @@ public partial class BackendAdminHttpApiHostModule : AbpModule
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
// 本地化
app.UseMapRequestLocalization();
// http调用链

3
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.BackendAdmin.HttpApi.Host.dll"]

1
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/LY.MicroService.BackendAdmin.HttpApi.Host.csproj

@ -61,6 +61,7 @@
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\cloud-aliyun\LINGYUN.Abp.Aliyun.SettingManagement\LINGYUN.Abp.Aliyun.SettingManagement.csproj" />
<ProjectReference Include="..\..\framework\cloud-tencent\LINGYUN.Abp.Tencent.SettingManagement\LINGYUN.Abp.Tencent.SettingManagement.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.ExceptionHandling.Emailing\LINGYUN.Abp.ExceptionHandling.Emailing.csproj" />

6
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/appsettings.Development.json

@ -9,12 +9,6 @@
"tag": "BackendAdmin"
},
"App": {
"Forwarded": {
"ForwardedHeaders": 5,
"KnownProxies": [
"127.0.0.1"
]
},
"CorsOrigins": "http://127.0.0.1:3100",
"ShowPii": true,
"RefreshClaimsUrl": "http://127.0.0.1:30015"

3
aspnet-core/services/LY.MicroService.BackendAdmin.HttpApi.Host/appsettings.json

@ -7,6 +7,9 @@
"InitVectorBytes": "s83ng0abvd02js84",
"DefaultSalt": "sf&5)s3#"
},
"Forwarded": {
"Headers": "XForwardedFor,XForwardedProto"
},
"Json": {
"OutputDateTimeFormat": "yyyy-MM-dd HH:mm:ss",
"InputDateTimeFormats": [

3
aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.IdentityServer.HttpApi.Host.dll"]

14
aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.Configure.cs

@ -7,9 +7,11 @@ using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -295,12 +297,22 @@ public partial class IdentityServerHttpApiHostModule
});
}
private void ConfigureMvc()
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureVirtualFileSystem()

9
aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/IdentityServerHttpApiHostModule.cs

@ -1,3 +1,4 @@
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
@ -59,6 +60,7 @@ namespace LY.MicroService.IdentityServer;
typeof(AbpLocalizationCultureMapModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpAutofacModule)
)]
public partial class IdentityServerHttpApiHostModule : AbpModule
@ -79,7 +81,6 @@ public partial class IdentityServerHttpApiHostModule : AbpModule
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureMvc();
ConfigureIdentity();
ConfigureDbContext();
ConfigureLocalization();
@ -94,6 +95,7 @@ public partial class IdentityServerHttpApiHostModule : AbpModule
ConfigureSwagger(context.Services);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
@ -103,10 +105,7 @@ public partial class IdentityServerHttpApiHostModule : AbpModule
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
// 本地化
app.UseMapRequestLocalization();
// http调用链

1
aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/LY.MicroService.identityServer.HttpApi.Host.csproj

@ -61,6 +61,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\authorization\LINGYUN.Abp.Authorization.OrganizationUnits\LINGYUN.Abp.Authorization.OrganizationUnits.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.ExceptionHandling.Emailing\LINGYUN.Abp.ExceptionHandling.Emailing.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Http.Client.Wrapper\LINGYUN.Abp.Http.Client.Wrapper.csproj" />

3
aspnet-core/services/LY.MicroService.IdentityServer.HttpApi.Host/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"ForwardedHeaders": "XForwardedFor,XForwardedProto"
},
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",
"InitVectorBytes": "s83ng0abvd02js84",

3
aspnet-core/services/LY.MicroService.IdentityServer/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.IdentityServer.dll"]

21
aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.Configure.cs

@ -7,10 +7,12 @@ using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
using LY.MicroService.IdentityServer.IdentityResources;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -28,6 +30,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Volo.Abp.Account.Localization;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.Auditing;
using Volo.Abp.Caching;
@ -124,6 +127,24 @@ public partial class IdentityServerModule
}
}
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureDbContext()
{
Configure<AbpDbContextOptions>(options =>

8
aspnet-core/services/LY.MicroService.IdentityServer/IdentityServerModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Account;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
using LINGYUN.Abp.Authentication.QQ;
@ -79,6 +80,7 @@ namespace LY.MicroService.IdentityServer;
typeof(AbpCAPEventBusModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpAliyunSmsModule)
)]
public partial class IdentityServerModule : AbpModule
@ -114,6 +116,7 @@ public partial class IdentityServerModule : AbpModule
ConfigureUrls(configuration);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
@ -126,10 +129,7 @@ public partial class IdentityServerModule : AbpModule
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
app.UseMapRequestLocalization();
if (env.IsDevelopment())
{

1
aspnet-core/services/LY.MicroService.IdentityServer/LY.MicroService.IdentityServer.csproj

@ -58,6 +58,7 @@
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\authentication\LINGYUN.Abp.Authentication.QQ\LINGYUN.Abp.Authentication.QQ.csproj" />
<ProjectReference Include="..\..\framework\authentication\LINGYUN.Abp.Authentication.WeChat\LINGYUN.Abp.Authentication.WeChat.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Http.Client.Wrapper\LINGYUN.Abp.Http.Client.Wrapper.csproj" />

3
aspnet-core/services/LY.MicroService.IdentityServer/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"ForwardedHeaders": "XForwardedFor,XForwardedProto"
},
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",
"InitVectorBytes": "s83ng0abvd02js84",

3
aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.LocalizationManagement.HttpApi.Host.dll"]

3
aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LY.MicroService.LocalizationManagement.HttpApi.Host.csproj

@ -31,7 +31,7 @@
<PackageReference Include="Serilog.Enrichers.Assembly" />
<PackageReference Include="Serilog.Enrichers.Process" />
<PackageReference Include="Serilog.Enrichers.Thread" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="Swashbuckle.AspNetCore" />
@ -49,6 +49,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\authorization\LINGYUN.Abp.Authorization.OrganizationUnits\LINGYUN.Abp.Authorization.OrganizationUnits.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.ExceptionHandling.Emailing\LINGYUN.Abp.ExceptionHandling.Emailing.csproj" />

14
aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.Configure.cs

@ -8,8 +8,10 @@ using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -248,12 +250,22 @@ public partial class LocalizationManagementHttpApiHostModule
});
}
private void ConfigureMvc()
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureVirtualFileSystem()

190
aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/LocalizationManagementHttpApiHostModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
using LINGYUN.Abp.Authorization.OrganizationUnits;
using LINGYUN.Abp.Data.DbMigrator;
@ -28,107 +29,104 @@ using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
namespace LY.MicroService.LocalizationManagement
namespace LY.MicroService.LocalizationManagement;
[DependsOn(
typeof(AbpSerilogEnrichersApplicationModule),
typeof(AbpSerilogEnrichersUniqueIdModule),
typeof(AbpAspNetCoreSerilogModule),
typeof(AbpAuditLoggingElasticsearchModule),
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(AbpLocalizationManagementApplicationModule),
typeof(AbpLocalizationManagementHttpApiModule),
typeof(AbpLocalizationManagementEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreMySQLModule),
typeof(AbpSaasEntityFrameworkCoreModule),
typeof(AbpFeatureManagementEntityFrameworkCoreModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(LocalizationManagementMigrationsEntityFrameworkCoreModule),
typeof(AbpDataDbMigratorModule),
typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
typeof(AbpAuthorizationOrganizationUnitsModule),
typeof(AbpEmailingExceptionHandlingModule),
typeof(AbpCAPEventBusModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpLocalizationCultureMapModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpAutofacModule)
)]
public partial class LocalizationManagementHttpApiHostModule : AbpModule
{
[DependsOn(
typeof(AbpSerilogEnrichersApplicationModule),
typeof(AbpSerilogEnrichersUniqueIdModule),
typeof(AbpAspNetCoreSerilogModule),
typeof(AbpAuditLoggingElasticsearchModule),
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(AbpLocalizationManagementApplicationModule),
typeof(AbpLocalizationManagementHttpApiModule),
typeof(AbpLocalizationManagementEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCoreMySQLModule),
typeof(AbpSaasEntityFrameworkCoreModule),
typeof(AbpFeatureManagementEntityFrameworkCoreModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(LocalizationManagementMigrationsEntityFrameworkCoreModule),
typeof(AbpDataDbMigratorModule),
typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
typeof(AbpAuthorizationOrganizationUnitsModule),
typeof(AbpEmailingExceptionHandlingModule),
typeof(AbpCAPEventBusModule),
typeof(AbpCachingStackExchangeRedisModule),
typeof(AbpLocalizationCultureMapModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAutofacModule)
)]
public partial class LocalizationManagementHttpApiHostModule : AbpModule
public override void PreConfigureServices(ServiceConfigurationContext context)
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var configuration = context.Services.GetConfiguration();
PreConfigureFeature();
PreForwardedHeaders();
PreConfigureApp(configuration);
PreConfigureCAP(configuration);
}
PreConfigureFeature();
PreForwardedHeaders();
PreConfigureApp(configuration);
PreConfigureCAP(configuration);
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureMvc();
ConfigureDbContext();
ConfigureLocalization();
ConfigureExceptionHandling();
ConfigureVirtualFileSystem();
ConfigureFeatureManagement();
ConfigureTiming(configuration);
ConfigureCaching(configuration);
ConfigureIdentity(configuration);
ConfigureAuditing(configuration);
ConfigureSwagger(context.Services);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
ConfigureSeedWorker(context.Services, hostingEnvironment.IsDevelopment());
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());
}
ConfigureDbContext();
ConfigureLocalization();
ConfigureExceptionHandling();
ConfigureVirtualFileSystem();
ConfigureFeatureManagement();
ConfigureTiming(configuration);
ConfigureCaching(configuration);
ConfigureIdentity(configuration);
ConfigureAuditing(configuration);
ConfigureSwagger(context.Services);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
ConfigureSeedWorker(context.Services, hostingEnvironment.IsDevelopment());
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
// 本地化
app.UseMapRequestLocalization();
// http调用链
app.UseCorrelationId();
// 虚拟文件系统
app.UseStaticFiles();
// 路由
app.UseRouting();
// 跨域
app.UseCors(DefaultCorsPolicyName);
// 认证
app.UseAuthentication();
app.UseDynamicClaims();
// 授权
app.UseAuthorization();
// Swagger
app.UseSwagger();
// Swagger可视化界面
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support Localization Management API");
});
// 审计日志
app.UseAuditing();
app.UseAbpSerilogEnrichers();
// 路由
app.UseConfiguredEndpoints();
}
app.UseForwardedHeaders();
// 本地化
app.UseMapRequestLocalization();
// http调用链
app.UseCorrelationId();
// 虚拟文件系统
app.UseStaticFiles();
// 路由
app.UseRouting();
// 跨域
app.UseCors(DefaultCorsPolicyName);
// 认证
app.UseAuthentication();
app.UseDynamicClaims();
// 授权
app.UseAuthorization();
// Swagger
app.UseSwagger();
// Swagger可视化界面
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Support Localization Management API");
});
// 审计日志
app.UseAuditing();
app.UseAbpSerilogEnrichers();
// 路由
app.UseConfiguredEndpoints();
}
}

3
aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/appsettings.Development.json

@ -30,7 +30,8 @@
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpFeatureManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpLocalizationManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None"
"AbpLocalizationManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpTextTemplating": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None"
},
"CAP": {
"EventBus": {

3
aspnet-core/services/LY.MicroService.LocalizationManagement.HttpApi.Host/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"ForwardedHeaders": "XForwardedFor,XForwardedProto"
},
"AllowedHosts": "*",
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",

3
aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.PlatformManagement.HttpApi.Host.dll"]

1
aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/LY.MicroService.PlatformManagement.HttpApi.Host.csproj

@ -52,6 +52,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\authorization\LINGYUN.Abp.Authorization.OrganizationUnits\LINGYUN.Abp.Authorization.OrganizationUnits.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.ExceptionHandling.Emailing\LINGYUN.Abp.ExceptionHandling.Emailing.csproj" />

14
aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.Configure.cs

@ -8,8 +8,10 @@ using LINGYUN.Platform.Localization;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
@ -283,12 +285,22 @@ public partial class PlatformManagementHttpApiHostModule
});
}
private void ConfigureMvc()
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureVirtualFileSystem()

11
aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/PlatformManagementHttpApiHostModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
using LINGYUN.Abp.Authorization.OrganizationUnits;
@ -88,6 +89,7 @@ namespace LY.MicroService.PlatformManagement;
typeof(AbpLocalizationCultureMapModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpAutofacModule)
)]
public partial class PlatformManagementHttpApiHostModule : AbpModule
@ -107,7 +109,6 @@ public partial class PlatformManagementHttpApiHostModule : AbpModule
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureMvc();
ConfigureDbContext();
ConfigureBlobStoring();
ConfigureLocalization();
@ -122,6 +123,7 @@ public partial class PlatformManagementHttpApiHostModule : AbpModule
ConfigureSwagger(context.Services);
ConfigureMultiTenancy(configuration);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
@ -150,10 +152,7 @@ public partial class PlatformManagementHttpApiHostModule : AbpModule
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
// 本地化
app.UseMapRequestLocalization();
// http调用链

3
aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.Development.json

@ -54,7 +54,8 @@
"AbpSaas": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpSettingManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpPermissionManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpLocalizationManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None"
"AbpLocalizationManagement": "Server=127.0.0.1;Database=Platform-v70;User Id=root;Password=123456;SslMode=None",
"AbpTextTemplating": "Server=127.0.0.1;Database=Platform-V70;User Id=root;Password=123456;SslMode=None"
},
"Features": {
"Validation": {

3
aspnet-core/services/LY.MicroService.PlatformManagement.HttpApi.Host/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"ForwardedHeaders": "XForwardedFor,XForwardedProto"
},
"AllowedHosts": "*",
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",

3
aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/Dockerfile

@ -8,4 +8,7 @@ EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.RealtimeMessage.HttpApi.Host.dll"]

1
aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/LY.MicroService.RealtimeMessage.HttpApi.Host.csproj

@ -56,6 +56,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\auditing\LINGYUN.Abp.AuditLogging.Elasticsearch\LINGYUN.Abp.AuditLogging.Elasticsearch.csproj" />
<ProjectReference Include="..\..\framework\authorization\LINGYUN.Abp.Authorization.OrganizationUnits\LINGYUN.Abp.Authorization.OrganizationUnits.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Features.LimitValidation.Redis.Client\LINGYUN.Abp.Features.LimitValidation.Redis.Client.csproj" />

14
aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.Configure.cs

@ -12,8 +12,10 @@ using LY.MicroService.RealtimeMessage.BackgroundJobs;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
@ -323,12 +325,22 @@ public partial class RealtimeMessageHttpApiHostModule
});
}
private void ConfigureMvc()
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureVirtualFileSystem()

11
aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/RealtimeMessageHttpApiHostModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
using LINGYUN.Abp.Authorization.OrganizationUnits;
@ -105,6 +106,7 @@ namespace LY.MicroService.RealtimeMessage;
typeof(AbpLocalizationCultureMapModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpAutofacModule)
)]
public partial class RealtimeMessageHttpApiHostModule : AbpModule
@ -128,7 +130,6 @@ public partial class RealtimeMessageHttpApiHostModule : AbpModule
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureMvc();
ConfigureDbContext();
ConfigureLocalization();
ConfigureNotifications();
@ -144,6 +145,7 @@ public partial class RealtimeMessageHttpApiHostModule : AbpModule
ConfigureJsonSerializer(configuration);
ConfigureBackgroundTasks(configuration);
ConfigureSwagger(context.Services);
ConfigureMvc(context.Services, configuration);
ConfigureCors(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLocking(context.Services, configuration);
@ -154,10 +156,7 @@ public partial class RealtimeMessageHttpApiHostModule : AbpModule
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
// 本地化
app.UseMapRequestLocalization();
// http调用链

3
aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"ForwardedHeaders": "XForwardedFor,XForwardedProto"
},
"AllowedHosts": "*",
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",

25
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/Dockerfile

@ -6,11 +6,32 @@ COPY . /app
## 有些定时作业可能需要建立独立的数据库连接, 可能跨不同的数据库, 配置一下MSSQL
## 解决连接SqlServer TLS版本过高问题
RUN sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf
RUN sed -i 's/TLSv1.2/TLSv1.0/g' /usr/lib/ssl/openssl.cnf
RUN sed -i 's/\[openssl_init\]/# \[openssl_init\]/g' /etc/ssl/openssl.cnf
RUN sed -i '$a\[openssl_init]' /etc/ssl/openssl.cnf
RUN sed -i '$a\providers = provider_sect' /etc/ssl/openssl.cnf
RUN sed -i '$a\ssl_conf = ssl_sect' /etc/ssl/openssl.cnf
RUN sed -i '$a\[provider_sect]' /etc/ssl/openssl.cnf
RUN sed -i '$a\default = default_sect' /etc/ssl/openssl.cnf
RUN sed -i '$a\legacy = legacy_sect' /etc/ssl/openssl.cnf
RUN sed -i '$a\[default_sect]' /etc/ssl/openssl.cnf
RUN sed -i '$a\activate = 1' /etc/ssl/openssl.cnf
RUN sed -i '$a\[legacy_sect]' /etc/ssl/openssl.cnf
RUN sed -i '$a\activate = 1' /etc/ssl/openssl.cnf
RUN sed -i '$a\[ssl_sect]' /etc/ssl/openssl.cnf
RUN sed -i '$a\system_default = system_default_sect' /etc/ssl/openssl.cnf
RUN sed -i '$a\[system_default_sect]' /etc/ssl/openssl.cnf
RUN sed -i '$a\CipherString = DEFAULT:@SECLEVEL=0' /etc/ssl/openssl.cnf
EXPOSE 80/tcp
VOLUME [ "./app/Logs" ]
VOLUME [ "./app/Modules" ]
RUN apt update
RUN apt install wget -y
ENTRYPOINT ["dotnet", "LY.MicroService.TaskManagement.HttpApi.Host.dll"]

1
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj

@ -49,6 +49,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\authorization\LINGYUN.Abp.Authorization.OrganizationUnits\LINGYUN.Abp.Authorization.OrganizationUnits.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.Data.DbMigrator\LINGYUN.Abp.Data.DbMigrator.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.EventBus.CAP\LINGYUN.Abp.EventBus.CAP.csproj" />
<ProjectReference Include="..\..\framework\common\LINGYUN.Abp.ExceptionHandling.Emailing\LINGYUN.Abp.ExceptionHandling.Emailing.csproj" />

14
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.Configure.cs

@ -9,7 +9,9 @@ using LINGYUN.Abp.TaskManagement.Localization;
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -286,12 +288,22 @@ public partial class TaskManagementHttpApiHostModule
});
}
private void ConfigureMvc()
private void ConfigureMvc(IServiceCollection services, IConfiguration configuration)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add((builder) =>
{
builder.Endpoints.MapHealthChecks(configuration["App:HealthChecks"] ?? "/healthz");
});
});
services.AddHealthChecks();
}
private void ConfigureVirtualFileSystem()

11
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.HttpOverrides;
using LINGYUN.Abp.AspNetCore.Mvc.Localization;
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper;
using LINGYUN.Abp.AuditLogging.Elasticsearch;
using LINGYUN.Abp.Authorization.OrganizationUnits;
@ -78,6 +79,7 @@ namespace LY.MicroService.TaskManagement;
typeof(AbpLocalizationCultureMapModule),
typeof(AbpHttpClientWrapperModule),
typeof(AbpAspNetCoreMvcWrapperModule),
typeof(AbpAspNetCoreHttpOverridesModule),
typeof(AbpCAPEventBusModule),
typeof(AbpAutofacModule)
)]
@ -99,7 +101,6 @@ public partial class TaskManagementHttpApiHostModule : AbpModule
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureMvc();
ConfigureDbContext();
ConfigureLocalization();
ConfigureBackgroundTasks();
@ -113,6 +114,7 @@ public partial class TaskManagementHttpApiHostModule : AbpModule
ConfigureMultiTenancy(configuration);
ConfigureSwagger(context.Services);
ConfigureJsonSerializer(configuration);
ConfigureMvc(context.Services, configuration);
ConfigureOpenTelemetry(context.Services, configuration);
ConfigureDistributedLock(context.Services, configuration);
ConfigureSecurity(context.Services, configuration, hostingEnvironment.IsDevelopment());
@ -123,10 +125,7 @@ public partial class TaskManagementHttpApiHostModule : AbpModule
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
app.UseAbpRequestLocalization();
app.UseStaticFiles();
app.UseCorrelationId();

3
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/appsettings.json

@ -2,6 +2,9 @@
"Clock": {
"Kind": "Local"
},
"Forwarded": {
"Headers": "XForwardedFor,XForwardedProto"
},
"StringEncryption": {
"DefaultPassPhrase": "s46c5q55nxpeS8Ra",
"InitVectorBytes": "s83ng0abvd02js84",

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save