Compare commits

7 Commits

Author SHA1 Message Date
0585818abf Merge branch 'temp' into deploy-dev 2024-07-18 16:23:33 +08:00
xuziqiang
9dfc3173ac Merge branch 'temp' into deploy-dev 2024-07-17 16:54:48 +08:00
xuziqiang
7690f72636 Merge branch 'deploy-dev' of http://123.60.103.97:3000/xuziqiang/SaaS-lib into deploy-dev 2024-07-17 10:54:29 +08:00
xuziqiang
211b013084 Merge branch 'temp' into deploy-dev 2024-07-17 10:54:24 +08:00
a94531bd57 Merge branch 'temp' into deploy-dev 2024-07-16 16:07:24 +08:00
7802223799 Merge branch 'temp' into deploy-dev 2024-07-12 17:07:16 +08:00
xuziqiang
e648da4b6e fix: 开启权限 2024-07-11 15:55:25 +08:00
433 changed files with 8372 additions and 45891 deletions

View File

@@ -26,68 +26,3 @@ Run `Volar: Switch TS Plugin on/off` from VSCode command palette.
4. Open the VSCode command palette
5. Search and run "Select TypeScript version" -> "Use workspace version"
test
#### 目录结构
|-- build 构建相关
|-- hx-ai-intelligent AI智能BSA系统
|-- mock 定义模拟数据(登录/登出/获取用户信息等)
|-- public 静态资源
|-- resources 资源文件
|-- src
|-- api 定义接口请求地址
|-- components 当前项目的公共组件
|-- ns-modal-form.vue 经过二次封装后的“弹窗”通用组件
|-- ns-steps.vue 经过二次封装后的“步骤条”通用组件
|-- config 配置文件
|-- app.config.ts(包含应用程序配置的对象包含api路径/组件配置等)
|-- directives 自定义指令在vue应用程序中设置全局指令
|-- enum 枚举用于http请求配置和错误代码的处理
|-- icon 图标svg图标文件
|-- router 路由
|-- store 状态管理
|-- theme 主题样式less/scss文件
|-- util 工具类
|-- debounce.ts 防抖函数(用于优化频繁出发的事件处理函数,例如窗口大小改变/滚动事件等)
|-- view 页面
|-- App.vue 根组件
|-- .env.development 当前项目开发环境配置
|-- .env.production 当前项目生产环境配置
|-- index.html 当前项目入口页
|-- tsconfig.json typescript配置
|-- vite.config.ts vite配置包含当前项目请求接口地址
|-- lib
|-- component 公共组件color/drawer/echarts/flowChart/form/modal/table/tree等组件的二次封装或将组件注册为插件形式使用
|-- paas 私有组件
|-- saas 私有组件
|-- type 类型定义
|-- use 自定义hook文件
|-- util http/axios等工具类
|-- node_modules 依赖包
|-- public 静态资源(不会被编译)
|-- src
|-- main.ts 入口文件(初始化saasInit函数的相关配置或服务的入口点。)
|-- type 声明定义应用程序的全局类型
|-- .env.development 开发环境配置
|-- .env.production 生产环境配置
|-- index.html 项目入口文件
|-- package.json 项目依赖包管理
|-- tsconfig.json typescript配置
|-- vite.config.ts vite配置
|-- README.md 项目说明
#### 开发前的相关配置或调试时需要本地启动项目
1、node官网下载并安装node环境node版本18.12以上稳定版。官网地址https://nodejs.cn/download/
node版本安装18.12以上版本npm版本安装9.6.0以上版即可
查看版本node -v npm -v
2、更换npm源为淘宝镜像
命令npm config set registry https://registry.npmmirror.com/
输入此命令查看是否完成修改npm config get registry
3、安装pnpm包管理器由于有时npm启动项目会因为存在重复配置及文件而报错使用pnpm可以避开这些问题将资源整合性能提高的同时也扩展了场景。
命令npm install -g pnpm
输入此命令查看是否完成安装pnpm -v
4、安装依赖包
命令pnpm install
5、启动hx-ai-intelligent 项目命令pnpm run ai 打包到线上命令pnpm run ai-build
6、启动hx-op项目 项目命令pnpm run ai-op 打包到线上命令pnpm run ai-op-build
7、如使用vscode编辑器可以在终端使用cmd终端启动项目powershell终端会报错

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -1,10 +1,6 @@
<template>
<a-config-provider :locale="locale">
<div style="width: 100%; height: 100%">
<a-spin :spinning="state.isLoading" size="large">
<router-view />
</a-spin>
</div>
<router-view />
</a-config-provider>
</template>
@@ -14,7 +10,6 @@
import { useRouter } from 'vue-router';
import Cookies from 'js-cookie';
import { message } from 'ant-design-vue';
import { items } from '/@/store/item';
export default defineComponent({
name: 'App',
@@ -42,8 +37,6 @@
return {
cachedViews,
locale: zhCN,
// 获得全局变量
state: items(),
};
},
});
@@ -79,129 +72,7 @@
}
}
.ns-form-body {
max-width: 1650px;
max-width: 1000px;
}
}
// 区域阴影
// .ns-list-table {
:deep(.ant-tabs) {
// // overflow: visible;
// box-shadow: @ns-content-box-shadow;
// border-radius: @ns-border-radius;
.ns-table-search {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
}
.ns-table-main {
border-bottom-left-radius: 0px !important;
border-bottom-right-radius: 0px !important;
}
// .ant-tabs-tabpane-active > div {
// height: 100%;
// }
}
// .ant-tabs .ns-table-main {
// border-bottom-left-radius: 0px !important;
// border-bottom-right-radius: 0px !important;
// }
// }
// 菜单icon(仅仅处理特殊需求未选中时icon与文字不同色)
:deep(.anticon) {
color: #8d96a3 !important;
}
:deep(.ant-menu-item-selected .ant-menu-item-only-child),
:deep(.ant-menu-submenu-selected .ant-menu-item-only-child),
:deep(.firstMenuItem-selected) {
.router-link-active > .anticon {
color: @white !important;
}
}
:deep(.ant-menu-item-active) {
.anticon {
color: @primary-color !important;
}
}
:deep(.ant-menu-submenu-active),
:deep(.ant-menu-submenu-open),
:deep(.ant-menu-item-selected) {
.ant-menu-submenu-title {
.anticon {
color: @primary-color !important;
}
}
}
@min-padding: 5px;
:deep(.ns-left-menu-space):not(.ns-left-menu-space-collapsed) {
.firstMenuItem-selected {
background-color: transparent !important;
}
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background-color: transparent;
&::before {
background-color: @primary-color;
opacity: 1;
height: calc(100% - @min-padding*2);
top: @min-padding;
border-radius: @ns-border-radius;
}
}
.ant-menu-item-active:not(.ant-menu-item-selected) {
background-color: transparent !important;
position: relative;
&::before {
content: '';
position: absolute;
width: 100%;
background-color: rgb(@primary-color, 0.1);
height: calc(100% - @min-padding*2);
top: @min-padding;
left: 0;
border-radius: @ns-border-radius;
z-index: 0;
}
}
// .ant-menu-inline .ant-menu-item-selected::after {
// content: '';
// position: absolute;
// top: 50%;
// right: -20px;
// transform: translateY(-50%);
// width: 3px !important;
// border-radius: 2px;
// background-color: red;
// height: 30px;
// z-index: 2;
// border: none;
// }
.ant-menu-title-content {
z-index: 1;
}
}
:deep(.nsHeader_action) {
.action {
font-size: 18px;
font-weight: bold;
letter-spacing: 0px;
line-height: 24px;
color: rgba(51, 51, 51, 1);
}
}
:deep(.ant-spin-nested-loading) {
min-height: 100%;
height: 100%;
}
:deep(.ant-spin-container) {
min-height: 100%;
height: 100%;
}
</style>

View File

@@ -1,31 +1,20 @@
const prefix = '/carbon-smart/api';
// 照明系统及相关接口
export enum lightingManage {
// 主页 ========================================================
// 获得分区与线路
getTree = prefix + '/illuminationCtrl/getCtrlPanelTree',
getArea = '/carbon-smart/IlluminationInfo/region',
// 修改线路的可用/禁用状态
setDisable = '/carbon-smart/api/illuminationCtrl/changePanelStatus',
// 获得设备 - 小灯泡
getBulbs = prefix + '/illuminationCtrl/getDeviceState',
setDisable = '/carbon-smart/IlluminationInfo/revisePanel',
// 主页 > 抽屉 > 控制面板 =======================================
// 计划列表tab页 ================================================
// 获取当前修改的内容对比数据
getChangeList = prefix + '/illuminationCtrl/getSceneChangeInfo',
// 提交当前修改
submitChangeList = prefix + '/illuminationCtrl/changeToSceneMode',
// 主页 > 抽屉 > 计划列表 =======================================
// 右侧表格修改数据提交
submitTableData = prefix + '/illuminationCtrl/refreshPlanStatus',
// 主页 > 抽屉 > 日志 ===========================================
// 获取日志
getLog = prefix + '/illuminationInfo/pageAbleLog',
// 获取日志详情
getLogDetail = prefix + '/illuminationInfo/fullLog',
// 获得计划列表tab页的表格数据
getPlanTable = '/carbon-smart/IlluminationPlan/selectPanelPlan',
// 获得计划列表tab页的穿梭框左侧数据
getLeftPlan = '/carbon-smart/IlluminationPlan/getPlan',
// 提交穿梭框被选择的数据
submitLeftPlan = '/carbon-smart/IlluminationPlan/joinPlan',
}

View File

@@ -1,29 +0,0 @@
const prefix = '/carbon-smart/api';
// 空调系统及相关接口
export enum airConditionControl {
// 主页 ======================================================
// 主页分区结构
getTree = prefix + '/airConditioningCtrl/getCtrlPanelTree',
// 主页小灯泡
getDeviceList = prefix + '/airConditioningCtrl/getDeviceState',
// 主页 > 抽屉 > 控制面板 =======================================
// 获得修改的比对列表
getChangeList = prefix + '/airConditioningCtrl/getSceneChangeInfo',
// 提交修改结果
submitChangeList = prefix + '/airConditioningCtrl/changeToSceneMode',
// 主页 > 抽屉 > 计划列表 =======================================
// 右侧表格修改数据提交
submitTableData = prefix + '/airConditioningCtrl/refreshPlanStatus',
// 主页 > 抽屉 > 日志 ===========================================
// 获取日志
getLog = prefix + '/airConditioningInfo/pageAbleLog',
// 获取日志详情
getLogDetail = prefix + '/airConditioningInfo/fullLog',
}

View File

@@ -1,7 +0,0 @@
import { BASE_URL } from './index';
export enum airConditioningSystemApi {
getVentHostCtrlList = `${BASE_URL}/api/ventHostCtrl/getDeviceState`, // 查询新风主机最新状态
getAcBoxCtrlList = `${BASE_URL}/api/acBoxCtrl/getDeviceState`, // 查询空调箱最新状态
getTempSysCtrlList = `${BASE_URL}/api/tempSysCtrl/getFloorHeatingState`, // 查询地暖最新状态
}

View File

@@ -1,10 +0,0 @@
import { BASE_URL } from '../index';
export enum alarmOverviewApi {
getAlarmEquipment = `${BASE_URL}/api/AlarmOverview/alarmEquipment`, //设备告警 数量
getAlarmEnergyConsumption = `${BASE_URL}/api/AlarmOverview/alarmEnergyConsumption`, //能碳告警 数量
getAlarmGateway = `${BASE_URL}/api/AlarmOverview/alarmGateway`, //网关告警 数量
getPriority = `${BASE_URL}/api/AlarmOverview/priority`, //优先级 数量
getProcessProgress = `${BASE_URL}/api/AlarmOverview/processProgress`, //进度 数量
getAlarmTrend = `${BASE_URL}/api/AlarmOverview/alarmTrend`, //30天告警 数量
}

View File

@@ -1,11 +0,0 @@
import { BASE_URL } from '../../index';
export enum deviceAlarms {
getTableList = `${BASE_URL}/api/AlarmEquipment/selectAlarmEquipment`, //设备告警分页
addOrUpNewData = `${BASE_URL}/api/AlarmEquipment/creatOrUpdate`, //设备告警添加 修改
del = `${BASE_URL}/api/AlarmEquipment/delete`, //设备告警删除
configGetTableList = `${BASE_URL}/api/AlarmEquipmentRule/selectAlarmEquipmentRule`, //配置设备告警分页
configAddOrUpNewData = `${BASE_URL}/api/AlarmEquipmentRule/creatOrUpdate`, //配置设备告警添加 修改
configFindById = `${BASE_URL}/api/AlarmEquipmentRule/findById`, //配置设备告警 查询详情
configDel = `${BASE_URL}/api/AlarmEquipmentRule/delete`, //配置设备告警删除
}

View File

@@ -1,11 +0,0 @@
import { BASE_URL } from '../../index';
export enum energyAlarms {
getTableList = `${BASE_URL}/api/AlarmEnergyConsumption/selectAlarmEnergyConsumption`, //能耗告警分页
addOrUpNewData = `${BASE_URL}/api/AlarmEnergyConsumption/creatOrUpdate`, //能耗告警添加 修改
del = `${BASE_URL}/api/AlarmEnergyConsumption/delete`, //能耗删除
configGetTableList = `${BASE_URL}/api/AlarmEnergyConsumptionRule/selectAlarmEnergyConsumptionRule`, //配置设备告警分页
configAddOrUpNewData = `${BASE_URL}/api/AlarmEnergyConsumptionRule/creatOrUpdate`, //配置设备告警添加 修改
configFindById = `${BASE_URL}/api/AlarmEnergyConsumptionRule/findById`, //配置设备告警 查询详情
configDel = `${BASE_URL}/api/AlarmEnergyConsumptionRule/delete`, //配置设备告警删除
}

View File

@@ -1,7 +0,0 @@
import { BASE_URL } from '../../index';
export enum notificationManagementApi {
getTableList = `${BASE_URL}/api/AlarmContactInformation/selectAlarmContactInformation`, //通知管理分页
upData = `${BASE_URL}/api/AlarmContactInformation/update`, //通知管理 修改
findById = `${BASE_URL}/api/AlarmContactInformation/findById`, //通知管理 查询详情
}

View File

@@ -1,9 +0,0 @@
import { BASE_URL } from '../index';
export enum energyAlarmApi {
getTableList = `${BASE_URL}/api/AlarmEnergyConsumptionLog/selectAlarmEnergyConsumptionLog`, //能碳告警 列表
getCodeList = `${BASE_URL}/api/AlarmEnergyConsumptionLog/selectErrorCodeList `, //能碳告警 列表
getSelectAlarmEnergyConsumptionLogStatusProcess = `${BASE_URL}/api/AlarmEnergyConsumptionLogStatusProcess/selectAlarmEnergyConsumptionLogStatusProcess`, //能碳告警 状态 没有创建工单log接口
noCreatOrUpdateLog = `${BASE_URL}/api/AlarmEnergyConsumptionLogStatusProcess/creatOrUpdate`, //能碳告警 状态 没有创建工单 添加 修改状态log
getEnergyGraph = `${BASE_URL}/energy/trigger/getEnergyGraph`, //能碳告警 状态 echarts图
}

View File

@@ -1,9 +0,0 @@
import { BASE_URL } from '../index';
export enum equipmentAlarmApi {
getTableList = `${BASE_URL}/api/AlarmEquipmentLog/selectAlarmEquipmentLog`, //设备告警 列表
getCodeList = `${BASE_URL}/api/AlarmEquipmentLog/selectErrorCodeList`, //设备告警 列表
getSelectAlarmEquipmentLogStatusProcess = `${BASE_URL}/api/AlarmEquipmentLogStatusProcess/selectAlarmEquipmentLogStatusProcess`, //设备告警 状态 没有创建工单log接口
noCreatOrUpdateLog = `${BASE_URL}/api/AlarmEquipmentLogStatusProcess/creatOrUpdate`, //设备告警 状态 没有创建工单 添加 修改状态log
getDeviceGraph = `${BASE_URL}/equipment/trigger/getDeviceGraph`, //设备告警 echats图
}

View File

@@ -1,4 +0,0 @@
import { BASE_URL } from '../index';
export enum gatewayAlarmApi {
getTableList = `${BASE_URL}/api/AlarmGatewayLog/selectAlarmGatewayLog`, //网关告警 列表
}

View File

@@ -0,0 +1,9 @@
export enum deviceAlarms {
getTableList = '/carbon-smart/api/AlarmEquipment/selectAlarmEquipment', //设备告警分页
addOrUpNewData = '/carbon-smart/api/AlarmEquipment/creatOrUpdate', //设备告警添加 修改
del = '/carbon-smart/api/AlarmEquipment/delete', //设备告警删除
configGetTableList = '/carbon-smart/api/AlarmEquipmentRule/selectAlarmEquipmentRule', //配置设备告警分页
configAddOrUpNewData = '/carbon-smart/api/AlarmEquipmentRule/creatOrUpdate', //配置设备告警添加 修改
configFindById = '/carbon-smart/api/AlarmEquipmentRule/findById', //配置设备告警 查询详情
configDel = '/carbon-smart/api/AlarmEquipmentRule/delete', //配置设备告警删除
}

View File

@@ -0,0 +1,9 @@
export enum energyAlarms {
getTableList = '/carbon-smart/api/AlarmEnergyConsumption/selectAlarmEnergyConsumption', //能耗告警分页
addOrUpNewData = '/carbon-smart/api/AlarmEnergyConsumption/creatOrUpdate', //能耗告警添加 修改
del = '/carbon-smart/api/AlarmEnergyConsumption/delete', //能耗删除
configGetTableList = '/carbon-smart/api/AlarmEnergyConsumptionRule/selectAlarmEnergyConsumptionRule', //配置设备告警分页
configAddOrUpNewData = '/carbon-smart/api/AlarmEnergyConsumptionRule/creatOrUpdate', //配置设备告警添加 修改
configFindById = '/carbon-smart/api/AlarmEnergyConsumptionRule/findById', //配置设备告警 查询详情
configDel = '/carbon-smart/api/AlarmEnergyConsumptionRule/delete', //配置设备告警删除
}

View File

@@ -0,0 +1,5 @@
export enum notificationManagementApi {
getTableList = '/carbon-smart/api/AlarmContactInformation/selectAlarmContactInformation', //通知管理分页
upData = '/carbon-smart/api/AlarmContactInformation/update', //通知管理 修改
findById = '/carbon-smart/api/AlarmContactInformation/findById', //通知管理 查询详情
}

View File

@@ -8,11 +8,6 @@ export enum carbonEmissionFactorLibrary {
getCarbonFactorTree = '/carbon-smart/api/carbon/emission/type/getCarbonFactorTree',
creat = '/carbon-smart/api/carbon/emission/type/creatOrUpdate',
delTreeNode = '/carbon-smart/api/carbon/emission/type/del',
move = '/carbon-smart/api/carbon/emission/type/move',
import = '/carbon-smart/api/carbon/emission/factor/import',
export = '/carbon-smart/api/carbon/emission/factor/export',
gasAndDatabase = '/carbon-smart/api/carbon/emission/factor/gasAndDatabase',
checkDel = '/carbon-smart/api/carbon/emission/factor/checkDel',
// 单位管理
dictionaryUnitManagement = '/carbon-smart/client/dict/dictionaryUnitManagement',
findOutermost = '/carbon-smart/client/dict/findOutermost',
@@ -20,7 +15,7 @@ export enum carbonEmissionFactorLibrary {
updateDictionary = '/carbon-smart/client/dict/updateDictionary',
delDictionary = '/carbon-smart/client/dict/delDictionary',
}
// 碳排管理-能耗统计接口
// 碳排管理-碳排统计接口
export enum energyConsumption {
getDicList = '/carbon-smart/client/dict/list',
pageList = '/carbon-smart/api/carbon/stats/pageList',
@@ -28,10 +23,6 @@ export enum energyConsumption {
creat = '/carbon-smart/api/carbon/stats/creat',
update = '/carbon-smart/api/carbon/stats/update',
del = '/carbon-smart/api/carbon/stats/del',
voucherDownloadList = '/carbon-smart/api/carbon/stats/voucherDownloadList',
energyAcquisition = '/carbon-smart/api/carbon/stats/energyAcquisition',
import = '/carbon-smart/api/carbon/stats/import',
export = '/carbon-smart/api/carbon/stats/export',
}
// 碳排管理-碳排速算接口
export enum quickCalculation {
@@ -44,70 +35,4 @@ export enum quickCalculation {
// 碳排管理-碳排统计接口
export enum carbonEmission {
carbonEmissionStatistics = '/carbon-smart/api/carbon/energy/correlation/carbonEmissionStatistics',
}
// 碳盘查接口
export enum carbonInventoryCheck {
carbonInventoryList = '/carbon-smart/api/carbon/report/carbonInventoryList',
createOrUpdate = '/carbon-smart/api/carbon/report/createOrUpdate',
findById = '/carbon-smart/api/carbon/report/findById',
delete = '/carbon-smart/api/carbon/report/delete',
downloadZip = '/carbon-smart/api/carbon/report/downloadZip',
// 填报页面接口
// 最左侧碳盘查报告树
getCategoryTree = '/carbon-smart/api/carbon/inventory/contact/getCategoryTree',
updateCategoryTree = '/carbon-smart/api/carbon/inventory/contact/update',
// 排放源中左侧树
getInventoryTree = '/carbon-smart/api/carbon/inventory/getInventoryTree',
create = '/carbon-smart/api/carbon/inventory/create',
update = '/carbon-smart/api/carbon/inventory/update',
del = '/carbon-smart/api/carbon/inventory/del',
// 获取排放源表格数据
findUnitById = '/carbon-smart/api/carbon/inventory/findById',
getDetailsList = '/carbon-smart/api/carbon/inventory/details/getDetailsList',
updateTable = '/carbon-smart/api/carbon/inventory/details/update',
voucherDownloadList = '/carbon-smart/api/carbon/inventory/details/voucherDownloadList',
nodeCancellationConsumption = '/carbon-smart/api/carbon/inventory/details/nodeCancellationConsumption',
// 排放统计接口
emissionStatistic = '/carbon-smart/api/carbon/inventory/emissionStatistic',
// 碳排流向
carbonFlowDirection = '/carbon-smart/api/carbon/inventory/carbonFlowDirection',
}
// 碳资产
export enum carbonAssets {
// 全部
carbonAssets = '/carbon-smart/api/carbon/trade/details/carbonAssets',
// 详情
carbonDetailsList = '/carbon-smart/api/carbon/trade/details/carbonDetailsList',
createOrUpdate = '/carbon-smart/api/carbon/trade/details/createOrUpdate',
delete = '/carbon-smart/api/carbon/trade/details/delete',
quotaStatistics = '/carbon-smart/api/carbon/trade/details/quotaStatistics',
import = '/carbon-smart/api/carbon/trade/details/import',
export = '/carbon-smart/api/carbon/trade/details/export',
}
// 上传图片接口
export enum uploadPic {
uploadfiles = '/carbon-smart/api/common/file/uploadfiles',
select = '/carbon-smart/api/common/file/select',
uploadfile = '/carbon-smart/api/common/file/uploadfile',
download = '/carbon-smart/api/common/file/download',
downloadZip = '/carbon-smart/api/common/file/downloadZip',
}
// 碳规划
export enum carbonPlanning {
// 全部
whole = '/carbon-smart/api/carbon/planning/whole',
// 详情
searchListByYear = '/carbon-smart/api/carbon/planning/searchListByYear',
searchListByMonth = '/carbon-smart/api/carbon/planning/searchListByMonth',
yearAndMonthAchievement = '/carbon-smart/api/carbon/planning/yearAndMonthAchievement',
annualElectricityConsumption = '/carbon-smart/api/carbon/planning/annualElectricityConsumption',
electricityUsageBackThen = '/carbon-smart/api/carbon/planning/electricityUsageBackThen',
detailedStatisticalDataTable = '/carbon-smart/api/carbon/planning/detailedStatisticalDataTable',
detailedStatisticalDataChart = '/carbon-smart/api/carbon/planning/detailedStatisticalDataChart',
batchOrUpdate = '/carbon-smart/api/carbon/planning/batchOrUpdate',
addNodes = '/carbon-smart/api/carbon/planning/addNodes',
benchmarkSetting = '/carbon-smart/api/carbon/planning/benchmarkSetting',
monthBenchmarkSetting = '/carbon-smart/api/carbon/planning/monthBenchmarkSetting',
benchmarkSubmit = '/carbon-smart/api/carbon/planning/benchmarkSubmit',
autoObtained = '/carbon-smart/api/carbon/planning/autoObtained',
}
}

View File

@@ -1,10 +0,0 @@
import { BASE_URL } from './index';
export enum coldAndHeatSourcesApi {
getUserWaterPumpState = `${BASE_URL}/api/tempSysCtrl/getUserWaterPumpState`, // 用户水泵查询最新状态
getLandWaterPumpState = `${BASE_URL}/api/tempSysCtrl/getLandWaterPumpState`, // 地源水泵查询最新状态
getLandHeatPumpState = `${BASE_URL}/api/tempSysCtrl/getLandHeatPumpState`, //螺旋式地源热泵 - 查询最新状态
getEnergyTankState = `${BASE_URL}/api/tempSysCtrl/getEnergyTankState`, //冷热水双蓄储能罐 - 查询最新状态
getCoolPumpState = `${BASE_URL}/api/tempSysCtrl/getCoolPumpState`, //释冷泵 - 查询最新状态
getAirHeatPumpState = `${BASE_URL}/api/tempSysCtrl/getAirHeatPumpState`, //空气源热泵 - 查询最新状态
}

View File

@@ -28,12 +28,4 @@ export enum group {
queryFormula = `${BASE_URL}/deviceGroup/queryFormula`, // 公式查询
dropGroupFilter = `${BASE_URL}/deviceGroup/dropGroupFilter`, // 分组列表查询
dropGroupInfoFilter = `${BASE_URL}/deviceGroup/dropGroupInfoFilter`, // 计算列表查询
queryDeviceToEnergy = `${BASE_URL}/deviceGroup/queryDeviceToEnergy`, // 能耗监测用查询设备(能耗监测设备树)
importGroup = `${BASE_URL}/deviceGroup/importGroup`, // 批量导入计算节点
getCarbonGroupList = `${BASE_URL}/deviceGroup/carbonEmissions/getGroupList`, // 分组管理-碳排放-分组查询设备
deleteCarbonDevice = `${BASE_URL}/deviceGroup/carbonEmissions/deleteDevice`, // 分组管理-碳排放-删除设备
addCarbonDevice = `${BASE_URL}/deviceGroup/carbonEmissions/addDevice`, // 分组管理-碳排放-添加设备
updateCarbonFactor = `${BASE_URL}/deviceGroup/carbonEmissions/updateFactor`, // 分组管理-碳排放-设置因子
}

View File

@@ -1,6 +0,0 @@
export enum dict {
dictionaryUnitManagement = '/carbon-smart/client/dict/dictionaryUnitManagement',
updateDictionary = '/carbon-smart/client/dict/updateDictionary',
createDictionary = '/carbon-smart/client/dict/createDictionary',
delDictionary = '/carbon-smart/client/dict/delDictionary',
}

View File

@@ -1,6 +0,0 @@
import { BASE_URL } from './index';
export enum electricDoorApi {
getDeviceState = `${BASE_URL}/api/eleDoorCtrl/getDeviceState`, // 查询设备最新状态
getDeviceRecordList = `${BASE_URL}/api/eleDoorCtrl/getDeviceRecordList`, // 查询设备日志列表
}

View File

@@ -40,33 +40,44 @@ export const dict = async ({
};
/**
* 获取所有枚举(参数为数组,可以不传参)
* 获取所有枚举(无需传参)
*/
export const getAllEnum = async ({
api = `${BASE_URL}/operation/enum/getAllEnum`,
params = {},
keyField = 'dicKey',
transform = (res: any) => res,
}: dictHttpConfig) => {
const res = await http.post(api, params);
return Promise.resolve(res);
const dictMap = JSON.parse(sessionStorage.getItem('dictMap') || '{}') as Object;
const key = get(params, keyField) as keyof typeof dictMap;
if (!dictMap.hasOwnProperty(key)) {
const res = await http.post(api, params);
const options = get(transform(res), `data.${key}`);
dictMap[key] = options;
sessionStorage.setItem('dictMap', JSON.stringify(dictMap));
}
return Promise.resolve({ data: { data: get(dictMap, key) } });
};
/**
* 获取单个枚举(需传参,参数 enumType)
* 获取单个枚举(需传参,参数 enumType)
*/
export const getEnum = async ({
api = `${BASE_URL}/operation/enum/getEnum`,
params = {},
keyField = 'dicKey',
transform = (res: any) => res,
}: dictHttpConfig) => {
const res = await http.get(api, params);
return Promise.resolve(res);
};
/**
* 获取谈规划单位(需传参,参数 enumType)
*/
export const getEnumEnergy = async ({
api = `${BASE_URL}/operation/enum/getEnumEnergy`,
params = {},
}: dictHttpConfig) => {
const res = await http.get(api, params);
return Promise.resolve(res);
};
const dictMap = JSON.parse(sessionStorage.getItem('dictMap') || '{}') as Object;
const key = get(params, keyField) as keyof typeof dictMap;
if (!dictMap.hasOwnProperty(key)) {
const res = await http.post(api, params);
const options = get(transform(res), `data.${key}`);
dictMap[key] = options;
sessionStorage.setItem('dictMap', JSON.stringify(dictMap));
}
return Promise.resolve({ data: { data: get(dictMap, key) } });
};

View File

@@ -1,6 +0,0 @@
import { BASE_URL } from './index';
export enum liftSystemApi {
getDeviceState = `${BASE_URL}/api/elevatorCtrl/getDeviceState`, // 查询设备最新状态
getDeviceRecordList = `${BASE_URL}/api/elevatorCtrl/getDeviceRecordList`, // 查询设备日志列表
}

View File

@@ -1,4 +0,0 @@
import { BASE_URL } from './index';
export enum menuS {
queryMenuPage = `${BASE_URL}/deviceInfo/queryDevicePage`, // 菜单列表
}

View File

@@ -1,28 +1,13 @@
// 设备监测
export enum deviceMonitor {
// getTableList = '/carbon/emission/factor/queryCarbonFactorPage',
// addNewData = '/carbon/emission/factor/creatOrUpdate',
// editUser = '/carbon-smart/api/user/edit',
// frozen = '/carbon-smart/api/user/frozen',
// resetPwd = '/carbon-smart/api/user/resetPwd',
// del = '/carbon-smart/api/user/del',
// batchDel = '/carbon-smart/api/user/batchDel',
// getCarbonFactorTree = '/carbon/emission/type/getCarbonFactorTree',
// queryDeptTree = '/carbon-smart/api/user/queryDeptTree',
// queryUserPerList = '/carbon-smart/api/user/queryUserPerList',
getDeviceGraph = '/carbon-smart/api/monitor/getDeviceGraph',
getDevicePointToMonitor = '/carbon-smart/api/monitor/getDevicePointToMonitor',
}
// 能耗监测
export enum energyMonitor {
getDeviceOrNodeEnergyGraph = '/carbon-smart/api/monitor/getDeviceOrNodeEnergyGraph',
getDeviceOrNodeEnergyAnalyse = '/carbon-smart/api/monitor/getDeviceOrNodeEnergyAnalyse',
}
// 环境监测
export enum environmentMonitor {
getDeviceStatus = '/carbon-smart/api/monitor/getDeviceStatus', //环境监测-获取设备状态
getDeviceAverages = '/carbon-smart/api/monitor/getDeviceAverages', //环境监测-获取设备平均值
queryDeviceArea = '/carbon-smart/deviceInfo/queryDeviceArea', //查询区域位置组成树结构
getDeviceHotMap = '/carbon-smart/api/monitor/getDeviceHotMap', //环境监测-获取环境热力图
getDeviceHistory = '/carbon-smart/api/monitor/getDeviceHistory', //环境监测-历史数据-获取环境设备历史数据
getDeviceAveragesByRate = '/carbon-smart/api/monitor/getDeviceAveragesByRate', //环境监测-平均数据-获取环境设备平均数据
queryDeviceInfoListPage = '/carbon-smart/api/monitor/queryDeviceInfoListPage', //环境监测-根据条件查询设备数据(分页) 配置监测点位
startUpDevice = '/carbon-smart/api/monitor/startUpDevice', // 环境监测-配置启动设备
stopDevice = '/carbon-smart/api/monitor/stopDevice', // 环境监测-停用设备
}

View File

@@ -1,15 +0,0 @@
const prefix = '/carbon-smart/api';
// 照明系统及相关接口
export enum planManage {
/**
* @param deviceType 设备类型(1照明,2空调,3排风扇,4风幕机,5电动窗,6给排水)
*/
// 获得未激活的计划
getTransData = prefix + '/deviceCtrlPlan/getDeActivatedPlanList',
// 获得激活的计划
getTableData = prefix + '/deviceCtrlPlan/getActivatedPlanList',
// 提交计划状态修改
submitTransData = prefix + '/deviceCtrlPlan/activePlanByIdList',
// 用于确认当前是否有计划正在运行
getRunningPlan = prefix + '/deviceCtrlPlan/getRunningPlan',
}

View File

@@ -1,9 +0,0 @@
import { BASE_URL } from './index';
export enum planToAddApi {
getActivatedPlanTree = `${BASE_URL}/api/deviceCtrlPlan/getPlanLibTree`, //计划树
getActivatedPlanListByTree = `${BASE_URL}/api/deviceCtrlPlan/getPageAblePlanListByTree`, //计划列表
updPlan = `${BASE_URL}/api/deviceCtrlPlan/updateCtrlPlan`, //修改计划
delPlan = `${BASE_URL}/api/deviceCtrlPlan/deleteCtrlPlanByIdList`, //修改计划
addPlan = `${BASE_URL}/api/deviceCtrlPlan/addCtrlPlan`, //添加计划
}

View File

@@ -1,54 +0,0 @@
// 前缀
const prefix = '/carbon-smart/api';
// 通风系统相关接口
export enum ventilating {
//传感器获取数据
getSensorData = prefix + '/sensorCtrl/getMultiFuncSensorState',
// 排风扇相关 =============================================
// 获得排风扇系统的树形结构
getTree1 = prefix + '/ventilatingFanCtrl/getCtrlPanelTree',
// 获得排风扇的 场景/禁用 修改数据
getChangeList1 = prefix + '/ventilatingFanCtrl/getSceneChangeInfo',
// 提交排风扇的修改内容
sendChangeList1 = prefix + '/ventilatingFanCtrl/changeToSceneMode',
// 获得排风扇的设备状态
getDevice1 = prefix + '/ventilatingFanCtrl/getDeviceState',
// 提交排风扇的修改内容
submitTableData1 = prefix + '/ventilatingFanCtrl/refreshPlanStatus',
// 排风扇日志
getLog1 = prefix + '/ventilatingFanInfo/pageAbleLog',
// 排风扇日志详情
getLogDetail1 = prefix + '/ventilatingFanInfo/fullLog',
// 风幕机相关 =============================================
// 获得风幕机的树形结构
getTree2 = prefix + '/airCurtainMachineCtrl/getCtrlPanelTree',
// 获得风幕机的 场景/禁用 修改数据
getChangeList2 = prefix + '/airCurtainMachineCtrl/getSceneChangeInfo',
// 提交风幕机的修改内容
sendChangeList2 = prefix + '/airCurtainMachineCtrl/changeToSceneMode',
// 获得风幕机的设备状态
getDevice2 = prefix + '/airCurtainMachineCtrl/getDeviceState',
// 提交风幕机的修改内容
submitTableData2 = prefix + '/airCurtainMachineCtrl/refreshPlanStatus',
// 风幕机日志
getLog2 = prefix + '/airCurtainMachineInfo/pageAbleLog',
// 风幕机日志详情
getLogDetail2 = prefix + '/airCurtainMachineInfo/fullLog',
// 电动窗相关 =============================================
// 获得电动窗的树形结构
getTree3 = prefix + '/eleOperatedWindowCtrl/getCtrlPanelTree',
// 获得电动窗的 场景/禁用 修改数据
getChangeList3 = prefix + '/eleOperatedWindowCtrl/getSceneChangeInfo',
// 提交电动窗的修改内容
sendChangeList3 = prefix + '/eleOperatedWindowCtrl/changeToSceneMode',
// 获得电动窗的设备状态
getDevice3 = prefix + '/eleOperatedWindowCtrl/getDeviceState',
// 提交电动窗的修改内容
submitTableData3 = prefix + '/eleOperatedWindowCtrl/refreshPlanStatus',
// 电动窗日志
getLog3 = prefix + '/eleOperatedWindowInfo/pageAbleLog',
// 电动窗日志详情
getLogDetail3 = prefix + '/eleOperatedWindowInfo/fullLog',
}

View File

@@ -1,26 +0,0 @@
// 前缀
const prefix = '/carbon-smart/api';
// 通风系统相关接口
export enum waterSys {
// 首页 ====================================================
// 获得污水池状态
getPool1 = prefix + '/waterSysCtrl/getSewagePoolState',
// 获得阀门状态
getValve = prefix + '/waterSysCtrl/getValveState',
// 获得集水池状态
getPool2 = prefix + '/waterSysCtrl/getCollectPoolState',
// 获得水泵状态
getPump = prefix + '/waterSysCtrl/getPumpState',
// 提交场景模式修改
submitList = prefix + '/waterSysCtrl/changeToSceneMode',
// 计划 tab1 ===============================================
submitTableData = prefix + '/waterSysCtrl/refreshPlanStatus',
// 日志 tab2 ===============================================
// 获得设备日志
getLog = prefix + '/waterSysInfo/pageAbleLog',
// 获得日志详情
getLogDetail = prefix + '/waterSysInfo/fullLog',
}

View File

@@ -1,6 +0,0 @@
import { withInstall } from '/@/utils';
import basicDrawer from './src/BasicDrawer.vue';
export const BasicDrawer = withInstall(basicDrawer);
export * from './src/typing';
export { useDrawer, useDrawerInner } from './src/useDrawer';

View File

@@ -1,255 +0,0 @@
<template>
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
<template #title v-if="!$slots.title">
<DrawerHeader :title="getMergeProps.title" :isDetail="isDetail" :showDetailBack="showDetailBack" @close="onClose">
<template #titleToolbar>
<slot name="titleToolbar"></slot>
</template>
</DrawerHeader>
</template>
<template v-else #title>
<slot name="title"></slot>
</template>
<ScrollContainer :style="getScrollContentStyle" v-loading="getLoading" :loading-tip="loadingText || t('common.loadingText')">
<slot></slot>
</ScrollContainer>
<DrawerFooter v-bind="getProps" @close="onClose" @ok="handleOk" :height="getFooterHeight">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</DrawerFooter>
</Drawer>
</template>
<script lang="ts">
import type { DrawerInstance, DrawerProps } from './typing';
import type { CSSProperties } from 'vue';
import { defineComponent, ref, computed, watch, unref, nextTick, toRaw, getCurrentInstance } from 'vue';
import { Drawer } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { isFunction, isNumber } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import DrawerFooter from './components/DrawerFooter.vue';
import DrawerHeader from './components/DrawerHeader.vue';
import { ScrollContainer } from '/@/components/Container';
import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { cloneDeep } from 'lodash-es';
export default defineComponent({
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
inheritAttrs: false,
props: basicProps,
emits: ['visible-change', 'open-change', 'ok', 'close', 'register'],
setup(props, { emit }) {
const visibleRef = ref(false);
const attrs = useAttrs();
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
const { t } = useI18n();
const { prefixVar, prefixCls } = useDesign('basic-drawer');
const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps,
emitVisible: undefined,
};
const instance = getCurrentInstance();
instance && emit('register', drawerInstance, instance.uid);
const getMergeProps = computed((): DrawerProps => {
// update-begin--author:liaozhiyang---date:20240320---for【QQYUN-8389】vue3.4以上版本导致角色抽屉隐藏footer逻辑错误toRaw改成cloneDeep否则props的变化不会触发computed
return { ...deepMerge(cloneDeep(props), unref(propsRef)) };
// update-end--author:liaozhiyang---date:20240320---for【QQYUN-8389】vue3.4以上版本导致角色抽屉隐藏footer逻辑错误toRaw改成cloneDeep否则props的变化不会触发computed
});
const getProps = computed((): DrawerProps => {
// update-begin--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
const opt = {
placement: 'right',
...unref(attrs),
...unref(getMergeProps),
open: unref(visibleRef),
};
// update-end--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
opt.title = undefined;
let { isDetail, width, wrapClassName, getContainer } = opt;
if (isDetail) {
if (!width) {
opt.width = '100%';
}
const detailCls = `${prefixCls}__detail`;
wrapClassName = opt['class'] ? opt['class'] : wrapClassName;
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
if (!getContainer) {
// TODO type error?
opt.getContainer = `.${prefixVar}-layout-content` as any;
}
}
console.log('getProps:opt',opt);
return opt as DrawerProps;
});
const getBindValues = computed((): DrawerProps => {
return {
...attrs,
...unref(getProps),
};
});
// Custom implementation of the bottom button,
const getFooterHeight = computed(() => {
const { footerHeight, showFooter } = unref(getProps);
if (showFooter && footerHeight) {
return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
}
return `0px`;
});
const getScrollContentStyle = computed((): CSSProperties => {
const footerHeight = unref(getFooterHeight);
return {
position: 'relative',
height: `calc(100% - ${footerHeight})`,
};
});
const getLoading = computed(() => {
return !!unref(getProps)?.loading;
});
watch(
() => props.visible,
(newVal, oldVal) => {
if (newVal !== oldVal) visibleRef.value = newVal;
},
{ deep: true }
);
watch(
() => props.open,
(newVal, oldVal) => {
if (newVal !== oldVal) visibleRef.value = newVal;
},
{ deep: true }
);
watch(
() => visibleRef.value,
(visible) => {
nextTick(() => {
emit('visible-change', visible);
emit('open-change', visible);
instance && drawerInstance.emitVisible?.(visible, instance.uid);
});
}
);
// Cancel event
async function onClose(e: Recordable) {
const { closeFunc } = unref(getProps);
emit('close', e);
if (closeFunc && isFunction(closeFunc)) {
const res = await closeFunc();
visibleRef.value = !res;
return;
}
visibleRef.value = false;
}
function setDrawerProps(props: Partial<DrawerProps>): void {
// Keep the last setDrawerProps
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible;
}
if (Reflect.has(props, 'open')) {
visibleRef.value = !!props.open;
}
}
function handleOk() {
emit('ok');
}
return {
onClose,
t,
prefixCls,
getMergeProps: getMergeProps as any,
getScrollContentStyle,
getProps: getProps as any,
getLoading,
getBindValues,
getFooterHeight,
handleOk,
};
},
});
</script>
<style lang="less">
@header-height: 60px;
@detail-header-height: 40px;
@prefix-cls: ~'@{namespace}-basic-drawer';
@prefix-cls-detail: ~'@{namespace}-basic-drawer__detail';
.@{prefix-cls} {
.ant-drawer-wrapper-body {
overflow: hidden;
}
.ant-drawer-close {
&:hover {
color: @error-color;
}
}
.ant-drawer-body {
height: calc(100% - @header-height);
padding: 0;
background-color: @component-background;
.scrollbar__wrap {
padding: 16px !important;
margin-bottom: 0 !important;
}
> .scrollbar > .scrollbar__bar.is-horizontal {
display: none;
}
}
}
.@{prefix-cls-detail} {
position: absolute;
.ant-drawer-header {
width: 100%;
height: @detail-header-height;
padding: 0;
border-top: 1px solid @border-color-base;
box-sizing: border-box;
}
.ant-drawer-title {
height: 100%;
}
.ant-drawer-close {
height: @detail-header-height;
line-height: @detail-header-height;
}
.scrollbar__wrap {
padding: 0 !important;
}
.ant-drawer-body {
height: calc(100% - @detail-header-height);
}
}
</style>

View File

@@ -1,75 +0,0 @@
<template>
<div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer">
<template v-if="!$slots.footer">
<slot name="insertFooter"></slot>
<a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn">
{{ cancelText }}
</a-button>
<slot name="centerFooter"></slot>
<a-button :type="okType" @click="handleOk" v-bind="okButtonProps" class="mr-2" :loading="confirmLoading" v-if="showOkBtn">
{{ okText }}
</a-button>
<slot name="appendFooter"></slot>
</template>
<template v-else>
<slot name="footer"></slot>
</template>
</div>
</template>
<script lang="ts">
import type { CSSProperties } from 'vue';
import { defineComponent, computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { footerProps } from '../props';
export default defineComponent({
name: 'BasicDrawerFooter',
props: {
...footerProps,
height: {
type: String,
default: '60px',
},
},
emits: ['ok', 'close'],
setup(props, { emit }) {
const { prefixCls } = useDesign('basic-drawer-footer');
const getStyle = computed((): CSSProperties => {
const heightStr = `${props.height}`;
return {
height: heightStr,
lineHeight: heightStr,
};
});
function handleOk() {
emit('ok');
}
function handleClose() {
emit('close');
}
return { handleOk, prefixCls, handleClose, getStyle };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-drawer-footer';
@footer-height: 60px;
.@{prefix-cls} {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 12px 0 20px;
text-align: right;
background-color: @component-background;
border-top: 1px solid @border-color-base;
> * {
margin-right: 8px;
}
}
</style>

View File

@@ -1,74 +0,0 @@
<template>
<BasicTitle v-if="!isDetail" :class="[prefixCls, 'is-drawer']">
<slot name="title"></slot>
{{ !$slots.title ? title : '' }}
</BasicTitle>
<div :class="[prefixCls, `${prefixCls}--detail`]" v-else>
<span :class="`${prefixCls}__twrap`">
<span @click="handleClose" v-if="showDetailBack">
<ArrowLeftOutlined :class="`${prefixCls}__back`" />
</span>
<span v-if="title">{{ title }}</span>
</span>
<span :class="`${prefixCls}__toolbar`">
<slot name="titleToolbar"></slot>
</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BasicTitle } from '/@/components/Basic';
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'BasicDrawerHeader',
components: { BasicTitle, ArrowLeftOutlined },
props: {
isDetail: propTypes.bool,
showDetailBack: propTypes.bool,
title: propTypes.string,
},
emits: ['close'],
setup(_, { emit }) {
const { prefixCls } = useDesign('basic-drawer-header');
function handleClose() {
emit('close');
}
return { prefixCls, handleClose };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-drawer-header';
@footer-height: 60px;
.@{prefix-cls} {
display: flex;
height: 100%;
align-items: center;
&__back {
padding: 0 12px;
cursor: pointer;
&:hover {
color: @primary-color;
}
}
&__twrap {
flex: 1;
}
&__toolbar {
padding-right: 50px;
}
}
</style>

View File

@@ -1,46 +0,0 @@
import type { PropType } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export const footerProps = {
confirmLoading: { type: Boolean },
/**
* @description: Show close button
*/
showCancelBtn: { type: Boolean, default: true },
cancelButtonProps: Object as PropType<Recordable>,
cancelText: { type: String, default: t('common.cancelText') },
/**
* @description: Show confirmation button
*/
showOkBtn: { type: Boolean, default: true },
okButtonProps: Object as PropType<Recordable>,
okText: { type: String, default: t('common.okText') },
okType: { type: String, default: 'primary' },
showFooter: { type: Boolean },
footerHeight: {
type: [String, Number] as PropType<string | number>,
default: 60,
},
};
export const basicProps = {
class: {type: [String, Object, Array]},
isDetail: { type: Boolean },
title: { type: String, default: '' },
loadingText: { type: String },
showDetailBack: { type: Boolean, default: true },
visible: { type: Boolean },
open: { type: Boolean },
loading: { type: Boolean },
maskClosable: { type: Boolean, default: true },
getContainer: {
type: [Object, String] as PropType<any>,
},
closeFunc: {
type: [Function, Object] as PropType<any>,
default: null,
},
destroyOnClose: { type: Boolean },
...footerProps,
};

View File

@@ -1,199 +0,0 @@
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
import type { ScrollContainerOptions } from '/@/components/Container/index';
export interface DrawerInstance {
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void;
emitVisible?: (visible: boolean, uid: number) => void;
}
export interface ReturnMethods extends DrawerInstance {
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
closeDrawer: () => void;
getVisible?: ComputedRef<boolean>;
getOpen?: ComputedRef<boolean>;
}
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
export interface ReturnInnerMethods extends DrawerInstance {
closeDrawer: () => void;
changeLoading: (loading: boolean) => void;
changeOkLoading: (loading: boolean) => void;
getVisible?: ComputedRef<boolean>;
getOpen?: ComputedRef<boolean>;
}
export type UseDrawerReturnType = [RegisterFn, ReturnMethods];
export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods];
export interface DrawerFooterProps {
showOkBtn: boolean;
showCancelBtn: boolean;
/**
* Text of the Cancel button
* @default 'cancel'
* @type string
*/
cancelText: string;
/**
* Text of the OK button
* @default 'OK'
* @type string
*/
okText: string;
/**
* Button type of the OK button
* @default 'primary'
* @type string
*/
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default';
/**
* The ok button props, follow jsx rules
* @type object
*/
okButtonProps: { props: ButtonProps; on: {} };
/**
* The cancel button props, follow jsx rules
* @type object
*/
cancelButtonProps: { props: ButtonProps; on: {} };
/**
* Whether to apply loading visual effect for OK button or not
* @default false
* @type boolean
*/
confirmLoading: boolean;
showFooter: boolean;
footerHeight: string | number;
}
export interface DrawerProps extends DrawerFooterProps {
isDetail?: boolean;
loading?: boolean;
showDetailBack?: boolean;
visible?: boolean;
open?: boolean;
/**
* Built-in ScrollContainer component configuration
* @type ScrollContainerOptions
*/
scrollOptions?: ScrollContainerOptions;
closeFunc?: () => Promise<any>;
triggerWindowResize?: boolean;
/**
* Whether a close (x) button is visible on top right of the Drawer dialog or not.
* @default true
* @type boolean
*/
closable?: boolean;
/**
* Whether to unmount child components on closing drawer or not.
* @default false
* @type boolean
*/
destroyOnClose?: boolean;
/**
* Return the mounted node for Drawer.
* @default 'body'
* @type any ( HTMLElement| () => HTMLElement | string)
*/
getContainer?: () => HTMLElement | string;
/**
* Whether to show mask or not.
* @default true
* @type boolean
*/
mask?: boolean;
/**
* Clicking on the mask (area outside the Drawer) to close the Drawer or not.
* @default true
* @type boolean
*/
maskClosable?: boolean;
/**
* Style for Drawer's mask element.
* @default {}
* @type object
*/
maskStyle?: CSSProperties;
/**
* The title for Drawer.
* @type any (string | slot)
*/
title?: VNodeChild | JSX.Element;
/**
* The class name of the container of the Drawer dialog.
* @type string
*/
class?: string;
// 兼容老版本的写法后续可能会删除优先写class
wrapClassName?: string;
/**
* Style of wrapper element which **contains mask** compare to `drawerStyle`
* @type object
*/
wrapStyle?: CSSProperties;
/**
* Style of the popup layer element
* @type object
*/
drawerStyle?: CSSProperties;
/**
* Style of floating layer, typically used for adjusting its position.
* @type object
*/
bodyStyle?: CSSProperties;
headerStyle?: CSSProperties;
/**
* Width of the Drawer dialog.
* @default 256
* @type string | number
*/
width?: string | number;
/**
* placement is top or bottom, height of the Drawer dialog.
* @type string | number
*/
height?: string | number;
/**
* The z-index of the Drawer.
* @default 1000
* @type number
*/
zIndex?: number;
/**
* The placement of the Drawer.
* @default 'right'
* @type string
*/
placement?: 'top' | 'right' | 'bottom' | 'left';
afterVisibleChange?: (visible?: boolean) => void;
keyboard?: boolean;
/**
* Specify a callback that will be called when a user clicks mask, close button or Cancel button.
*/
onClose?: (e?: Event) => void;
}
export interface DrawerActionType {
scrollBottom: () => void;
scrollTo: (to: number) => void;
getScrollWrap: () => Element | null;
}

View File

@@ -1,156 +0,0 @@
import type { UseDrawerReturnType, DrawerInstance, ReturnMethods, DrawerProps, UseDrawerInnerReturnType } from './typing';
import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw, computed } from 'vue';
import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is';
import { tryOnUnmounted } from '@vueuse/core';
import { isEqual } from 'lodash-es';
import { error } from '/@/utils/log';
const dataTransferRef = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({});
/**
* @description: Applicable to separate drawer and call outside
*/
export function useDrawer(): UseDrawerReturnType {
if (!getCurrentInstance()) {
throw new Error('useDrawer() can only be used inside setup() or functional components!');
}
const drawer = ref<DrawerInstance | null>(null);
const loaded = ref<Nullable<boolean>>(false);
const uid = ref<string>('');
function register(drawerInstance: DrawerInstance, uuid: string) {
isProdMode() &&
tryOnUnmounted(() => {
drawer.value = null;
loaded.value = null;
dataTransferRef[unref(uid)] = null;
});
if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) {
return;
}
uid.value = uuid;
drawer.value = drawerInstance;
loaded.value = true;
drawerInstance.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible;
};
}
const getInstance = () => {
const instance = unref(drawer);
if (!instance) {
error('useDrawer instance is undefined!');
}
return instance;
};
const methods: ReturnMethods = {
setDrawerProps: (props: Partial<DrawerProps>): void => {
getInstance()?.setDrawerProps(props);
},
getVisible: computed((): boolean => {
return visibleData[~~unref(uid)];
}),
getOpen: computed((): boolean => {
return visibleData[~~unref(uid)];
}),
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
// update-begin--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
getInstance()?.setDrawerProps({
open: visible,
});
// update-end--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
if (!data) return;
if (openOnSet) {
dataTransferRef[unref(uid)] = null;
dataTransferRef[unref(uid)] = toRaw(data);
return;
}
const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data));
if (!equal) {
dataTransferRef[unref(uid)] = toRaw(data);
}
},
closeDrawer: () => {
// update-begin--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
getInstance()?.setDrawerProps({ open: false });
// update-end--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
},
};
return [register, methods];
}
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
const currentInstance = getCurrentInstance();
const uidRef = ref<string>('');
if (!getCurrentInstance()) {
throw new Error('useDrawerInner() can only be used inside setup() or functional components!');
}
const getInstance = () => {
const instance = unref(drawerInstanceRef);
if (!instance) {
error('useDrawerInner instance is undefined!');
return;
}
return instance;
};
const register = (modalInstance: DrawerInstance, uuid: string) => {
isProdMode() &&
tryOnUnmounted(() => {
drawerInstanceRef.value = null;
});
uidRef.value = uuid;
drawerInstanceRef.value = modalInstance;
currentInstance?.emit('register', modalInstance, uuid);
};
watchEffect(() => {
const data = dataTransferRef[unref(uidRef)];
if (!data) return;
if (!callbackFn || !isFunction(callbackFn)) return;
nextTick(() => {
callbackFn(data);
});
});
return [
register,
{
changeLoading: (loading = true) => {
getInstance()?.setDrawerProps({ loading });
},
changeOkLoading: (loading = true) => {
getInstance()?.setDrawerProps({ confirmLoading: loading });
},
getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)];
}),
getOpen: computed((): boolean => {
return visibleData[~~unref(uidRef)];
}),
closeDrawer: () => {
getInstance()?.setDrawerProps({ open: false });
},
setDrawerProps: (props: Partial<DrawerProps>) => {
getInstance()?.setDrawerProps(props);
},
},
];
};

View File

@@ -1,10 +0,0 @@
export { default as BasicTable } from './src/BasicTable.vue';
export { default as TableAction } from './src/components/TableAction.vue';
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
// export { default as TableImg } from './src/components/TableImg.vue';
export * from './src/types/table';
// export * from './src/types/pagination';
// export * from './src/types/tableAction';
// export { useTable } from './src/hooks/useTable';
// export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';
// export type { EditRecordRow } from './src/components/editable';

View File

@@ -1,605 +0,0 @@
<template>
<div ref="wrapRef" :class="getWrapperClass">
<!-- <BasicForm
:class="{ 'table-search-area-hidden': !getBindValues.formConfig?.schemas?.length }"
submitOnReset
v-bind="getFormProps"
v-if="getBindValues.useSearchForm"
:tableAction="tableAction"
@register="registerForm"
@submit="handleSearchInfoChange"
@advanced-change="redoHeight"
>
<template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</BasicForm> -->
<!-- antd v3 升级兼容阻止数据的收集防止控制台报错 -->
<!-- https://antdv.com/docs/vue/migration-v3-cn -->
<a-form-item-rest>
<!-- TV360X-377关联记录必填影响到了table的输入框和页码样式 -->
<a-form-item>
<Table ref="tableElRef" v-bind="getBindValues" :rowClassName="getRowClassName" v-show="getEmptyDataIsShowTable" @resizeColumn="handleResizeColumn" @change="handleTableChange">
<!-- antd的原生插槽直接传递 -->
<template #[item]="data" v-for="item in slotNamesGroup.native" :key="item">
<!-- update-begin--author:liaozhiyang---date:20240424---forissues/1146BasicTable使用headerCell全选框出不来 -->
<template v-if="item === 'headerCell'">
<CustomSelectHeader v-if="isCustomSelection(data.column)" v-bind="selectHeaderProps" />
<slot v-else :name="item" v-bind="data || {}"></slot>
</template>
<slot v-else :name="item" v-bind="data || {}"></slot>
<!-- update-begin--author:liaozhiyang---date:20240424---forissues/1146BasicTable使用headerCell全选框出不来 -->
</template>
<template #headerCell="{ column }">
<!-- update-begin--author:sunjianlei---date:220230630---forQQYUN-5571自封装选择列解决数据行选择卡顿问题 -->
<CustomSelectHeader v-if="isCustomSelection(column)" v-bind="selectHeaderProps"/>
<HeaderCell v-else :column="column" />
<!-- update-end--author:sunjianlei---date:220230630---forQQYUN-5571自封装选择列解决数据行选择卡顿问题 -->
</template>
<!-- 增加对antdv3.x兼容 -->
<template #bodyCell="data">
<!-- update-begin--author:liaozhiyang---date:220230717---forissues-179antd3 一些警告以及报错(针对表格) -->
<!-- update-begin--author:liusq---date:20230921---forissues/770slotsBak异常报错的问题,增加判断column是否存在 -->
<template v-if="data.column?.slotsBak?.customRender">
<!-- update-end--author:liusq---date:20230921---forissues/770slotsBak异常报错的问题,增加判断column是否存在 -->
<slot :name="data.column.slotsBak.customRender" v-bind="data || {}"></slot>
</template>
<template v-else>
<slot name="bodyCell" v-bind="data || {}"></slot>
</template>
<!-- update-begin--author:liaozhiyang---date:22030717---forissues-179antd3 一些警告以及报错(针对表格) -->
</template>
<!-- update-begin--author:liaozhiyang---date:20240425---forpull/1201添加antd的TableSummary功能兼容老的summary表尾合计 -->
<template v-if="showSummaryRef && !getBindValues.showSummary" #summary="data">
<slot name="summary" v-bind="data || {}">
<TableSummary :data="data || {}" v-bind="getSummaryProps" />
</slot>
</template>
<!-- update-end--author:liaozhiyang---date:20240425---forpull/1201添加antd的TableSummary功能兼容老的summary表尾合计 -->
</Table>
</a-form-item>
</a-form-item-rest>
</div>
</template>
<script lang="ts">
import type { BasicTableProps, TableActionType, SizeType, ColumnChangeParam, BasicColumn } from './types/table';
import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect, watch, onUnmounted, onMounted, nextTick } from 'vue';
import { Table } from 'ant-design-vue';
// import { BasicForm, useForm } from '/@/components/Form/index';
// import { PageWrapperFixedHeightKey } from '/@/components/Page/injectionKey';
import CustomSelectHeader from './components/CustomSelectHeader.vue'
import expandIcon from './components/ExpandIcon';
// import HeaderCell from './components/HeaderCell.vue';
import TableSummary from './components/TableSummary';
import { InnerHandlers } from './types/table';
import { usePagination } from './hooks/usePagination';
import { useColumns } from './hooks/useColumns';
import { useDataSource } from './hooks/useDataSource';
import { useLoading } from './hooks/useLoading';
import { useRowSelection } from './hooks/useRowSelection';
import { useTableScroll } from './hooks/useTableScroll';
import { useCustomRow } from './hooks/useCustomRow';
import { useTableStyle } from './hooks/useTableStyle';
import { useTableHeader } from './hooks/useTableHeader';
import { useTableExpand } from './hooks/useTableExpand';
import { createTableContext } from './hooks/useTableContext';
import { useTableFooter } from './hooks/useTableFooter';
import { useTableForm } from './hooks/useTableForm';
// import { useDesign } from '/@/hooks/web/useDesign';
import { useCustomSelection } from "./hooks/useCustomSelection";
import { omit, pick } from 'lodash-es';
import { basicProps } from './props';
// import { isFunction } from '/@/utils/is';
// import { warn } from '/@/utils/log';
export default defineComponent({
components: {
Table,
// BasicForm,
HeaderCell,
TableSummary,
CustomSelectHeader,
},
props: basicProps,
emits: [
'fetch-success',
'fetch-error',
'selection-change',
'register',
'row-click',
'row-dbClick',
'row-contextmenu',
'row-mouseenter',
'row-mouseleave',
'edit-end',
'edit-cancel',
'edit-row-end',
'edit-change',
'expanded-rows-change',
'change',
'columns-change',
'table-redo',
],
setup(props, { attrs, emit, slots, expose }) {
const tableElRef = ref(null);
const tableData = ref<Recordable[]>([]);
const wrapRef = ref(null);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const { prefixCls } = useDesign('basic-table');
const [registerForm, formActions] = useForm();
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
});
const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false);
watchEffect(() => {
unref(isFixedHeightPage) &&
props.canResize &&
warn("'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)");
});
const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, getPagination, setPagination, setShowPagination, getShowPagination } = usePagination(getProps);
// update-begin--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
// const { getRowSelection, getRowSelectionRef, getSelectRows, clearSelectedRowKeys, getSelectRowKeys, deleteSelectRowByKey, setSelectedRowKeys } =
// useRowSelection(getProps, tableData, emit);
// 子级列名
const childrenColumnName = computed(() => getProps.value.childrenColumnName || 'children');
// 自定义选择列
const {
getRowSelection,
getSelectRows,
getSelectRowKeys,
setSelectedRowKeys,
getRowSelectionRef,
selectHeaderProps,
isCustomSelection,
handleCustomSelectColumn,
clearSelectedRowKeys,
deleteSelectRowByKey,
getExpandIconColumnIndex,
} = useCustomSelection(
getProps,
emit,
wrapRef,
getPaginationInfo,
tableData,
childrenColumnName
)
// update-end--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
const {
handleTableChange: onTableChange,
getDataSourceRef,
getDataSource,
getRawDataSource,
setTableData,
updateTableDataRecord,
deleteTableDataRecord,
insertTableDataRecord,
findTableDataRecord,
fetch,
getRowKey,
reload,
getAutoCreateKey,
updateTableData,
} = useDataSource(
getProps,
{
tableData,
getPaginationInfo,
setLoading,
setPagination,
validate: formActions.validate,
clearSelectedRowKeys,
},
emit
);
function handleTableChange(...args) {
onTableChange.call(undefined, ...args);
emit('change', ...args);
// 解决通过useTable注册onChange时不起作用的问题
const { onChange } = unref(getProps);
onChange && isFunction(onChange) && onChange.call(undefined, ...args);
}
const { getViewColumns, getColumns, setCacheColumnsByField, setColumns, getColumnsRef, getCacheColumns } = useColumns(
getProps,
getPaginationInfo,
// update-begin--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
handleCustomSelectColumn,
// update-end--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
);
const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef, getColumnsRef, getRowSelectionRef, getDataSourceRef);
const { customRow } = useCustomRow(getProps, {
setSelectedRowKeys,
getSelectRowKeys,
clearSelectedRowKeys,
getAutoCreateKey,
emit,
});
const { getRowClassName } = useTableStyle(getProps, prefixCls);
const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit);
const handlers: InnerHandlers = {
onColumnsChange: (data: ColumnChangeParam[]) => {
emit('columns-change', data);
// support useTable
unref(getProps).onColumnsChange?.(data);
},
};
const { getHeaderProps } = useTableHeader(getProps, slots, handlers);
// update-begin--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
const getSummaryProps = computed(() => {
return pick(unref(getProps), ['summaryFunc', 'summaryData', 'hasExpandedRow', 'rowKey']);
});
const getIsEmptyData = computed(() => {
return (unref(getDataSourceRef) || []).length === 0;
});
const showSummaryRef = computed(() => {
const summaryProps = unref(getSummaryProps);
return (summaryProps.summaryFunc || summaryProps.summaryData) && !unref(getIsEmptyData);
});
// update-end--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
const { getFooterProps } = useTableFooter(getProps, slots, getScrollRef, tableElRef, getDataSourceRef);
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } = useTableForm(getProps, slots, fetch, getLoading);
const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef);
let propsData: Recordable = {
// ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
...attrs,
customRow,
//树列表展开使用AntDesignVue默认的加减图标 author:scott date:20210914
//expandIcon: slots.expandIcon ? null : expandIcon(),
...unref(getProps),
...unref(getHeaderProps),
scroll: unref(getScrollRef),
loading: unref(getLoading),
tableLayout: 'fixed',
rowSelection: unref(getRowSelectionRef),
rowKey: unref(getRowKey),
columns: toRaw(unref(getViewColumns)),
pagination: toRaw(unref(getPaginationInfo)),
dataSource,
footer: unref(getFooterProps),
...unref(getExpandOption),
// 【QQYUN-5837】动态计算 expandIconColumnIndex
expandIconColumnIndex: getExpandIconColumnIndex.value,
};
//update-begin---author:wangshuai ---date:20230214 for[QQYUN-4237]代码生成 内嵌子表模式 没有滚动条------------
//额外的展开行存在插槽时会将滚动移除掉,注释掉
/*if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll');
}*/
//update-end---author:wangshuai ---date:20230214 for[QQYUN-4237]代码生成 内嵌子表模式 没有滚动条------------
// update-begin--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
// 自定义选择列,需要去掉原生的
delete propsData.rowSelection
// update-end--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
// update-begin--author:liaozhiyang---date:20230919---for【QQYUN-6387】展开写法去掉报错
!propsData.isTreeTable && delete propsData.expandIconColumnIndex;
propsData.expandedRowKeys === null && delete propsData.expandedRowKeys;
// update-end--author:liaozhiyang---date:20230919---for【QQYUN-6387】展开写法去掉报错
propsData = omit(propsData, ['class', 'onChange']);
return propsData;
});
// 统一设置表格列宽度
const getMaxColumnWidth = computed(() => {
const values = unref(getBindValues);
return values.maxColumnWidth > 0 ? values.maxColumnWidth + 'px' : null;
});
const getWrapperClass = computed(() => {
const values = unref(getBindValues);
return [
prefixCls,
attrs.class,
{
[`${prefixCls}-form-container`]: values.useSearchForm,
[`${prefixCls}--inset`]: values.inset,
[`${prefixCls}-col-max-width`]: getMaxColumnWidth.value != null,
// 是否显示表尾合计
[`${prefixCls}--show-summary`]: values.showSummary,
},
];
});
const getEmptyDataIsShowTable = computed(() => {
const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
if (emptyDataIsShowTable || !useSearchForm) {
return true;
}
return !!unref(getDataSourceRef).length;
});
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
}
const tableAction: TableActionType = {
reload,
getSelectRows,
clearSelectedRowKeys,
getSelectRowKeys,
deleteSelectRowByKey,
setPagination,
setTableData,
updateTableDataRecord,
deleteTableDataRecord,
insertTableDataRecord,
findTableDataRecord,
redoHeight,
setSelectedRowKeys,
setColumns,
setLoading,
getDataSource,
getRawDataSource,
setProps,
getRowSelection,
getPaginationRef: getPagination,
getColumns,
getCacheColumns,
emit,
updateTableData,
setShowPagination,
getShowPagination,
setCacheColumnsByField,
expandAll,
collapseAll,
getSize: () => {
return unref(getBindValues).size as SizeType;
},
};
createTableContext({ ...tableAction, wrapRef, getBindValues });
// update-begin--author:sunjianlei---date:220230718---for【issues/179】兼容新老slots写法移除控制台警告
// 获取分组之后的slot名称
const slotNamesGroup = computed<{
// AntTable原生插槽
native: string[];
// 列自定义插槽
custom: string[];
}>(() => {
const native: string[] = [];
const custom: string[] = [];
const columns = unref<Recordable[]>(getViewColumns) as BasicColumn[];
const allCustomRender = columns.map<string>((column) => column.slotsBak?.customRender);
for (const name of Object.keys(slots)) {
// 过滤特殊的插槽
if (['bodyCell'].includes(name)) {
continue;
}
if (allCustomRender.includes(name)) {
custom.push(name);
} else {
native.push(name);
}
}
return { native, custom };
});
// update-end--author:sunjianlei---date:220230718---for【issues/179】兼容新老slots写法移除控制台警告
// update-begin--author:liaozhiyang---date:20231226---for【issues/945】BasicTable组件设置默认展开不生效
nextTick(() => {
getProps.value.defaultExpandAllRows && expandAll();
})
// update-end--author:sunjianlei---date:20231226---for【issues/945】BasicTable组件设置默认展开不生效
expose(tableAction);
emit('register', tableAction, formActions);
return {
tableElRef,
getBindValues,
getLoading,
registerForm,
handleSearchInfoChange,
getEmptyDataIsShowTable,
handleTableChange,
getRowClassName,
wrapRef,
tableAction,
redoHeight,
handleResizeColumn: (w, col) => {
console.log('col',col);
col.width = w;
},
getFormProps: getFormProps as any,
replaceFormSlotKey,
getFormSlotKeys,
getWrapperClass,
getMaxColumnWidth,
columns: getViewColumns,
// update-begin--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
selectHeaderProps,
isCustomSelection,
// update-end--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
slotNamesGroup,
// update-begin--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
getSummaryProps,
showSummaryRef,
// update-end--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
};
},
});
</script>
<style lang="less">
@border-color: #cecece4d;
@prefix-cls: ~'@{namespace}-basic-table';
[data-theme='dark'] {
.ant-table-tbody > tr:hover.ant-table-row-selected > td,
.ant-table-tbody > tr.ant-table-row-selected td {
background-color: #262626;
}
.@{prefix-cls} {
//表格选择工具栏样式
.alert {
// background-color: #323232;
// border-color: #424242;
}
}
}
.@{prefix-cls} {
max-width: 100%;
&-row__striped {
td {
background-color: @app-content-background;
}
}
// update-begin--author:liaozhiyang---date:20240613---for【TV360X-1232】查询区域隐藏后点击刷新不走请求了(采用css隐藏)
> .table-search-area-hidden {
display: none;
}
// update-end--author:liaozhiyang---date:20240613---for【TV360X-1232】查询区域隐藏后点击刷新不走请求了(采用css隐藏)
&-form-container {
padding: 10px;
.ant-form {
padding: 12px 10px 6px 10px;
margin-bottom: 8px;
background-color: @component-background;
border-radius: 2px;
}
}
.ant-tag {
margin-right: 0;
}
//update-begin-author:liusq---date:20230517--for: [issues/526]RangePicker 设置预设范围按钮样式问题---
.ant-picker-preset {
.ant-tag {
margin-right: 8px !important;
}
}
//update-end-author:liusq---date:20230517--for: [issues/526]RangePicker 设置预设范围按钮样式问题---
.ant-table-wrapper {
padding: 6px;
background-color: @component-background;
border-radius: 2px;
.ant-table-title {
min-height: 40px;
padding: 0 0 8px 0 !important;
}
.ant-table.ant-table-bordered .ant-table-title {
border: none !important;
}
}
.ant-table {
width: 100%;
overflow-x: hidden;
&-title {
display: flex;
padding: 8px 6px;
border-bottom: none;
justify-content: space-between;
align-items: center;
}
//定义行颜色
.trcolor {
background-color: rgba(255, 192, 203, 0.31);
color: red;
}
//.ant-table-tbody > tr.ant-table-row-selected td {
//background-color: fade(@primary-color, 8%) !important;
//}
}
.ant-pagination {
margin: 10px 0 0 0;
}
.ant-table-footer {
padding: 0;
.ant-table-wrapper {
padding: 0;
}
table {
border: none !important;
}
.ant-table-content {
overflow-x: hidden !important;
// overflow-y: scroll !important;
}
td {
padding: 12px 8px;
}
}
//表格选择工具栏样式
.alert {
height: 38px;
// background-color: #e6f7ff;
// border-color: #91d5ff;
}
&--inset {
.ant-table-wrapper {
padding: 0;
}
}
// ------ 统一设置表格列最大宽度 ------
&-col-max-width {
.ant-table-thead tr th,
.ant-table-tbody tr td {
max-width: v-bind(getMaxColumnWidth);
}
}
// ------ 统一设置表格列最大宽度 ------
// update-begin--author:sunjianlei---date:220230718---for【issues/622】修复表尾合计错位的问题
&--show-summary {
.ant-table > .ant-table-footer {
padding: 12px 0 0;
}
.ant-table.ant-table-bordered > .ant-table-footer {
border: 0;
}
}
// update-end--author:sunjianlei---date:220230718---for【issues/622】修复表尾合计错位的问题
// update-begin--author:liaozhiyang---date:20240604---for【TV360X-377】关联记录必填影响到了table的输入框和页码样式
> .ant-form-item {
margin-bottom: 0;
}
// update-end--author:liaozhiyang---date:20240604---for【TV360X-377】关联记录必填影响到了table的输入框和页码样式
}
</style>

View File

@@ -1,26 +0,0 @@
import type { Component } from 'vue';
import { Input, Select, Checkbox, InputNumber, Switch, DatePicker, TimePicker } from 'ant-design-vue';
import type { ComponentType } from './types/componentType';
import { ApiSelect, ApiTreeSelect } from '/@/components/Form';
const componentMap = new Map<ComponentType, Component>();
componentMap.set('Input', Input);
componentMap.set('InputNumber', InputNumber);
componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('Switch', Switch);
componentMap.set('Checkbox', Checkbox);
componentMap.set('DatePicker', DatePicker);
componentMap.set('TimePicker', TimePicker);
export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component);
}
export function del(compName: ComponentType) {
componentMap.delete(compName);
}
export { componentMap };

View File

@@ -1,67 +0,0 @@
<!-- 自定义选择列表头实现部分 -->
<template>
<!-- update-begin--author:liaozhiyang---date:20231130---forissues/5595BasicTable组件hideSelectAll: true无法隐藏全选框 -->
<template v-if="isRadio">
<!-- radio不存在全选所以放个空标签 -->
<span></span>
</template>
<template v-else>
<template v-if="hideSelectAll">
<span></span>
</template>
<a-checkbox :disabled="disabled" v-else :checked="checked" :indeterminate="isHalf" @update:checked="onChange" />
</template>
<!-- update-end--author:liaozhiyang---date:20231130---forissues/5595BasicTable组件hideSelectAll: true无法隐藏全选框 -->
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({
isRadio: {
type: Boolean,
required: true,
},
selectedLength: {
type: Number,
required: true,
},
// 当前页条目数
pageSize: {
type: Number,
required: true,
},
hideSelectAll: {
type: Boolean,
default: false,
},
// update-begin--author:liaozhiyang---date:20231016---for【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
disabled: {
type: Boolean,
required: true,
},
// update-end--author:liaozhiyang---date:20231016---for【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
});
const emit = defineEmits(['select-all']);
// 是否全选
const checked = computed(() => {
if (props.isRadio) {
return false;
}
return props.selectedLength > 0 && props.selectedLength >= props.pageSize;
});
// 是否半选
const isHalf = computed(() => {
if (props.isRadio) {
return false;
}
return props.selectedLength > 0 && props.selectedLength < props.pageSize;
});
function onChange(checked: boolean) {
emit('select-all', checked);
}
</script>
<style scoped lang="scss"></style>

View File

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

View File

@@ -1,23 +0,0 @@
import { BasicArrow } from '/@/components/Basic';
export default () => {
return (props: Recordable) => {
if (!props.expandable) {
if (props.needIndentSpaced) {
return <span class="ant-table-row-expand-icon ant-table-row-spaced" />;
} else {
return <span />;
}
}
return (
<BasicArrow
style="margin-right: 8px"
iconStyle="margin-top: -2px;"
onClick={(e: Event) => {
props.onExpand(props.record, e);
}}
expand={props.expanded}
/>
);
};
};

View File

@@ -1,57 +0,0 @@
<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">
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';
export default defineComponent({
name: 'TableHeaderCell',
components: {
EditTableHeaderCell,
BasicHelp,
},
props: {
column: {
type: Object as PropType<BasicColumn>,
default: () => ({}),
},
},
setup(props) {
const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => !!props.column?.edit);
const getTitle = computed(() => {
// update-begin--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
const result = props.column?.customTitle || props.column?.title;
if (typeof result === 'string') {
return result;
} else {
return '';
}
// update-end--author:liaozhiyang---date:20231218---for【QQYUN-6366】升级到antd4.x
});
const getHelpMessage = computed(() => props.column?.helpMessage);
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header-cell';
.@{prefix-cls} {
&__help {
margin-left: 8px;
color: rgb(0 0 0 / 65%) !important;
}
}
</style>

View File

@@ -1,283 +0,0 @@
<template>
<div :class="[prefixCls, getAlign]" @click="onCellClick">
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
<template v-if="action.slot">
<slot name="customButton"></slot>
</template>
<template v-else>
<Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)">
<PopConfirmButton v-bind="action">
<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">
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
<template v-if="action.label">{{ action.label }}</template>
</PopConfirmButton>
</template>
<Divider type="vertical" class="action-divider" v-if="divider && index < getActions.length - 1" />
</template>
<Dropdown
:overlayClassName="dropdownCls"
:trigger="['hover']"
:dropMenuList="getDropdownList"
popconfirm
v-if="dropDownActions && getDropdownList.length > 0"
>
<slot name="more"></slot>
<!-- 设置插槽 -->
<template v-slot:[item.slot] v-for="(item, index) in getDropdownSlotList" :key="`${index}-${item.label}`">
<slot :name="item.slot"></slot>
</template>
<a-button type="link" size="small" v-if="!$slots.more"> 更多 <Icon icon="mdi-light:chevron-down"></Icon> </a-button>
</Dropdown>
</div>
</template>
<script lang="ts">
import { defineComponent, 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 { ActionItem, TableActionType } from '/@/components/Table';
import { PopConfirmButton } from '/@/components/Button';
import { Dropdown } from '/@/components/Dropdown';
import { useDesign } from '/@/hooks/web/useDesign';
import { useTableContext } from '../hooks/useTableContext';
import { usePermission } from '/@/hooks/web/usePermission';
import { isBoolean, isFunction, isString } from '/@/utils/is';
import { propTypes } from '/@/utils/propTypes';
import { ACTION_COLUMN_FLAG } from '../const';
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');
const dropdownCls = `${prefixCls}-dropdown`;
let table: Partial<TableActionType> = {};
if (!props.outside) {
table = useTableContext();
}
const { hasPermission } = usePermission();
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
})
.map((action) => {
const { popConfirm } = action;
// update-begin--author:liaozhiyang---date:20240105---for【issues/951】table删除记录时按钮显示错位
if (popConfirm) {
const overlayClassName = popConfirm.overlayClassName;
popConfirm.overlayClassName = `${overlayClassName ? overlayClassName : ''} ${prefixCls}-popconfirm`;
}
// update-end--author:liaozhiyang---date:20240105---for【issues/951】table删除记录时按钮显示错位
return {
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
type: 'link',
size: 'small',
...action,
...(popConfirm || {}),
// update-begin--author:liaozhiyang---date:20240108---for【issues/936】表格操作栏删除当接口失败时气泡确认框不会消失
onConfirm: handelConfirm(popConfirm?.confirm),
// update-end--author:liaozhiyang---date:20240108---for【issues/936】表格操作栏删除当接口失败时气泡确认框不会消失
onCancel: popConfirm?.cancel,
enable: !!popConfirm,
};
});
});
const getDropdownList = computed((): any[] => {
//过滤掉隐藏的dropdown,避免出现多余的分割线
const list = (toRaw(props.dropDownActions) || []).filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
});
return list.map((action, index) => {
const { label, popConfirm } = action;
// update-begin--author:liaozhiyang---date:20240105---for【issues/951】table删除记录时按钮显示错位
if (popConfirm) {
const overlayClassName = popConfirm.overlayClassName;
popConfirm.overlayClassName = `${overlayClassName ? overlayClassName : ''} ${prefixCls}-popconfirm`;
}
// update-end--author:liaozhiyang---date:20240105---for【issues/951】table删除记录时按钮显示错位
// update-begin--author:liaozhiyang---date:20240108---for【issues/936】表格操作栏删除当接口失败时气泡确认框不会消失
if (popConfirm) {
popConfirm.confirm = handelConfirm(popConfirm?.confirm);
}
// update-end--author:liaozhiyang---date:20240108---for【issues/936】表格操作栏删除当接口失败时气泡确认框不会消失
return {
...action,
...popConfirm,
onConfirm: handelConfirm(popConfirm?.confirm),
onCancel: popConfirm?.cancel,
text: label,
divider: index < list.length - 1 ? props.divider : false,
};
});
});
/*
2023-01-08
liaozhiyang
给传进来的函数包一层promise
*/
const handelConfirm = (fn) => {
if (typeof fn !== 'function') return fn;
const anyc = () => {
return new Promise<void>((resolve) => {
const result = fn();
if (Object.prototype.toString.call(result) === '[object Promise]') {
result
.finally(() => {
resolve();
})
.catch((err) => {
console.log(err);
});
} else {
resolve();
}
});
};
return anyc;
};
const getDropdownSlotList = computed((): any[] => {
return unref(getDropdownList).filter((item) => item.slot);
});
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.value) ?? 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();
}
return { prefixCls, getActions, getDropdownList, getDropdownSlotList, getAlign, onCellClick, getTooltip, dropdownCls };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-action';
.@{prefix-cls} {
display: flex;
align-items: center;
/* update-begin-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px*/
height: 22px;
/* update-end-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px*/
.action-divider {
display: table;
}
&.left {
justify-content: flex-start;
}
&.center {
justify-content: center;
}
&.right {
justify-content: flex-end;
}
button {
display: flex;
align-items: center;
span {
margin-left: 0 !important;
}
}
button.ant-btn-circle {
span {
margin: auto !important;
}
}
.ant-divider,
.ant-divider-vertical {
margin: 0 2px;
}
.icon-more {
transform: rotate(90deg);
svg {
font-size: 1.1em;
font-weight: 700;
}
}
&-popconfirm {
.ant-popconfirm-buttons {
min-width: 120px;
// update-begin--author:liaozhiyang---date:20240124---for【issues/1019】popConfirm确认框待端后端返回过程中处理中样式错乱
display: flex;
align-items: center;
justify-content: center;
// update-end--author:liaozhiyang---date:20240124---for【issues/1019】popConfirm确认框待端后端返回过程中处理中样式错乱
}
}
// update-begin--author:liaozhiyang---date:20240407---for【QQYUN-8762】调整table操作栏ant-dropdown样式
&-dropdown {
.ant-dropdown-menu .ant-dropdown-menu-item-divider {
margin: 2px 0;
}
.ant-dropdown-menu .ant-dropdown-menu-item {
padding: 3px 8px;
font-size: 13.6px;
}
.dropdown-event-area {
padding: 0 !important;
}
}
// update-end--author:liaozhiyang---date:20240407---for【QQYUN-8762】调整table操作栏ant-dropdown样式
}
</style>

View File

@@ -1,134 +0,0 @@
<template>
<Table
v-if="summaryFunc || summaryData"
:showHeader="false"
:bordered="bordered"
:pagination="false"
:dataSource="getDataSource"
:rowKey="(r) => r[rowKey]"
:columns="getColumns"
tableLayout="fixed"
:scroll="scroll"
/>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent, 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 { INDEX_COLUMN_FLAG } from '../const';
import { propTypes } from '/@/utils/propTypes';
import { useTableContext } from '../hooks/useTableContext';
const SUMMARY_ROW_KEY = '_row';
const SUMMARY_INDEX_KEY = '_index';
export default defineComponent({
name: 'BasicTableFooter',
components: { Table },
props: {
bordered: {
type: Boolean,
default: false,
},
summaryFunc: {
type: Function as PropType<Fn>,
},
summaryData: {
type: Array as PropType<Recordable[]>,
},
scroll: {
type: Object as PropType<Recordable>,
},
rowKey: propTypes.string.def('key'),
// 是否有展开列
hasExpandedRow: propTypes.bool,
},
setup(props) {
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 [];
}
// update-begin--author:liaozhiyang---date:20230227---for【QQYUN-8172】可编辑单元格编辑完以后不更新合计值
let dataSource = cloneDeep(unref(table.getDataSource()));
// update-end--author:liaozhiyang---date:20230227---for【QQYUN-8172】可编辑单元格编辑完以后不更新合计值
dataSource = summaryFunc(dataSource);
dataSource.forEach((item, i) => {
item[props.rowKey] = `${i}`;
});
return dataSource;
});
const getColumns = computed(() => {
const dataSource = unref(getDataSource);
let columns: BasicColumn[] = cloneDeep(table.getColumns());
// update-begin--author:liaozhiyang---date:220230804---for【issues/638】表格合计列自定义隐藏或展示时合计栏会错位
columns = columns.filter((item) => !item.defaultHidden);
// update-begin--author:liaozhiyang---date:220230804---for【issues/638】表格合计列自定义隐藏或展示时合计栏会错位
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));
// 是否有序号列
let hasIndexCol = false;
// 是否有选择列
let hasSelection = table.getRowSelection() && hasRowSummary
if (index !== -1) {
if (hasIndexSummary) {
hasIndexCol = true;
columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
columns[index].ellipsis = false;
} else {
Reflect.deleteProperty(columns[index], 'customRender');
}
}
if (hasSelection) {
// update-begin--author:liaozhiyang---date:20231009---for【issues/776】显示100条/页复选框只能显示3个的问题(fixed也有可能设置true)
const isFixed = columns.some((col) => col.fixed === 'left' || col.fixed === true);
// update-begin--author:liaozhiyang---date:20231009---for【issues/776】显示100条/页复选框只能显示3个的问题(fixed也有可能设置true)
columns.unshift({
width: 50,
title: 'selection',
key: 'selectionKey',
align: 'center',
...(isFixed ? { fixed: 'left' } : {}),
customRender: ({ record }) => hasIndexCol ? '' : record[SUMMARY_ROW_KEY],
});
}
if (props.hasExpandedRow) {
const isFixed = columns.some((col) => col.fixed === 'left');
columns.unshift({
width: 50,
title: 'expandedRow',
key: 'expandedRowKey',
align: 'center',
...(isFixed ? { fixed: 'left' } : {}),
customRender: () => '',
});
}
return columns;
});
return { getColumns, getDataSource };
},
});
</script>
<style lang="less" scoped>
// update-begin--author:liaozhiyang---date:20231009---for【issues/776】显示100条/页复选框只能显示3个的问题(隐藏合计的滚动条)
.ant-table-wrapper {
:deep(.ant-table-body) {
overflow-x: hidden !important;
}
}
// update-end--author:liaozhiyang---date:20231009---for【issues/776】显示100条/页复选框只能显示3个的问题(隐藏合计的滚动条)
</style>

View File

@@ -1,165 +0,0 @@
<template>
<div style="width: 100%">
<div v-if="$slots.headerTop" style="margin: 5px">
<slot name="headerTop"></slot>
</div>
<div :class="`flex items-center ${prefixCls}__table-title-box`">
<div :class="`${prefixCls}__tableTitle`">
<slot name="tableTitle" v-if="$slots.tableTitle"></slot>
<!--修改标题插槽位置-->
<TableTitle :helpMessage="titleHelpMessage" :title="title" v-if="!$slots.tableTitle && title" />
</div>
<div :class="`${prefixCls}__toolbar`">
<slot name="toolbar"></slot>
<Divider type="vertical" v-if="$slots.toolbar && showTableSetting" />
<TableSetting :class="`${prefixCls}__toolbar-desktop`" style="white-space: nowrap;" :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" />
<a-popover :overlayClassName="`${prefixCls}__toolbar-mobile`" trigger="click" placement="left" :getPopupContainer="(n) => n?.parentElement">
<template #content>
<TableSetting mode="mobile" :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" />
</template>
<a-button :class="`${prefixCls}__toolbar-mobile`" v-if="showTableSetting" type="text" preIcon="ant-design:menu" shape="circle" />
</a-popover>
</div>
</div>
<!--添加tableTop插槽-->
<div style="margin: -4px 0 -2px; padding-top: 5px">
<slot name="tableTop">
<a-alert type="info" show-icon class="alert" v-if="openRowSelection != null">
<template #message>
<template v-if="selectRowKeys.length > 0">
<span>
<span>已选中 {{ selectRowKeys.length }} 条记录</span>
<span v-if="isAcrossPage">(可跨页)</span>
</span>
<a-divider type="vertical" />
<a @click="setSelectedRowKeys([])">清空</a>
<slot name="alertAfter" />
</template>
<template v-else>
<span>未选中任何数据</span>
</template>
</template>
</a-alert>
</slot>
</div>
<!--添加tableTop插槽-->
</div>
</template>
<script lang="ts">
import type { TableSetting, ColumnChangeParam } from '../types/table';
import type { PropType } from 'vue';
import { defineComponent, computed } from 'vue';
import { Divider } from 'ant-design-vue';
import TableSettingComponent from './settings/index.vue';
import TableTitle from './TableTitle.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useTableContext } from '../hooks/useTableContext';
export default defineComponent({
name: 'BasicTableHeader',
components: {
Divider,
TableTitle,
TableSetting: TableSettingComponent,
},
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: '',
},
},
emits: ['columns-change'],
setup(_, { emit }) {
const { prefixCls } = useDesign('basic-table-header');
function handleColumnChange(data: ColumnChangeParam[]) {
emit('columns-change', data);
}
const { getSelectRowKeys, setSelectedRowKeys, getRowSelection } = useTableContext();
const selectRowKeys = computed(() => getSelectRowKeys());
const openRowSelection = computed(() => getRowSelection());
// 是否允许跨页选择
const isAcrossPage = computed(() => openRowSelection.value?.preserveSelectedRowKeys === true);
return { prefixCls, handleColumnChange, selectRowKeys, setSelectedRowKeys, openRowSelection, isAcrossPage };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header';
.@{prefix-cls} {
&__toolbar {
//flex: 1;
width: 140px;
display: flex;
align-items: center;
justify-content: flex-end;
> * {
margin-right: 8px;
}
&-desktop {
display: block;
}
&-mobile {
display: none;
}
}
&__tableTitle {
flex: 1;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
> * {
margin-right: 4px;
margin-bottom: 4px;
}
}
@media (max-width: @screen-lg) {
&__table-title-box {
align-items: flex-end;
}
&__toolbar {
width: 30px;
text-align: center;
> * {
margin-right: 0;
}
.table-settings > * {
margin-right: 0;
margin-bottom: 6px;
}
&-desktop {
display: none;
}
&-mobile {
display: block;
.table-settings > * {
margin-right: 6px;
margin-bottom: 0;
}
}
}
}
}
</style>

View File

@@ -1,76 +0,0 @@
<template>
<div :class="prefixCls" class="flex items-center mx-auto" v-if="imgList && imgList.length" :style="getWrapStyle">
<Badge :count="!showBadge || imgList.length == 1 ? 0 : imgList.length" v-if="simpleShow">
<div class="img-div">
<PreviewGroup>
<template v-for="(img, index) in imgList" :key="img">
<Image
:width="size"
:style="{
display: index === 0 ? '' : 'none !important',
}"
:src="srcPrefix + img"
/>
</template>
</PreviewGroup>
</div>
</Badge>
<PreviewGroup v-else>
<template v-for="(img, index) in imgList" :key="img">
<Image :width="size" :style="{ marginLeft: index === 0 ? 0 : margin }" :src="srcPrefix + img" />
</template>
</PreviewGroup>
</div>
</template>
<script lang="ts">
import type { CSSProperties } from 'vue';
import { defineComponent, 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: { 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),
// src前缀将会附加在imgList中每一项之前
srcPrefix: propTypes.string.def(''),
},
setup(props) {
const getWrapStyle = computed((): CSSProperties => {
const { size } = props;
const s = `${size}px`;
return { height: s, width: s };
});
const { prefixCls } = useDesign('basic-table-img');
return { prefixCls, getWrapStyle };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-img';
.@{prefix-cls} {
.ant-image {
margin-right: 4px;
cursor: zoom-in;
img {
border-radius: 2px;
}
}
.img-div {
display: inline-grid;
}
}
</style>

View File

@@ -1,163 +0,0 @@
import type { PropType, VNode } from 'vue';
import { defineComponent, unref, computed, isVNode } from 'vue';
import { cloneDeep, pick } from 'lodash-es';
import { isFunction } from '/@/utils/is';
import type { BasicColumn } from '../types/table';
import { INDEX_COLUMN_FLAG } from '../const';
import { propTypes } from '/@/utils/propTypes';
import { useTableContext } from '../hooks/useTableContext';
import { TableSummary, TableSummaryRow, TableSummaryCell } from 'ant-design-vue';
const SUMMARY_ROW_KEY = '_row';
const SUMMARY_INDEX_KEY = '_index';
export default defineComponent({
name: 'BasicTableSummary',
components: { TableSummary, TableSummaryRow, TableSummaryCell },
props: {
summaryFunc: {
type: Function as PropType<Fn>,
},
summaryData: {
type: Array as PropType<Recordable[]>,
},
rowKey: propTypes.string.def('key'),
// 是否有展开列
hasExpandedRow: propTypes.bool,
data: {
type: Object as PropType<Recordable>,
default: () => {},
},
},
setup(props) {
const table = useTableContext();
const getDataSource = computed((): Recordable[] => {
const {
summaryFunc,
summaryData,
data: { pageData },
} = props;
if (summaryData?.length) {
summaryData.forEach((item, i) => (item[props.rowKey] = `${i}`));
return summaryData;
}
if (!isFunction(summaryFunc)) {
return [];
}
let dataSource = cloneDeep(unref(pageData));
dataSource = summaryFunc(dataSource);
dataSource.forEach((item, i) => {
item[props.rowKey] = `${i}`;
});
return dataSource;
});
const getColumns = computed(() => {
const dataSource = unref(getDataSource);
let columns: BasicColumn[] = cloneDeep(table.getColumns({ sort: true }));
columns = columns.filter((item) => !item.defaultHidden);
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));
// 是否有序号列
let hasIndexCol = false;
// 是否有选择列
const hasSelection = table.getRowSelection() && hasRowSummary;
if (index !== -1) {
if (hasIndexSummary) {
hasIndexCol = true;
columns[index].customSummaryRender = ({ record }) => record[SUMMARY_INDEX_KEY];
columns[index].ellipsis = false;
} else {
Reflect.deleteProperty(columns[index], 'customSummaryRender');
}
}
if (hasSelection) {
const isFixed = columns.some((col) => col.fixed === 'left' || col.fixed === true);
columns.unshift({
width: 60,
title: 'selection',
key: 'selectionKey',
align: 'center',
...(isFixed ? { fixed: 'left' } : {}),
customSummaryRender: ({ record }) => (hasIndexCol ? '' : record[SUMMARY_ROW_KEY]),
});
}
if (props.hasExpandedRow) {
const isFixed = columns.some((col) => col.fixed === 'left');
columns.unshift({
width: 50,
title: 'expandedRow',
key: 'expandedRowKey',
align: 'center',
...(isFixed ? { fixed: 'left' } : {}),
customSummaryRender: () => '',
});
}
return columns;
});
function isRenderCell(data: any) {
return data && typeof data === 'object' && !Array.isArray(data) && !isVNode(data);
}
const getValues = (row: Recordable, col: BasicColumn, index: number) => {
const value = row[col.dataIndex as string];
let childNode: VNode | JSX.Element | string | number | undefined | null;
childNode = value;
if (col.customSummaryRender) {
const renderData = col.customSummaryRender({
text: value,
value,
record: row,
index,
column: cloneDeep(col),
});
if (isRenderCell(renderData)) {
childNode = renderData.children;
} else {
childNode = renderData;
}
if (typeof childNode === 'object' && !Array.isArray(childNode) && !isVNode(childNode)) {
childNode = null;
}
if (Array.isArray(childNode) && childNode.length === 1) {
childNode = childNode[0];
}
return childNode;
}
return childNode;
};
const getCellProps = (col: BasicColumn) => {
const cellProps = pick(col, ['colSpan', 'rowSpan', 'align']);
return {
...cellProps,
};
};
return () => {
return (
<TableSummary fixed>
{(unref(getDataSource) || []).map((row) => {
return (
<TableSummaryRow key={row[props.rowKey]}>
{unref(getColumns).map((col, index) => {
return (
<TableSummaryCell {...getCellProps(col)} index={index} key={`${row[props.rowKey]}_${col.dataIndex}_${index}`}>
{getValues(row, col, index)}
</TableSummaryCell>
);
})}
</TableSummaryRow>
);
})}
</TableSummary>
);
};
},
});

View File

@@ -1,53 +0,0 @@
<template>
<BasicTitle :class="prefixCls" v-if="getTitle" :helpMessage="helpMessage">
{{ getTitle }}
</BasicTitle>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import { BasicTitle } from '/@/components/Basic/index';
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[]>,
},
},
setup(props) {
const { prefixCls } = useDesign('basic-table-title');
const getTitle = computed(() => {
const { title, getSelectRows = () => {} } = props;
let tit = title;
if (isFunction(title)) {
tit = title({
selectRows: getSelectRows(),
});
}
return tit;
});
return { getTitle, prefixCls };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-title';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -1,38 +0,0 @@
import type { FunctionalComponent, defineComponent } from 'vue';
import type { ComponentType } from '../../types/componentType';
import { componentMap } from '/@/components/Table/src/componentMap';
import { Popover } from 'ant-design-vue';
import { h } from 'vue';
export interface ComponentProps {
component: ComponentType;
rule: boolean;
popoverVisible: boolean;
ruleMessage: string;
getPopupContainer?: Fn;
}
export const CellComponent: FunctionalComponent = (
{ component = 'Input', rule = true, ruleMessage, popoverVisible, getPopupContainer }: ComponentProps,
{ attrs }
) => {
const Comp = componentMap.get(component) as typeof defineComponent;
const DefaultComp = h(Comp, attrs);
if (!rule) {
return DefaultComp;
}
return h(
Popover,
{
overlayClassName: 'edit-cell-rule-popover',
open: !!popoverVisible,
...(getPopupContainer ? { getPopupContainer } : {}),
},
{
default: () => DefaultComp,
content: () => ruleMessage,
}
);
};

View File

@@ -1,508 +0,0 @@
<template>
<div :class="prefixCls">
<div v-show="!isEdit" :class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }" @click="handleEdit">
<div class="cell-content" :title="column.ellipsis ? getValues ?? '' : ''">
<!-- update-begin--author:liaozhiyang---date:20240731---forissues/6957editableCell组件值长度为0无法编辑 -->
<!-- update-begin--author:liaozhiyang---date:20240709---forissues/6851editableCell组件值为0时不展示 -->
{{ typeof getValues === 'string' && getValues.length === 0 ? '&nbsp;' : getValues ?? '&nbsp;' }}
<!-- update-end--author:liaozhiyang---date:20240709---forissues/6851editableCell组件值为0时不展示 -->
<!-- update-end--author:liaozhiyang---date:20240731---forissues/6957editableCell组件值长度为0无法编辑 -->
</div>
<FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
</div>
<a-spin v-if="isEdit" :spinning="spinning">
<div :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
<CellComponent
v-bind="getComponentProps"
:component="getComponent"
:style="getWrapperStyle"
:popoverVisible="getRuleVisible"
:rule="getRule"
:ruleMessage="ruleMessage"
:class="getWrapperClass"
ref="elRef"
@change="handleChange"
@options-change="handleOptionsChange"
@pressEnter="handleEnter"
/>
<div :class="`${prefixCls}__action`" v-if="!getRowEditable">
<CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmitClick" />
<CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
</div>
</div>
</a-spin>
</div>
</template>
<script lang="ts">
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';
import { useDesign } from '/@/hooks/web/useDesign';
import { useTableContext } from '../../hooks/useTableContext';
import clickOutside from '/@/directives/clickOutside';
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 { treeToList } from '/@/utils/helper/treeHelper';
import { Spin } from 'ant-design-vue';
export default defineComponent({
name: 'EditableCell',
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, ASpin: Spin },
directives: {
clickOutside,
},
props: {
value: {
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
default: '',
},
record: {
type: Object as PropType<EditRecordRow>,
},
column: {
type: Object as PropType<BasicColumn>,
default: () => ({}),
},
index: propTypes.number,
},
setup(props) {
const table = useTableContext();
const isEdit = ref(false);
const elRef = ref();
const ruleVisible = ref(false);
const ruleMessage = ref('');
const optionsRef = ref<LabelValueOptions>([]);
const currentValueRef = ref<any>(props.value);
const defaultValueRef = ref<any>(props.value);
const spinning = ref<boolean>(false);
const { prefixCls } = useDesign('editable-cell');
const getComponent = computed(() => props.column?.editComponent || 'Input');
const getRule = computed(() => props.column?.editRule);
const getRuleVisible = computed(() => {
return unref(ruleMessage) && unref(ruleVisible);
});
const getIsCheckComp = computed(() => {
const component = unref(getComponent);
return ['Checkbox', 'Switch'].includes(component);
});
const getComponentProps = computed(() => {
const compProps = props.column?.editComponentProps ?? {};
const component = unref(getComponent);
const apiSelectProps: Recordable = {};
if (component === 'ApiSelect') {
apiSelectProps.cache = true;
}
const isCheckValue = unref(getIsCheckComp);
const valueField = isCheckValue ? 'checked' : 'value';
const val = unref(currentValueRef);
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
return {
size: 'small',
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
getCalendarContainer: () => unref(table?.wrapRef.value) ?? document.body,
placeholder: createPlaceholderMessage(unref(getComponent)),
...apiSelectProps,
...omit(compProps, 'onChange'),
[valueField]: value,
};
});
const getValues = computed(() => {
const { editComponentProps, editValueMap } = props.column;
const value = unref(currentValueRef);
if (editValueMap && isFunction(editValueMap)) {
return editValueMap(value);
}
const component = unref(getComponent);
if (!component.includes('Select')) {
return value;
}
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
const option = options.find((item) => `${item.value}` === `${value}`);
return option?.label ?? value;
});
const getWrapperStyle = computed((): CSSProperties => {
if (unref(getIsCheckComp) || unref(getRowEditable)) {
return {};
}
return {
width: 'calc(100% - 48px)',
};
});
const getWrapperClass = computed(() => {
const { align = 'center' } = props.column;
return `edit-cell-align-${align}`;
});
const getRowEditable = computed(() => {
const { editable } = props.record || {};
return !!editable;
});
watchEffect(() => {
defaultValueRef.value = props.value;
currentValueRef.value = props.value;
});
watchEffect(() => {
const { editable } = props.column;
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
isEdit.value = !!editable || unref(getRowEditable);
}
});
function handleEdit() {
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
ruleMessage.value = '';
isEdit.value = true;
nextTick(() => {
const el = unref(elRef);
el?.focus?.();
});
}
async function handleChange(e: any) {
const component = unref(getComponent);
if (!e) {
currentValueRef.value = e;
} else if (e?.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
} else if (component === 'Checkbox') {
currentValueRef.value = (e as ChangeEvent).target.checked;
} else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) {
currentValueRef.value = e;
}
const onChange = props.column?.editComponentProps?.onChange;
if (onChange && isFunction(onChange)) onChange(...arguments);
table.emit?.('edit-change', {
column: props.column,
value: unref(currentValueRef),
record: toRaw(props.record),
});
handleSubmiRule();
}
async function handleSubmiRule() {
const { column, record } = props;
const { editRule } = column;
const currentValue = unref(currentValueRef);
if (editRule) {
if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
ruleVisible.value = true;
const component = unref(getComponent);
ruleMessage.value = createPlaceholderMessage(component);
return false;
}
if (isFunction(editRule)) {
const res = await editRule(currentValue, record as Recordable);
if (!!res) {
ruleMessage.value = res;
ruleVisible.value = true;
return false;
} else {
ruleMessage.value = '';
return true;
}
}
}
ruleMessage.value = '';
return true;
}
async function handleSubmit(needEmit = true, valid = true) {
if (valid) {
const isPass = await handleSubmiRule();
if (!isPass) return false;
}
const { column, index, record } = props;
if (!record) return false;
const { key, dataIndex } = column;
const value = unref(currentValueRef);
if (!key || !dataIndex) return;
const dataKey = (dataIndex || key) as string;
if (!record.editable) {
const { getBindValues } = table;
const { beforeEditSubmit, columns } = unref(getBindValues);
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),
index,
key,
value,
});
} catch (e) {
result = false;
} finally {
spinning.value = false;
}
if (result === false) {
return;
}
}
}
set(record, dataKey, value);
//const record = await table.updateTableData(index, dataKey, value);
needEmit && table.emit?.('edit-end', { record, index, key, value });
isEdit.value = false;
}
async function handleEnter() {
if (props.column?.editRow) {
return;
}
handleSubmit();
}
function handleSubmitClick() {
handleSubmit();
}
function handleCancel() {
isEdit.value = false;
currentValueRef.value = defaultValueRef.value;
const { column, index, record } = props;
const { key, dataIndex } = column;
table.emit?.('edit-cancel', {
record,
index,
key: dataIndex || key,
value: unref(currentValueRef),
});
}
function onClickOutside() {
if (props.column?.editable || unref(getRowEditable)) {
return;
}
const component = unref(getComponent);
if (component.includes('Input')) {
handleCancel();
}
}
// only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) {
const { replaceFields } = props.column?.editComponentProps ?? {};
const component = unref(getComponent);
if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {};
let listOptions: Recordable[] = treeToList(options, { children });
listOptions = listOptions.map((item) => {
return {
label: item[title],
value: item[value],
};
});
optionsRef.value = listOptions as LabelValueOptions;
} else {
optionsRef.value = options;
}
}
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
if (props.record) {
/* eslint-disable */
// update-begin--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
const { dataIndex, key } = props.column;
const field: any = dataIndex || key;
if (isArray(props.record[cbs])) {
const findItem = props.record[cbs]?.find((item) => item[field]);
if (findItem) {
findItem[field] = handle;
} else {
props.record[cbs]?.push({ [field]: handle });
}
} else {
props.record[cbs] = [{ [field]: handle }];
}
// update-end--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
}
}
if (props.record) {
initCbs('submitCbs', handleSubmit);
initCbs('validCbs', handleSubmiRule);
initCbs('cancelCbs', handleCancel);
if (props.column.dataIndex) {
if (!props.record.editValueRefs) props.record.editValueRefs = {};
props.record.editValueRefs[props.column.dataIndex] = currentValueRef;
}
/* eslint-disable */
props.record.onCancelEdit = () => {
// update-begin--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
isArray(props.record?.cancelCbs) &&
props.record?.cancelCbs.forEach((item) => {
const [fn] = Object.values(item);
fn();
});
// update-end--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
};
/* eslint-disable */
props.record.onSubmitEdit = async () => {
if (isArray(props.record?.submitCbs)) {
if (!props.record?.onValid?.()) return;
const submitFns = props.record?.submitCbs || [];
// update-begin--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
submitFns.forEach((item) => {
const [fn] = Object.values(item);
fn(false, false);
});
// update-end--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
table.emit?.('edit-row-end');
return true;
}
};
}
return {
isEdit,
prefixCls,
handleEdit,
currentValueRef,
handleSubmit,
handleChange,
handleCancel,
elRef,
getComponent,
getRule,
onClickOutside,
ruleMessage,
getRuleVisible,
getComponentProps,
handleOptionsChange,
getWrapperStyle,
getWrapperClass,
getRowEditable,
getValues,
handleEnter,
handleSubmitClick,
spinning,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-editable-cell';
.edit-cell-align-left {
text-align: left;
input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
text-align: left;
}
}
.edit-cell-align-center {
text-align: center;
input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
text-align: center;
}
}
.edit-cell-align-right {
text-align: right;
input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
text-align: right;
}
}
.edit-cell-rule-popover {
.ant-popover-inner-content {
padding: 4px 8px;
color: @error-color;
// border: 1px solid @error-color;
border-radius: 2px;
}
}
.@{prefix-cls} {
position: relative;
&__wrapper {
display: flex;
align-items: center;
justify-content: center;
> .ant-select {
min-width: calc(100% - 50px);
}
}
&__icon {
&:hover {
transform: scale(1.2);
svg {
color: @primary-color;
}
}
}
.ellipsis-cell {
.cell-content {
overflow-wrap: break-word;
word-break: break-word;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&__normal {
&-icon {
position: absolute;
top: 4px;
right: 0;
display: none;
width: 20px;
cursor: pointer;
}
}
&:hover {
.@{prefix-cls}__normal-icon {
display: inline-block;
}
}
}
</style>

View File

@@ -1,28 +0,0 @@
import { ComponentType } from '../../types/componentType';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
/**
* @description: 生成placeholder
*/
export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input')) {
return t('common.inputText');
}
if (component.includes('Picker')) {
return t('common.chooseText');
}
if (
component.includes('Select') ||
component.includes('Checkbox') ||
component.includes('Radio') ||
component.includes('Switch') ||
component.includes('DatePicker') ||
component.includes('TimePicker')
) {
return t('common.chooseText');
}
return '';
}

View File

@@ -1,78 +0,0 @@
import type { BasicColumn } from '/@/components/Table/src/types/table';
import { h, Ref, toRaw } from 'vue';
import EditableCell from './EditableCell.vue';
import { isArray } from '/@/utils/is';
interface Params {
text: string;
record: Recordable;
index: number;
}
export function renderEditCell(column: BasicColumn) {
return ({ text: value, record, index }: Params) => {
toRaw(record).onValid = async () => {
if (isArray(record?.validCbs)) {
// update-begin--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
const validFns = (record?.validCbs || []).map((item) => {
const [fn] = Object.values(item);
// @ts-ignore
return fn();
});
// update-end--author:liaozhiyang---date:20240424---for【issues/1165】解决canResize为true时第一行校验不过
const res = await Promise.all(validFns);
return res.every((item) => !!item);
} else {
return false;
}
};
toRaw(record).onEdit = async (edit: boolean, submit = false) => {
if (!submit) {
record.editable = edit;
}
if (!edit && submit) {
if (!(await record.onValid())) return false;
const res = await record.onSubmitEdit?.();
if (res) {
record.editable = false;
return true;
}
return false;
}
// cancel
if (!edit && !submit) {
record.onCancelEdit?.();
}
return true;
};
return h(EditableCell, {
value,
record,
column,
index,
});
};
}
interface Cbs {
[key: string]: Fn;
}
export type EditRecordRow<T = Recordable> = Partial<
{
onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
onValid: () => Promise<boolean>;
editable: boolean;
onCancel: Fn;
onSubmit: Fn;
submitCbs: Cbs[];
cancelCbs: Cbs[];
validCbs: Cbs[];
editValueRefs: Recordable<Ref>;
} & T
>;

View File

@@ -1,536 +0,0 @@
<template>
<Tooltip placement="top" v-bind="getBindProps" >
<template #title>
<span>{{ t('component.table.settingColumn') }}</span>
</template>
<Popover
v-model:open="popoverVisible"
placement="bottomLeft"
trigger="click"
@open-change="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`"
:getPopupContainer="getPopupContainer"
>
<template #title>
<div :class="`${prefixCls}__popover-title`">
<Checkbox :indeterminate="indeterminate" v-model:checked="checkAll" @change="onCheckAllChange">
{{ t('component.table.settingColumnShow') }}
</Checkbox>
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
{{ t('component.table.settingIndexColumnShow') }}
</Checkbox>
<!-- <Checkbox-->
<!-- v-model:checked="checkSelect"-->
<!-- @change="handleSelectCheckChange"-->
<!-- :disabled="!defaultRowSelection"-->
<!-- >-->
<!-- {{ t('component.table.settingSelectColumnShow') }}-->
<!-- </Checkbox>-->
</div>
</template>
<template #content>
<ScrollContainer>
<CheckboxGroup v-model:value="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)">
<DragOutlined class="table-column-drag-icon" />
<Checkbox :value="item.value">
{{ item.label }}
</Checkbox>
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4" :getPopupContainer="getPopupContainer">
<template #title>
{{ t('component.table.settingFixedLeft') }}
</template>
<Icon
icon="line-md:arrow-align-left"
:class="[
`${prefixCls}__fixed-left`,
{
active: item.fixed === 'left',
disabled: !checkedList.includes(item.value),
},
]"
@click="handleColumnFixed(item, 'left')"
/>
</Tooltip>
<Divider type="vertical" />
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4" :getPopupContainer="getPopupContainer">
<template #title>
{{ t('component.table.settingFixedRight') }}
</template>
<Icon
icon="line-md:arrow-align-left"
:class="[
`${prefixCls}__fixed-right`,
{
active: item.fixed === 'right',
disabled: !checkedList.includes(item.value),
},
]"
@click="handleColumnFixed(item, 'right')"
/>
</Tooltip>
</div>
</template>
</CheckboxGroup>
</ScrollContainer>
<div :class="`${prefixCls}__popover-footer`">
<a-button size="small" @click="reset">
{{ t('common.resetText') }}
</a-button>
<a-button size="small" type="primary" @click="saveSetting"> 保存 </a-button>
</div>
</template>
<SettingOutlined />
</Popover>
</Tooltip>
</template>
<script lang="ts">
import type { BasicColumn, ColumnChangeParam } from '../../types/table';
import { defineComponent, ref, reactive, toRefs, watchEffect, nextTick, unref, computed } 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';
import { Icon } from '/@/components/Icon';
import { ScrollContainer } from '/@/components/Container';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
import { useColumnsCache } from '../../hooks/useColumnsCache';
import { useDesign } from '/@/hooks/web/useDesign';
// import { useSortable } from '/@/hooks/web/useSortable';
import { isFunction, isNullAndUnDef } from '/@/utils/is';
import { getPopupContainer as getParentContainer } from '/@/utils';
import { cloneDeep, omit } from 'lodash-es';
import Sortablejs from 'sortablejs';
import type Sortable from 'sortablejs';
interface State {
checkAll: boolean;
isInit?: boolean;
checkedList: string[];
defaultCheckList: string[];
}
interface Options {
label: string;
value: string;
fixed?: boolean | 'left' | 'right';
}
export default defineComponent({
name: 'ColumnSetting',
props: {
isMobile: Boolean,
},
components: {
SettingOutlined,
Popover,
Tooltip,
Checkbox,
CheckboxGroup: Checkbox.Group,
DragOutlined,
ScrollContainer,
Divider,
Icon,
},
emits: ['columns-change'],
setup(props, { emit, attrs }) {
const { t } = useI18n();
const table = useTableContext();
const popoverVisible = ref(false);
// update-begin--author:sunjianlei---date:20221101---for: 修复第一次进入时列表配置不能拖拽
// nextTick(() => popoverVisible.value = false);
// update-end--author:sunjianlei---date:20221101---for: 修复第一次进入时列表配置不能拖拽
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 checkIndex = ref(false);
const checkSelect = ref(false);
const { prefixCls } = useDesign('basic-column-setting');
const getValues = computed(() => {
return unref(table?.getBindValues) || {};
});
const getBindProps = computed(() => {
let obj = {};
if (props.isMobile) {
obj['open'] = false;
}
return obj;
});
let sortable: Sortable;
const sortableOrder = ref<string[]>();
// 列表字段配置缓存
const { saveSetting, resetSetting } = useColumnsCache(
{
state,
popoverVisible,
plainOptions,
plainSortOptions,
sortableOrder,
checkIndex,
},
setColumns,
handleColumnFixed
);
watchEffect(() => {
setTimeout(() => {
const columns = table.getColumns();
if (columns.length && !state.isInit) {
init();
}
}, 0);
});
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;
}
function init() {
const columns = getColumns();
const checkList = table
.getColumns({ ignoreAction: 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;
}
});
}
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([]);
}
}
const indeterminate = computed(() => {
const len = plainOptions.value.length;
let checkedLen = state.checkedList.length;
unref(checkIndex) && checkedLen--;
return checkedLen > 0 && checkedLen < len;
});
// 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);
}
// reset columns
function reset() {
// state.checkedList = [...state.defaultCheckList];
// update-begin--author:liaozhiyang---date:20231103---for【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
state.checkedList = table
.getColumns({ ignoreAction: true })
.map((item) => {
return item.dataIndex || item.title;
})
.filter(Boolean) as string[];
// update-end--author:liaozhiyang---date:20231103---for【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
state.checkAll = true;
plainOptions.value = unref(cachePlainOptions);
plainSortOptions.value = unref(cachePlainOptions);
setColumns(table.getCacheColumns());
if (sortableOrder.value) {
sortable.sort(sortableOrder.value);
}
resetSetting();
}
// Open the pop-up window for drag and drop initialization
function handleVisibleChange() {
if (inited) return;
// update-begin--author:liaozhiyang---date:20240529---for【TV360X-254】列设置闪现及苹果浏览器弹窗过长
setTimeout(() => {
// update-begin--author:liaozhiyang---date:20240529---for【TV360X-254】列设置闪现及苹果浏览器弹窗过长
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), {
animation: 500,
delay: 400,
delayOnTouchOnly: true,
handle: '.table-column-drag-icon ',
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
return;
}
// Sort column
const columns = cloneDeep(plainSortOptions.value);
if (oldIndex > newIndex) {
columns.splice(newIndex, 0, columns[oldIndex]);
columns.splice(oldIndex + 1, 1);
} else {
columns.splice(newIndex + 1, 0, columns[oldIndex]);
columns.splice(oldIndex, 1);
}
plainSortOptions.value = columns;
// update-begin--author:liaozhiyang---date:20230904---for【QQYUN-6424】table字段列表设置不显示后再拖拽字段顺序原本不显示的又显示了
// update-begin--author:liaozhiyang---date:20240522---for【TV360X-108】刷新后勾选之前未勾选的字段拖拽之后该字段对应的表格列消失了
const cols = columns.map((item) => item.value);
const arr = cols.filter((cItem) => state.checkedList.find((lItem) => lItem === cItem));
setColumns(arr);
// 最开始的代码
// setColumns(columns);
// update-end--author:liaozhiyang---date:20240522---for【TV360X-108】刷新后勾选之前未勾选的字段拖拽之后该字段对应的表格列消失了
// update-end--author:liaozhiyang---date:20230904---for【QQYUN-6424】table字段列表设置不显示后再拖拽字段顺序原本不显示的又显示了
},
});
// 记录原始 order 序列
if (!sortableOrder.value) {
sortableOrder.value = sortable.toArray();
}
inited = true;
}, 2000);
}
// Control whether the serial number column is displayed
function handleIndexCheckChange(e: CheckboxChangeEvent) {
table.setProps({
showIndexColumn: e.target.checked,
});
}
// Control whether the check box is displayed
function handleSelectCheckChange(e: CheckboxChangeEvent) {
table.setProps({
rowSelection: e.target.checked ? defaultRowSelection : undefined,
});
}
function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
if (!state.checkedList.includes(item.dataIndex as string)) return;
const columns = getColumns() 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;
if (isFixed && !item.width) {
item.width = 100;
}
table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed });
setColumns(columns);
}
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 };
});
emit('columns-change', data);
}
function getPopupContainer() {
return isFunction(attrs.getPopupContainer) ? attrs.getPopupContainer() : getParentContainer();
}
return {
getBindProps,
t,
...toRefs(state),
popoverVisible,
indeterminate,
onCheckAllChange,
onChange,
plainOptions,
reset,
saveSetting,
prefixCls,
columnListRef,
handleVisibleChange,
checkIndex,
checkSelect,
handleIndexCheckChange,
handleSelectCheckChange,
defaultRowSelection,
handleColumnFixed,
getPopupContainer,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-column-setting';
.table-column-drag-icon {
margin: 0 5px;
cursor: move;
}
.@{prefix-cls} {
&__popover-title {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
}
/* 卡片底部样式 */
&__popover-footer {
position: relative;
top: 7px;
text-align: right;
padding: 4px 0 0;
border-top: 1px solid #f0f0f0;
.ant-btn {
margin-right: 6px;
}
}
&__check-item {
display: flex;
align-items: center;
min-width: 100%;
padding: 4px 16px 8px 0;
.ant-checkbox-wrapper {
width: 100%;
&:hover {
color: @primary-color;
}
}
}
&__fixed-left,
&__fixed-right {
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
&.active,
&:hover {
color: @primary-color;
}
&.disabled {
color: @disabled-color;
cursor: not-allowed;
}
}
&__fixed-right {
transform: rotate(180deg);
}
&__cloumn-list {
svg {
width: 1em !important;
height: 1em !important;
}
.ant-popover-inner-content {
// max-height: 360px;
padding-right: 0;
padding-left: 0;
// overflow: auto;
}
.ant-checkbox-group {
// update-begin--author:liaozhiyang---date:20240118---for【QQYUN-7887】表格列设置宽度过长
// width: 100%;
min-width: 260px;
max-width: min-content;
// update-end--author:liaozhiyang---date:20240118---for【QQYUN-7887】表格列设置宽度过长
// flex-wrap: wrap;
}
// update-begin--author:liaozhiyang---date:20240529---for【TV360X-254】列设置闪现及苹果浏览器弹窗过长
&.ant-popover,
.ant-popover-content,
.ant-popover-inner,
.ant-popover-inner-content,
.scroll-container,
.scrollbar__wrap {
max-width: min-content;
}
// update-end--author:liaozhiyang---date:20240529---for【TV360X-254】列设置闪现及苹果浏览器弹窗过长
.scrollbar {
height: 220px;
}
}
}
</style>

View File

@@ -1,48 +0,0 @@
<template>
<Tooltip placement="top" v-bind="getBindProps">
<template #title>
<span>{{ t('component.table.settingFullScreen') }}</span>
</template>
<FullscreenOutlined @click="toggle" v-if="!isFullscreen" />
<FullscreenExitOutlined @click="toggle" v-else />
</Tooltip>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
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',
props: {
isMobile: Boolean,
},
components: {
FullscreenExitOutlined,
FullscreenOutlined,
Tooltip,
},
setup(props) {
const table = useTableContext();
const { t } = useI18n();
const { toggle, isFullscreen } = useFullscreen(table.wrapRef);
const getBindProps = computed(() => {
let obj = {};
if (props.isMobile) {
obj['visible'] = false;
}
return obj;
});
return {
getBindProps,
toggle,
isFullscreen,
t,
};
},
});
</script>

View File

@@ -1,45 +0,0 @@
<template>
<Tooltip placement="top" v-bind="getBindProps">
<template #title>
<span>{{ t('common.redo') }}</span>
</template>
<RedoOutlined @click="redo" />
</Tooltip>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
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',
props: {
isMobile: Boolean,
},
components: {
RedoOutlined,
Tooltip,
},
setup(props) {
const table = useTableContext();
const { t } = useI18n();
const getBindProps = computed(() => {
let obj = {};
if (props.isMobile) {
obj['visible'] = false;
}
return obj;
});
function redo() {
table.reload();
table.emit!('table-redo');
}
return { getBindProps, redo, t };
},
});
</script>

View File

@@ -1,99 +0,0 @@
<template>
<Tooltip placement="top" v-bind="getBindProps">
<template #title>
<span>{{ t('component.table.settingDens') }}</span>
</template>
<Dropdown placement="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer">
<ColumnHeightOutlined />
<template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
<MenuItem key="large">
<span>{{ t('component.table.settingDensLarge') }}</span>
</MenuItem>
<MenuItem key="middle">
<span>{{ t('component.table.settingDensMiddle') }}</span>
</MenuItem>
<MenuItem key="small">
<span>{{ t('component.table.settingDensSmall') }}</span>
</MenuItem>
</Menu>
</template>
</Dropdown>
</Tooltip>
</template>
<script lang="ts">
import type { SizeType } from '../../types/table';
import { computed, defineComponent, ref } from 'vue';
import { Tooltip, Dropdown, Menu } 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';
import { useRoute } from 'vue-router';
import { createLocalStorage } from '/@/utils/cache';
export default defineComponent({
name: 'SizeSetting',
props: {
isMobile: Boolean,
},
components: {
ColumnHeightOutlined,
Tooltip,
Dropdown,
Menu,
MenuItem: Menu.Item,
},
setup(props) {
const table = useTableContext();
const { t } = useI18n();
const $ls = createLocalStorage();
const route = useRoute();
const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
const getBindProps = computed(() => {
let obj = {};
if (props.isMobile) {
obj['visible'] = false;
}
return obj;
});
function handleTitleClick({ key }: { key: SizeType }) {
selectedKeysRef.value = [key];
table.setProps({
size: key,
});
// update-begin--author:liaozhiyang---date:20240604---for【TV360X-100】缓存表格密度
$ls.set(cacheKey.value, key);
// update-end--author:liaozhiyang---date:20240604---for【TV360X-100】缓存表格密度
}
// update-begin--author:liaozhiyang---date:20240604---for【TV360X-100】缓存表格密度
const cacheKey = computed(() => {
const path = route.path;
let key = path.replace(/[\/\\]/g, '_');
let cacheKey = table.getBindValues.value.tableSetting?.cacheKey;
if (cacheKey) {
key += ':' + cacheKey;
}
return 'tableSizeCache:' + key;
});
const local: SizeType | null = $ls.get(cacheKey.value);
if (local) {
selectedKeysRef.value = [local];
table.setProps({
size: local,
});
}
// update-end--author:liaozhiyang---date:20240604---for【TV360X-100】缓存表格密度
return {
getBindProps,
handleTitleClick,
selectedKeysRef,
getPopupContainer,
t,
};
},
});
</script>

View File

@@ -1,74 +0,0 @@
<template>
<div class="table-settings">
<RedoSetting v-if="getSetting.redo" :isMobile="isMobile" :getPopupContainer="getTableContainer" />
<SizeSetting v-if="getSetting.size" :isMobile="isMobile" :getPopupContainer="getTableContainer" />
<ColumnSetting v-if="getSetting.setting" :isMobile="isMobile" @columns-change="handleColumnChange" :getPopupContainer="getTableContainer" />
<FullScreenSetting v-if="getSetting.fullScreen" :isMobile="isMobile" :getPopupContainer="getTableContainer" />
</div>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import type { TableSetting, ColumnChangeParam } from '../../types/table';
import { defineComponent, computed, unref } from 'vue';
import ColumnSetting from './ColumnSetting.vue';
import SizeSetting from './SizeSetting.vue';
import RedoSetting from './RedoSetting.vue';
import FullScreenSetting from './FullScreenSetting.vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
export default defineComponent({
name: 'TableSetting',
components: {
ColumnSetting,
SizeSetting,
RedoSetting,
FullScreenSetting,
},
props: {
setting: {
type: Object as PropType<TableSetting>,
default: () => ({}),
},
mode: String,
},
emits: ['columns-change'],
setup(props, { emit }) {
const { t } = useI18n();
const table = useTableContext();
const getSetting = computed((): TableSetting => {
return {
redo: true,
size: true,
setting: true,
fullScreen: false,
...props.setting,
};
});
const isMobile = computed(() => props.mode === 'mobile');
function handleColumnChange(data: ColumnChangeParam[]) {
emit('columns-change', data);
}
function getTableContainer() {
return table ? unref(table.wrapRef) : document.body;
}
return { getSetting, t, handleColumnChange, getTableContainer, isMobile };
},
});
</script>
<style lang="less">
.table-settings {
& > * {
margin-right: 12px;
}
svg {
width: 1.3em;
height: 1.3em;
}
}
</style>

View File

@@ -1,30 +0,0 @@
import componentSetting from '/@/settings/componentSetting';
const { table } = componentSetting;
const { pageSizeOptions, defaultPageSize, defaultSize, fetchSetting, defaultSortFn, defaultFilterFn } = table;
export const ROW_KEY = 'key';
// Optional display number per page;
export const PAGE_SIZE_OPTIONS = pageSizeOptions;
// Number of items displayed per page
export const PAGE_SIZE = defaultPageSize;
// Common interface field settings
export const FETCH_SETTING = fetchSetting;
// Configure general sort function
export const DEFAULT_SORT_FN = defaultSortFn;
export const DEFAULT_FILTER_FN = defaultFilterFn;
// Default layout of table cells
export const DEFAULT_ALIGN = 'center';
// Default Size
export const DEFAULT_SIZE = defaultSize;
export const INDEX_COLUMN_FLAG = 'INDEX';
export const ACTION_COLUMN_FLAG = 'ACTION';

View File

@@ -1,348 +0,0 @@
import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import type { ComputedRef } from 'vue';
import { Table } from 'ant-design-vue';
import { computed, Ref, ref, toRaw, unref, watch, reactive } from 'vue';
import { renderEditCell } from '../components/editable';
import { usePermission } from '/@/hooks/web/usePermission';
import { useI18n } from '/@/hooks/web/useI18n';
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 { CUS_SEL_COLUMN_KEY } from './useCustomSelection';
function handleItem(item: BasicColumn, ellipsis: boolean) {
const { key, dataIndex, children } = item;
item.align = item.align || DEFAULT_ALIGN;
if (ellipsis) {
if (!key) {
item.key = dataIndex;
}
if (!isBoolean(item.ellipsis)) {
Object.assign(item, {
ellipsis,
});
}
}
if (children && children.length) {
handleChildren(children, !!ellipsis);
}
}
function handleChildren(children: BasicColumn[] | undefined, ellipsis: boolean) {
if (!children) return;
children.forEach((item) => {
const { children } = item;
handleItem(item, ellipsis);
handleChildren(children, ellipsis);
});
}
function handleIndexColumn(propsRef: ComputedRef<BasicTableProps>, getPaginationRef: ComputedRef<boolean | PaginationProps>, columns: BasicColumn[]) {
const { t } = useI18n();
const { showIndexColumn, indexColumnProps, isTreeTable } = unref(propsRef);
let pushIndexColumns = false;
if (unref(isTreeTable)) {
return;
}
columns.forEach(() => {
const indIndex = columns.findIndex((column) => column.flag === INDEX_COLUMN_FLAG);
if (showIndexColumn) {
pushIndexColumns = indIndex === -1;
} else if (!showIndexColumn && indIndex !== -1) {
columns.splice(indIndex, 1);
}
});
if (!pushIndexColumns) return;
const isFixedLeft = columns.some((item) => item.fixed === 'left');
columns.unshift({
flag: INDEX_COLUMN_FLAG,
width: 50,
title: t('component.table.index'),
align: 'center',
customRender: ({ index }) => {
const getPagination = unref(getPaginationRef);
if (isBoolean(getPagination)) {
return `${index + 1}`;
}
const { current = 1, pageSize = PAGE_SIZE } = getPagination;
return ((current < 1 ? 1 : current) - 1) * pageSize + index + 1;
},
...(isFixedLeft
? {
fixed: 'left',
}
: {}),
...indexColumnProps,
});
}
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
const { actionColumn, showActionColumn } = unref(propsRef);
if (!actionColumn || !showActionColumn) return;
const hasIndex = columns.findIndex((column) => column.flag === ACTION_COLUMN_FLAG);
if (hasIndex === -1) {
columns.push({
...columns[hasIndex],
...actionColumn,
flag: ACTION_COLUMN_FLAG,
});
}
}
export function useColumns(
propsRef: ComputedRef<BasicTableProps>,
getPaginationRef: ComputedRef<boolean | PaginationProps>,
handleCustomSelectColumn: Fn
) {
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
let cacheColumns = unref(propsRef).columns;
const getColumnsRef = computed(() => {
const columns = cloneDeep(unref(columnsRef));
handleIndexColumn(propsRef, getPaginationRef, columns);
handleActionColumn(propsRef, columns);
// update-begin--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
handleCustomSelectColumn(columns);
// update-end--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
if (!columns) {
return [];
}
const { ellipsis } = unref(propsRef);
columns.forEach((item) => {
const { customRender, slots } = item;
handleItem(item, Reflect.has(item, 'ellipsis') ? !!item.ellipsis : !!ellipsis && !customRender && !slots);
});
return columns;
});
function isIfShow(column: BasicColumn): boolean {
const ifShow = column.ifShow;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(column);
}
return isIfShow;
}
const { hasPermission } = usePermission();
const getViewColumns = computed(() => {
const viewColumns = sortFixedColumn(unref(getColumnsRef));
const columns = cloneDeep(viewColumns);
const result = columns
.filter((column) => {
return hasPermission(column.auth) && isIfShow(column);
})
.map((column) => {
// update-begin--author:liaozhiyang---date:20230718---for: 【issues-179】antd3 一些警告以及报错(针对表格)
if(column.slots?.customRender) {
// slots的备份兼容老的写法转成新写法避免控制台警告
column.slotsBak = column.slots;
delete column.slots;
}
// update-end--author:liaozhiyang---date:20230718---for: 【issues-179】antd3 一些警告以及报错(针对表格)
const { slots, customRender, format, edit, editRow, flag, title: metaTitle } = column;
if (!slots || !slots?.title) {
// column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title as string;
Reflect.deleteProperty(column, 'title');
}
//update-begin-author:taoyan date:20211203 for:【online报表】分组标题显示错误都显示成了联系信息 LOWCOD-2343
if (column.children) {
column.title = metaTitle;
}
//update-end-author:taoyan date:20211203 for:【online报表】分组标题显示错误都显示成了联系信息 LOWCOD-2343
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);
});
// update-begin--author:liaozhiyang---date:20230919---for【QQYUN-6387】展开写法去掉报错
if (propsRef.value.expandedRowKeys && !propsRef.value.isTreeTable) {
let index = 0;
const findIndex = result.findIndex((item) => item.key === CUS_SEL_COLUMN_KEY);
if (findIndex != -1) {
index = findIndex + 1;
}
const next: any = result[index + 1];
let expand = Table.EXPAND_COLUMN;
if (next && (next['fixed'] == true || next['fixed'] == 'left')) {
expand = Object.assign(expand, { fixed: 'left' });
}
result.splice(index, 0, expand);
}
return result;
// update-end--author:liaozhiyang---date:20230919---for【QQYUN-6387】展开写法去掉报错
});
watch(
() => unref(propsRef).columns,
(columns) => {
columnsRef.value = columns;
cacheColumns = columns?.filter((item) => !item.flag) ?? [];
}
);
function setCacheColumnsByField(dataIndex: string | undefined, value: Partial<BasicColumn>) {
if (!dataIndex || !value) {
return;
}
cacheColumns.forEach((item) => {
if (item.dataIndex === dataIndex) {
Object.assign(item, value);
return;
}
});
}
// update-begin--author:sunjianlei---date:20220523---for: 【VUEN-1089】合并vben最新版代码解决表格字段排序问题
/**
* set columns
* @param columnList keycolumn
*/
function setColumns(columnList: Partial<BasicColumn>[] | (string | string[])[]) {
const columns = cloneDeep(columnList);
if (!isArray(columns)) return;
if (columns.length <= 0) {
columnsRef.value = [];
return;
}
const firstColumn = columns[0];
const cacheKeys = cacheColumns.map((item) => item.dataIndex);
if (!isString(firstColumn) && !isArray(firstColumn)) {
columnsRef.value = columns as BasicColumn[];
} else {
const columnKeys = (columns as (string | string[])[]).map((m) => m.toString());
const newColumns: BasicColumn[] = [];
cacheColumns.forEach((item) => {
newColumns.push({
...item,
defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)),
});
});
// Sort according to another array
if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => {
return columnKeys.indexOf(prev.dataIndex?.toString() as string) - columnKeys.indexOf(next.dataIndex?.toString() as string);
});
}
columnsRef.value = newColumns;
}
}
// update-end--author:sunjianlei---date:20220523---for: 【VUEN-1089】合并vben最新版代码解决表格字段排序问题
function getColumns(opt?: GetColumnsParams) {
const { ignoreIndex, ignoreAction, sort } = opt || {};
let columns = toRaw(unref(getColumnsRef));
if (ignoreIndex) {
columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG);
}
if (ignoreAction) {
columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG);
}
// update-begin--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
// 过滤自定义选择列
columns = columns.filter((item) => item.key !== CUS_SEL_COLUMN_KEY);
// update-enb--author:sunjianlei---date:220230630---for【QQYUN-5571】自封装选择列解决数据行选择卡顿问题
if (sort) {
columns = sortFixedColumn(columns);
}
return columns;
}
function getCacheColumns() {
return cacheColumns;
}
return {
getColumnsRef,
getCacheColumns,
getColumns,
setColumns,
getViewColumns,
setCacheColumnsByField,
};
}
function sortFixedColumn(columns: BasicColumn[]) {
const fixedLeftColumns: BasicColumn[] = [];
const fixedRightColumns: BasicColumn[] = [];
const defColumns: BasicColumn[] = [];
for (const column of columns) {
if (column.fixed === 'left') {
fixedLeftColumns.push(column);
continue;
}
if (column.fixed === 'right') {
fixedRightColumns.push(column);
continue;
}
defColumns.push(column);
}
return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter((item) => !item.defaultHidden);
}
// format cell
export function formatCell(text: string, format: CellFormat, record: Recordable, index: number) {
if (!format) {
return text;
}
// custom function
if (isFunction(format)) {
return format(text, record, index);
}
try {
// date type
const DATE_FORMAT_PREFIX = 'date|';
if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) {
const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
if (!dateFormat) {
return text;
}
return formatToDate(text, dateFormat);
}
// Map
if (isMap(format)) {
return format.get(text);
}
} catch (error) {
return text;
}
}

View File

@@ -1,141 +0,0 @@
import { computed, nextTick, unref, watchEffect } from 'vue';
import { router } from '/@/router';
import { useRoute } from 'vue-router';
import { createLocalStorage } from '/@/utils/cache';
import { useTableContext } from './useTableContext';
import { useMessage } from '/@/hooks/web/useMessage';
/**
* 列表配置缓存
*/
export function useColumnsCache(opt, setColumns, handleColumnFixed) {
let isInit = false;
const table = useTableContext();
const $ls = createLocalStorage();
const { createMessage: $message } = useMessage();
const route = useRoute();
// 列表配置缓存key
const cacheKey = computed(() => {
// update-begin--author:liaozhiyang---date:20240226---for【QQYUN-8367】online报表配置列展示保存影响到其他页面的table字段的显示隐藏开发环境热更新会有此问题生产环境无问题
const path = route.path;
let key = path.replace(/[\/\\]/g, '_');
// update-end--author:liaozhiyang---date:20240226---for【QQYUN-8367】online报表配置列展示保存影响到其他页面的table字段的显示隐藏开发环境热更新会有此问题生产环境无问题
let cacheKey = table.getBindValues.value.tableSetting?.cacheKey;
if (cacheKey) {
key += ':' + cacheKey;
}
return 'columnCache:' + key;
});
watchEffect(() => {
const columns = table.getColumns();
if (columns.length) {
init();
}
});
async function init() {
if (isInit) {
return;
}
isInit = true;
let columnCache = $ls.get(cacheKey.value);
if (columnCache && columnCache.checkedList) {
const { checkedList, sortedList, sortableOrder, checkIndex } = columnCache;
await nextTick();
// checkbox的排序缓存
opt.sortableOrder.value = sortableOrder;
// checkbox的选中缓存
opt.state.checkedList = checkedList;
// tableColumn的排序缓存
opt.plainSortOptions.value.sort((prev, next) => {
return sortedList.indexOf(prev.value) - sortedList.indexOf(next.value);
});
// 重新排序tableColumn
checkedList.sort((prev, next) => sortedList.indexOf(prev) - sortedList.indexOf(next));
// 是否显示行号列
if (checkIndex) {
table.setProps({ showIndexColumn: true });
}
setColumns(checkedList);
// 设置固定列
setColumnFixed(columnCache);
}
}
/** 设置被固定的列 */
async function setColumnFixed(columnCache) {
const { fixedColumns } = columnCache;
const columns = opt.plainOptions.value;
for (const column of columns) {
let fixedCol = fixedColumns.find((fc) => fc.key === (column.key || column.dataIndex));
if (fixedCol) {
await nextTick();
handleColumnFixed(column, fixedCol.fixed);
}
}
}
// 判断列固定状态
const fixedReg = /^(true|left|right)$/;
/** 获取被固定的列 */
function getFixedColumns() {
let fixedColumns: any[] = [];
const columns = opt.plainOptions.value;
for (const column of columns) {
if (fixedReg.test((column.fixed ?? '').toString())) {
fixedColumns.push({
key: column.key || column.dataIndex,
fixed: column.fixed === true ? 'left' : column.fixed,
});
}
}
return fixedColumns;
}
/** 保存列配置 */
function saveSetting() {
const { checkedList } = opt.state;
const sortedList = unref(opt.plainSortOptions).map((item) => item.value);
$ls.set(cacheKey.value, {
// 保存的列
checkedList,
// 排序后的列
sortedList,
// 是否显示行号列
checkIndex: unref(opt.checkIndex),
// checkbox原始排序
sortableOrder: unref(opt.sortableOrder),
// 固定列
fixedColumns: getFixedColumns(),
});
$message.success('保存成功');
// 保存之后直接关闭
opt.popoverVisible.value = false;
}
/** 重置(删除)列配置 */
async function resetSetting() {
// 重置固定列
await resetFixedColumn();
$ls.remove(cacheKey.value);
$message.success('重置成功');
}
async function resetFixedColumn() {
const columns = opt.plainOptions.value;
for (const column of columns) {
column.fixed;
if (fixedReg.test((column.fixed ?? '').toString())) {
await nextTick();
handleColumnFixed(column, null);
}
}
}
return {
saveSetting,
resetSetting,
};
}

View File

@@ -1,108 +0,0 @@
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';
interface Options {
setSelectedRowKeys: (keys: string[]) => void;
getSelectRowKeys: () => string[];
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
) {
const customRow = (record: Recordable, index: number) => {
return {
onClick: (e: Event) => {
e?.stopPropagation();
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 isCheckbox = rowSelection.type === 'checkbox';
if (isCheckbox) {
// 找到tr
const tr: HTMLElement = (e as MouseEvent).composedPath?.().find((dom: HTMLElement) => dom.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]);
return;
}
const keyIndex = keys.findIndex((item) => item === key);
keys.splice(keyIndex, 1);
setSelectedRowKeys(keys);
return;
}
const isRadio = rowSelection.type === 'radio';
if (isRadio) {
// update-begin--author:liaozhiyang---date:20231016---for【QQYUN-6794】table列表增加radio禁用功能
const rowSelection = propsRef.value.rowSelection;
if (rowSelection.getCheckboxProps) {
const result = rowSelection.getCheckboxProps(record);
if (result.disabled) {
return;
}
}
// update-end--author:liaozhiyang---date:20231016---for【QQYUN-6794】table列表增加radio禁用功能
if (!keys.includes(key)) {
if (keys.length) {
clearSelectedRowKeys();
}
setSelectedRowKeys([key]);
return;
} else {
// update-begin--author:liaozhiyang---date:20240527---for【TV360X-359】erp主表点击已选中的选到了最后一个
// 点击已经选中的直接return不在做操作
return;
// update-end--author:liaozhiyang---date:20240527---for【TV360X-359】erp主表点击已选中的选到了最后一个
}
clearSelectedRowKeys();
}
}
handleClick();
emit('row-click', record, index, e);
},
onDblclick: (event: Event) => {
emit('row-dbClick', record, index, event);
},
onContextmenu: (event: Event) => {
emit('row-contextmenu', record, index, event);
},
onMouseenter: (event: Event) => {
emit('row-mouseenter', record, index, event);
},
onMouseleave: (event: Event) => {
emit('row-mouseleave', record, index, event);
},
};
};
return {
customRow,
};
}

View File

@@ -1,636 +0,0 @@
import type { BasicColumn } from '/@/components/Table';
import type { Ref, ComputedRef } from 'vue';
import type { BasicTableProps, PaginationProps, TableRowSelection } from '/@/components/Table';
import { computed, nextTick, onUnmounted, ref, toRaw, unref, watch, watchEffect } from 'vue';
import { omit, isEqual } from 'lodash-es';
import { throttle } from 'lodash-es';
import { Checkbox, Radio } from 'ant-design-vue';
import { isFunction } from '/@/utils/is';
import { findNodeAll } from '/@/utils/helper/treeHelper';
import { ROW_KEY } from '/@/components/Table/src/const';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import { useMessage } from '/@/hooks/web/useMessage';
import { ModalFunc } from 'ant-design-vue/lib/modal/Modal';
// 自定义选择列的key
export const CUS_SEL_COLUMN_KEY = 'j-custom-selected-column';
/**
* 自定义选择列
*/
export function useCustomSelection(
propsRef: ComputedRef<BasicTableProps>,
emit: EmitType,
wrapRef: Ref<null | HTMLDivElement>,
getPaginationRef: ComputedRef<boolean | PaginationProps>,
tableData: Ref<Recordable[]>,
childrenColumnName: ComputedRef<string>
) {
const { createConfirm } = useMessage();
// 表格body元素
const bodyEl = ref<HTMLDivElement>();
// body元素高度
const bodyHeight = ref<number>(0);
// 表格tr高度
const rowHeight = ref<number>(0);
// body 滚动高度
const scrollTop = ref(0);
// 选择的key
const selectedKeys = ref<string[]>([]);
// 选择的行
const selectedRows = ref<Recordable[]>([]);
// 变更的行
let changeRows: Recordable[] = [];
let allSelected: boolean = false;
// 扁平化数据children数据也会放到一起
const flattedData = computed(() => {
// update-begin--author:liaozhiyang---date:20231016---for【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
const data = flattenData(tableData.value, childrenColumnName.value);
const rowSelection = propsRef.value.rowSelection;
if (rowSelection?.type === 'checkbox' && rowSelection.getCheckboxProps) {
for (let i = 0, len = data.length; i < len; i++) {
const record = data[i];
const result = rowSelection.getCheckboxProps(record);
if (result.disabled) {
data.splice(i, 1);
i--;
len--;
}
}
}
return data;
// update-end--author:liaozhiyang---date:20231016---for【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
});
const getRowSelectionRef = computed((): TableRowSelection | null => {
const { rowSelection } = unref(propsRef);
if (!rowSelection) {
return null;
}
return {
preserveSelectedRowKeys: true,
// selectedRowKeys: unref(selectedKeys),
// onChange: (selectedRowKeys: string[]) => {
// setSelectedRowKeys(selectedRowKeys);
// },
...omit(rowSelection, ['onChange', 'selectedRowKeys']),
};
});
// 是否是单选
const isRadio = computed(() => {
return getRowSelectionRef.value?.type === 'radio';
});
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
});
// 列key字段
const getRowKey = computed(() => {
const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
// 获取行的key字段数据
const getRecordKey = (record) => {
if (!getRowKey.value) {
return record[ROW_KEY];
} else if (isFunction(getRowKey.value)) {
return getRowKey.value(record);
} else {
return record[getRowKey.value];
}
};
// 分页配置
const getPagination = computed<PaginationProps>(() => {
return typeof getPaginationRef.value === 'boolean' ? {} : getPaginationRef.value;
});
// 当前页条目数量
const currentPageSize = computed(() => {
const { pageSize = 10, total = flattedData.value.length } = getPagination.value;
return pageSize > total ? total : pageSize;
});
// 选择列表头props
const selectHeaderProps = computed(() => {
return {
onSelectAll,
isRadio: isRadio.value,
selectedLength: flattedData.value.filter((data) => selectedKeys.value.includes(getRecordKey(data))).length,
// update-begin--author:liaozhiyang---date:20240511---for【QQYUN-9289】解决表格条数不足pageSize数量时行数全部勾选但是全选框不勾选
// 【TV360X-53】为空时会报错加强判断
pageSize: tableData.value?.length ?? 0,
// update-end--author:liaozhiyang---date:20240511---for【QQYUN-9289】解决表格条数不足pageSize数量时行数全部勾选但是全选框不勾选
// 【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
disabled: flattedData.value.length == 0,
hideSelectAll: unref(propsRef)?.rowSelection?.hideSelectAll,
};
});
// 监听传入的selectedRowKeys
// update-begin--author:liaozhiyang---date:20240306---for【QQYUN-8390】部门人员组件点击重置未清空selectedRowKeys.value=[]watch没监听到加deep
watch(
() => unref(propsRef)?.rowSelection?.selectedRowKeys,
(val: string[]) => {
// 解决selectedRowKeys在页面调用处使用ref失效
const value = unref(val);
if (Array.isArray(value) && !sameArray(value, selectedKeys.value)) {
setSelectedRowKeys(value);
}
},
{
immediate: true,
deep: true
}
);
// update-end--author:liaozhiyang---date:20240306---for【QQYUN-8390】部门人员组件点击重置未清空selectedRowKeys.value=[]watch没监听到加deep
/**
* 2024-03-06
* liaozhiyang
* 判断是否同一个数组 (引用地址,长度,元素位置信息相同才是同一个数组。数组元素只有字符串)
*/
function sameArray(a, b) {
if (a === b) {
if (a.length === b.length) {
return a.toString() === b.toString();
} else {
return false;
}
} else {
// update-begin--author:liaozhiyang---date:20240425---for【QQYUN-9123】popupdict打开弹窗打开程序运行
if (isEqual(a, b)) {
return true;
}
// update-end--author:liaozhiyang---date:20240425---for【QQYUN-9123】popupdict打开弹窗打开程序运行
return false;
}
}
// 当任意一个变化时,触发同步检测
watch([selectedKeys, selectedRows], () => {
nextTick(() => {
syncSelectedRows();
});
});
// 监听滚动条事件
const onScrollTopChange = throttle((e) => (scrollTop.value = e?.target?.scrollTop), 150);
let bodyResizeObserver: Nullable<ResizeObserver> = null;
// 获取首行行高
watchEffect(() => {
if (bodyEl.value) {
// 监听div高度变化
bodyResizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.target === bodyEl.value && entry.contentRect) {
const { height } = entry.contentRect;
bodyHeight.value = Math.ceil(height);
}
}
});
bodyResizeObserver.observe(bodyEl.value);
const el = bodyEl.value?.querySelector('tbody.ant-table-tbody tr.ant-table-row') as HTMLDivElement;
if (el) {
rowHeight.value = el.offsetHeight;
return;
}
}
rowHeight.value = 50;
// 这种写法是为了监听到 size 的变化
propsRef.value.size && void 0;
});
onMountedOrActivated(async () => {
bodyEl.value = await getTableBody(wrapRef.value!);
bodyEl.value.addEventListener('scroll', onScrollTopChange);
});
onUnmounted(() => {
if (bodyEl.value) {
bodyEl.value?.removeEventListener('scroll', onScrollTopChange);
}
if (bodyResizeObserver != null) {
bodyResizeObserver.disconnect();
}
});
// 选择全部
function onSelectAll(checked: boolean) {
// update-begin--author:liaozhiyang---date:20231122---for【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
if (unref(propsRef)?.rowSelection?.onSelectAll) {
allSelected = checked;
changeRows = getInvertRows(selectedRows.value);
}
// update-end--author:liaozhiyang---date:20231122---for【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
// 取消全选
if (!checked) {
// update-begin--author:liaozhiyang---date:20240510---for【issues/1173】取消全选只是当前页面取消
// selectedKeys.value = [];
// selectedRows.value = [];
// emitChange('all');
flattedData.value.forEach((item) => {
updateSelected(item, false);
});
// update-end--author:liaozhiyang---date:20240510---for【issues/1173】取消全选只是当前页面取消
return;
}
let modal: Nullable<ReturnType<ModalFunc>> = null;
// 全选
const checkAll = () => {
if (modal != null) {
modal.update({
content: '正在分批全选,请稍后……',
cancelButtonProps: { disabled: true },
});
}
let showCount = 0;
// 最小选中数量
let minSelect = 100;
const hidden: Recordable[] = [];
flattedData.value.forEach((item, index, array) => {
if (array.length > 120) {
if (showCount <= minSelect && recordIsShow(index, Math.max((minSelect - 10) / 2, 3))) {
showCount++;
updateSelected(item, checked);
} else {
hidden.push(item);
}
} else {
updateSelected(item, checked);
}
});
if (hidden.length > 0) {
return batchesSelectAll(hidden, checked, minSelect);
} else {
emitChange('all');
}
};
// 当数据量大于120条时全选会导致页面卡顿需进行慢速全选
if (flattedData.value.length > 120) {
modal = createConfirm({
title: '全选',
content: '当前数据量较大,全选可能会导致页面卡顿,确定要执行此操作吗?',
iconType: 'warning',
onOk: () => checkAll(),
});
} else {
checkAll();
}
}
// 分批全选
function batchesSelectAll(hidden: Recordable[], checked: boolean, minSelect: number) {
return new Promise<void>((resolve) => {
(function call() {
// 每隔半秒钟选择100条数据
setTimeout(() => {
const list = hidden.splice(0, minSelect);
if (list.length > 0) {
list.forEach((item) => {
updateSelected(item, checked);
});
call();
} else {
setTimeout(() => {
emitChange('all');
// update-begin--author:liaozhiyang---date:20230811---for【QQYUN-5687】批量选择提示成功后又来一个提示
setTimeout(() =>resolve(), 0);
// update-end--author:liaozhiyang---date:20230811---for【QQYUN-5687】批量选择提示成功后又来一个提示
}, 500);
}
}, 300);
})();
});
}
// 选中单个
function onSelect(record, checked) {
updateSelected(record, checked);
emitChange();
}
function updateSelected(record, checked) {
const recordKey = getRecordKey(record);
if (isRadio.value) {
selectedKeys.value = [recordKey];
selectedRows.value = [record];
return;
}
const index = selectedKeys.value.findIndex((key) => key === recordKey);
if (checked) {
if (index === -1) {
selectedKeys.value.push(recordKey);
selectedRows.value.push(record);
}
} else {
if (index !== -1) {
selectedKeys.value.splice(index, 1);
selectedRows.value.splice(index, 1);
}
}
}
// 调用用户自定义的onChange事件
function emitChange(mode = 'single') {
const { rowSelection } = unref(propsRef);
if (rowSelection) {
const { onChange } = rowSelection;
if (onChange && isFunction(onChange)) {
setTimeout(() => {
onChange(selectedKeys.value, selectedRows.value);
}, 0);
}
}
emit('selection-change', {
keys: getSelectRowKeys(),
rows: getSelectRows(),
});
// update-begin--author:liaozhiyang---date:20231122---for【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
if (mode == 'all') {
const rowSelection = unref(propsRef)?.rowSelection;
if (rowSelection?.onSelectAll) {
rowSelection.onSelectAll(allSelected, toRaw(getSelectRows()), toRaw(changeRows));
}
}
// update-end--author:liaozhiyang---date:20231122---for【issues/5577】BasicTable组件全选和取消全选时不触发
}
// 用于判断是否是自定义选择列
function isCustomSelection(column: BasicColumn) {
return column.key === CUS_SEL_COLUMN_KEY;
}
/**
* 判断当前行是否可视,虚拟滚动用
* @param index 行下标
* @param threshold 前后阈值默认可视区域前后显示3条
*/
function recordIsShow(index: number, threshold = 3) {
// 只有数据量大于50条时才会进行虚拟滚动
const isVirtual = flattedData.value.length > 50;
if (isVirtual) {
// 根据 scrollTop、bodyHeight、rowHeight 计算出当前行是否可视阈值前后3条
// flag1 = 判断当前行是否在可视区域上方3条
const flag1 = scrollTop.value - rowHeight.value * threshold < index * rowHeight.value;
// flag2 = 判断当前行是否在可视区域下方3条
const flag2 = index * rowHeight.value < scrollTop.value + bodyHeight.value + rowHeight.value * threshold;
// 全部条件满足时,才显示当前行
return flag1 && flag2;
}
return true;
}
// 自定义渲染Body
function bodyCustomRender(params) {
const { index } = params;
// update-begin--author:liaozhiyang---date:20231009--for【issues/776】显示100条/页复选框只能显示3个的问题
if (propsRef.value.canResize && !recordIsShow(index)) {
return '';
}
if (isRadio.value) {
return renderRadioComponent(params);
} else {
return renderCheckboxComponent(params);
}
// update-end--author:liaozhiyang---date:20231009---for【issues/776】显示100条/页复选框只能显示3个的问题
}
/**
* 渲染checkbox组件
*/
function renderCheckboxComponent({ record }) {
const recordKey = getRecordKey(record);
// 获取用户自定义checkboxProps
const checkboxProps = ((getCheckboxProps) => {
if (typeof getCheckboxProps === 'function') {
try {
return getCheckboxProps(record) ?? {};
} catch (error) {
console.error(error);
}
}
return {};
})(propsRef.value.rowSelection?.getCheckboxProps);
return (
<Checkbox
{...checkboxProps}
key={'j-select__' + recordKey}
checked={selectedKeys.value.includes(recordKey)}
onUpdate:checked={(checked) => onSelect(record, checked)}
// update-begin--author:liaozhiyang---date:20230326---for【QQYUN-8694】BasicTable在使用clickToRowSelect=true下selection-change 事件在触发多次
onClick={(e) => e.stopPropagation()}
// update-end--author:liaozhiyang---date:20230326---for【QQYUN-8694】BasicTable在使用clickToRowSelect=true下selection-change 事件在触发多次
/>
);
}
/**
* 渲染radio组件
*/
function renderRadioComponent({ record }) {
const recordKey = getRecordKey(record);
// update-begin--author:liaozhiyang---date:20231016---for【QQYUN-6794】table列表增加radio禁用功能
// 获取用户自定义radioProps
const checkboxProps = (() => {
const rowSelection = propsRef.value.rowSelection;
if (rowSelection?.getCheckboxProps) {
return rowSelection.getCheckboxProps(record);
}
return {};
})();
// update-end--author:liaozhiyang---date:20231016---for【QQYUN-6794】table列表增加radio禁用功能
return (
<Radio
{...checkboxProps}
key={'j-select__' + recordKey}
checked={selectedKeys.value.includes(recordKey)}
onUpdate:checked={(checked) => onSelect(record, checked)}
// update-begin--author:liaozhiyang---date:20230326---for【QQYUN-8694】BasicTable在使用clickToRowSelect=true下selection-change 事件在触发多次
onClick={(e) => e.stopPropagation()}
// update-end--author:liaozhiyang---date:20230326---for【QQYUN-8694】BasicTable在使用clickToRowSelect=true下selection-change 事件在触发多次
/>
);
}
// 创建选择列
function handleCustomSelectColumn(columns: BasicColumn[]) {
// update-begin--author:liaozhiyang---date:20230919---for【issues/757】JPopup表格的选择列固定配置不生效
const rowSelection = propsRef.value.rowSelection;
if (!rowSelection) {
return;
}
const isFixedLeft = rowSelection.fixed || columns.some((item) => item.fixed === 'left');
// update-begin--author:liaozhiyang---date:20230919---for【issues/757】JPopup表格的选择列固定配置不生效
columns.unshift({
title: '选择列',
flag: 'CHECKBOX',
key: CUS_SEL_COLUMN_KEY,
width: 50,
minWidth: 50,
maxWidth: 50,
align: 'center',
...(isFixedLeft ? { fixed: 'left' } : {}),
customRender: bodyCustomRender,
});
}
// 清空所有选择
function clearSelectedRowKeys() {
onSelectAll(false);
}
// 通过 selectedKeys 同步 selectedRows
function syncSelectedRows() {
if (selectedKeys.value.length !== selectedRows.value.length) {
setSelectedRowKeys(selectedKeys.value);
}
}
// 设置选择的key
function setSelectedRowKeys(rowKeys: string[]) {
const isSomeRowKeys = selectedKeys.value === rowKeys;
selectedKeys.value = rowKeys;
const allSelectedRows = findNodeAll(
toRaw(unref(flattedData)).concat(toRaw(unref(selectedRows))),
(item) => rowKeys.includes(getRecordKey(item)),
{
children: propsRef.value.childrenColumnName ?? 'children',
}
);
const trueSelectedRows: any[] = [];
rowKeys.forEach((key: string) => {
const found = allSelectedRows.find((item) => getRecordKey(item) === key);
found && trueSelectedRows.push(found);
});
// update-begin--author:liaozhiyang---date:20231103---for【issues/828】解决卡死问题
if (!(isSomeRowKeys && equal(selectedRows.value, trueSelectedRows))) {
selectedRows.value = trueSelectedRows;
emitChange();
}
// update-end--author:liaozhiyang---date:20231103---for【issues/828】解决卡死问题
}
/**
*2023-11-03
*廖志阳
*检测selectedRows.value和trueSelectedRows是否相等防止死循环
*/
function equal(oldVal, newVal) {
let oldKeys = [],
newKeys = [];
if (oldVal.length === newVal.length) {
oldKeys = oldVal.map((item) => getRecordKey(item));
newKeys = newVal.map((item) => getRecordKey(item));
for (let i = 0, len = oldKeys.length; i < len; i++) {
const findItem = newKeys.find((item) => item === oldKeys[i]);
if (!findItem) {
return false;
}
}
return true;
}
return false;
}
/**
*2023-11-22
*廖志阳
*根据全选或者反选返回源数据中这次需要变更的数据
*/
function getInvertRows(rows: any): any {
const allRows = findNodeAll(toRaw(unref(flattedData)), () => true, {
children: propsRef.value.childrenColumnName ?? 'children',
});
if (rows.length === 0 || rows.length === allRows.length) {
return allRows;
} else {
const allRowsKey = allRows.map((item) => getRecordKey(item));
rows.forEach((rItem) => {
const rItemKey = getRecordKey(rItem);
const findIndex = allRowsKey.findIndex((item) => rItemKey === item);
if (findIndex != -1) {
allRowsKey.splice(findIndex, 1);
allRows.splice(findIndex, 1);
}
});
}
return allRows;
}
function getSelectRows<T = Recordable>() {
return unref(selectedRows) as T[];
}
function getSelectRowKeys() {
return unref(selectedKeys);
}
function getRowSelection() {
return unref(getRowSelectionRef)!;
}
function deleteSelectRowByKey(key: string) {
const index = selectedKeys.value.findIndex((item) => item === key);
if (index !== -1) {
selectedKeys.value.splice(index, 1);
selectedRows.value.splice(index, 1);
}
}
// 【QQYUN-5837】动态计算 expandIconColumnIndex
const getExpandIconColumnIndex = computed(() => {
const { expandIconColumnIndex } = unref(propsRef);
// 未设置选择列,则保持不变
if (getRowSelectionRef.value == null) {
return expandIconColumnIndex;
}
// 设置了选择列,并且未传入 index 参数,则返回 1
if (expandIconColumnIndex == null) {
return 1;
}
return expandIconColumnIndex;
});
return {
getRowSelection,
getRowSelectionRef,
getSelectRows,
getSelectRowKeys,
setSelectedRowKeys,
deleteSelectRowByKey,
selectHeaderProps,
isCustomSelection,
handleCustomSelectColumn,
clearSelectedRowKeys,
getExpandIconColumnIndex,
};
}
function getTableBody(wrap: HTMLDivElement) {
return new Promise<HTMLDivElement>((resolve) => {
(function fn() {
const bodyEl = wrap.querySelector('.ant-table-wrapper .ant-table-body') as HTMLDivElement;
if (bodyEl) {
resolve(bodyEl);
} else {
setTimeout(fn, 100);
}
})();
});
}
function flattenData<RecordType>(data: RecordType[] | undefined, childrenColumnName: string): RecordType[] {
let list: RecordType[] = [];
(data || []).forEach((record) => {
list.push(record);
if (record && typeof record === 'object' && childrenColumnName in record) {
list = [...list, ...flattenData<RecordType>((record as any)[childrenColumnName], childrenColumnName)];
}
});
return list;
}

View File

@@ -1,349 +0,0 @@
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import { ref, unref, ComputedRef, computed, onMounted, watch, reactive, Ref, watchEffect } from 'vue';
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { buildUUID } from '/@/utils/uuid';
import { isFunction, isBoolean } from '/@/utils/is';
import { get, cloneDeep } from 'lodash-es';
import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const';
interface ActionType {
getPaginationInfo: ComputedRef<boolean | PaginationProps>;
setPagination: (info: Partial<PaginationProps>) => void;
setLoading: (loading: boolean) => void;
// update-begin--author:sunjianlei---date:220220419---for由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate
validate: () => Recordable;
// update-end--author:sunjianlei---date:220220419---for由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate
clearSelectedRowKeys: () => void;
tableData: Ref<Recordable[]>;
}
interface SearchState {
sortInfo: Recordable;
filterInfo: Record<string, string[]>;
}
export function useDataSource(
propsRef: ComputedRef<BasicTableProps>,
{ getPaginationInfo, setPagination, setLoading, validate, clearSelectedRowKeys, tableData }: ActionType,
emit: EmitType
) {
const searchState = reactive<SearchState>({
sortInfo: {},
filterInfo: {},
});
const dataSourceRef = ref<Recordable[]>([]);
const rawDataSourceRef = ref<Recordable>({});
watchEffect(() => {
tableData.value = unref(dataSourceRef);
});
watch(
() => unref(propsRef).dataSource,
() => {
const { dataSource, api } = unref(propsRef);
!api && dataSource && (dataSourceRef.value = dataSource);
},
{
immediate: true,
}
);
function handleTableChange(pagination: PaginationProps, filters: Partial<Recordable<string[]>>, sorter: SorterResult) {
const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef);
if (clearSelectOnPageChange) {
clearSelectedRowKeys();
}
setPagination(pagination);
const params: Recordable = {};
if (sorter && isFunction(sortFn)) {
const sortInfo = sortFn(sorter);
searchState.sortInfo = sortInfo;
params.sortInfo = sortInfo;
}
if (filters && isFunction(filterFn)) {
const filterInfo = filterFn(filters);
searchState.filterInfo = filterInfo;
params.filterInfo = filterInfo;
}
fetch(params);
}
function setTableKey(items: any[]) {
if (!items || !Array.isArray(items)) return;
items.forEach((item) => {
if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID();
}
if (item.children && item.children.length) {
setTableKey(item.children);
}
});
}
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
});
const getRowKey = computed(() => {
const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) {
return unref(dataSourceRef);
}
if (unref(getAutoCreateKey)) {
const firstItem = dataSource[0];
const lastItem = dataSource[dataSource.length - 1];
if (firstItem && lastItem) {
if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
const data = cloneDeep(unref(dataSourceRef));
data.forEach((item) => {
if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID();
}
if (item.children && item.children.length) {
setTableKey(item.children);
}
});
dataSourceRef.value = data;
}
}
}
return unref(dataSourceRef);
});
async function updateTableData(index: number, key: string, value: any) {
const record = dataSourceRef.value[index];
if (record) {
dataSourceRef.value[index][key] = value;
}
return dataSourceRef.value[index];
}
function updateTableDataRecord(rowKey: string | number, record: Recordable): Recordable | undefined {
const row = findTableDataRecord(rowKey);
if (row) {
for (const field in row) {
if (Reflect.has(record, field)) row[field] = record[field];
//update-begin---author:wangshuai---date:2024-06-11---for:【TV360X-437】树表 部分组件编辑完后,列表未刷新---
if (Reflect.has(record, field + '_dictText')) {
row[field + '_dictText'] = record[field + '_dictText'];
}
//update-end---author:wangshuai---date:2024-06-11---for:【TV360X-437】树表 部分组件编辑完后,列表未刷新---
}
return row;
}
}
function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const rowKeyName = unref(getRowKey);
if (!rowKeyName) return;
const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
for (const key of rowKeys) {
let index: number | undefined = dataSourceRef.value.findIndex((row) => {
let targetKeyName: string;
if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row);
} else {
targetKeyName = rowKeyName as string;
}
return row[targetKeyName] === key;
});
if (index >= 0) {
dataSourceRef.value.splice(index, 1);
}
index = unref(propsRef).dataSource?.findIndex((row) => {
let targetKeyName: string;
if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row);
} else {
targetKeyName = rowKeyName as string;
}
return row[targetKeyName] === key;
});
if (typeof index !== 'undefined' && index !== -1) unref(propsRef).dataSource?.splice(index, 1);
}
setPagination({
total: unref(propsRef).dataSource?.length,
});
}
function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined {
// if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
index = index ?? dataSourceRef.value?.length;
unref(dataSourceRef).splice(index, 0, record);
return unref(dataSourceRef);
}
function findTableDataRecord(rowKey: string | number) {
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;
}
}
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);
}
async function fetch(opt?: FetchParams) {
const { api, searchInfo, defSort, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = unref(propsRef);
if (!api || !isFunction(api)) return;
try {
setLoading(true);
const { pageField, sizeField, listField, totalField } = Object.assign({}, FETCH_SETTING, fetchSetting);
let pageParams: Recordable = {};
const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps;
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
pageParams = {};
} else {
pageParams[pageField] = (opt && opt.page) || current;
pageParams[sizeField] = pageSize;
}
const { sortInfo = {}, filterInfo } = searchState;
let params: Recordable = {
...pageParams,
// 由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate
...(useSearchForm ? await validate() : {}),
...searchInfo,
...defSort,
...(opt?.searchInfo ?? {}),
...sortInfo,
...filterInfo,
...(opt?.sortInfo ?? {}),
...(opt?.filterInfo ?? {}),
};
if (beforeFetch && isFunction(beforeFetch)) {
params = (await beforeFetch(params)) || params;
}
// update-begin--author:liaozhiyang---date:20240227---for【QQYUN-8316】table查询条件,请求剔除空字符串字段
for (let item of Object.entries(params)) {
const [key, val] = item;
if (val === '') {
delete params[key];
};
};
// update-end--author:liaozhiyang---date:20240227---for【QQYUN-8316】table查询条件,请求剔除空字符串字段
const res = await api(params);
rawDataSourceRef.value = res;
const isArrayResult = Array.isArray(res);
let resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
const resultTotal: number = isArrayResult ? 0 : get(res, totalField);
// 假如数据变少导致总页数变少并小于当前选中页码通过getPaginationRef获取到的页码是不正确的需获取正确的页码再次执行
if (resultTotal) {
const currentTotalPage = Math.ceil(Number(resultTotal) / pageSize);
if (current > currentTotalPage) {
setPagination({
current: currentTotalPage,
});
return await fetch(opt);
}
}
if (afterFetch && isFunction(afterFetch)) {
resultItems = (await afterFetch(resultItems)) || resultItems;
}
dataSourceRef.value = resultItems;
setPagination({
total: Number(resultTotal) || 0,
});
if (opt && opt.page) {
setPagination({
current: opt.page || 1,
});
}
emit('fetch-success', {
items: unref(resultItems),
total: Number(resultTotal),
});
return resultItems;
} catch (error) {
emit('fetch-error', error);
dataSourceRef.value = [];
setPagination({
total: 0,
});
} finally {
setLoading(false);
}
}
function setTableData<T = Recordable>(values: T[]) {
dataSourceRef.value = values;
}
function getDataSource<T = Recordable>() {
return getDataSourceRef.value as T[];
}
function getRawDataSource<T = Recordable>() {
return rawDataSourceRef.value as T;
}
async function reload(opt?: FetchParams) {
return await fetch(opt);
}
onMounted(() => {
useTimeoutFn(() => {
unref(propsRef).immediate && fetch();
}, 16);
});
return {
getDataSourceRef,
getDataSource,
getRawDataSource,
getRowKey,
setTableData,
getAutoCreateKey,
fetch,
reload,
updateTableData,
updateTableDataRecord,
deleteTableDataRecord,
insertTableDataRecord,
findTableDataRecord,
handleTableChange,
};
}

View File

@@ -1,21 +0,0 @@
import { ref, ComputedRef, unref, computed, watch } from 'vue';
import type { BasicTableProps } from '../types/table';
export function useLoading(props: ComputedRef<BasicTableProps>) {
const loadingRef = ref(unref(props).loading);
watch(
() => unref(props).loading,
(loading) => {
loadingRef.value = loading;
}
);
const getLoading = computed(() => unref(loadingRef));
function setLoading(loading: boolean) {
loadingRef.value = loading;
}
return { getLoading, setLoading };
}

View File

@@ -1,85 +0,0 @@
import type { PaginationProps } from '../types/pagination';
import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, ComputedRef, watch } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import { isBoolean } from '/@/utils/is';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
import { useI18n } from '/@/hooks/web/useI18n';
interface ItemRender {
page: number;
type: 'page' | 'prev' | 'next';
originalElement: any;
}
function itemRender({ page, type, originalElement }: ItemRender) {
if (type === 'prev') {
return page === 0 ? null : <LeftOutlined />;
} else if (type === 'next') {
return page === 1 ? null : <RightOutlined />;
}
return originalElement;
}
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const { t } = useI18n();
const configRef = ref<PaginationProps>({});
const show = ref(true);
watch(
() => unref(refProps).pagination,
(pagination) => {
if (!isBoolean(pagination) && pagination) {
configRef.value = {
...unref(configRef),
...(pagination ?? {}),
};
}
}
);
const getPaginationInfo = computed((): PaginationProps | boolean => {
const { pagination } = unref(refProps);
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
return false;
}
return {
current: 1,
pageSize: PAGE_SIZE,
size: 'small',
defaultPageSize: PAGE_SIZE,
showTotal: (total) => t('component.table.total', { total }),
showSizeChanger: true,
pageSizeOptions: PAGE_SIZE_OPTIONS,
itemRender: itemRender,
showQuickJumper: true,
...(isBoolean(pagination) ? {} : pagination),
...unref(configRef),
};
});
function setPagination(info: Partial<PaginationProps>) {
const paginationInfo = unref(getPaginationInfo);
configRef.value = {
...(!isBoolean(paginationInfo) ? paginationInfo : {}),
...info,
};
}
function getPagination() {
return unref(getPaginationInfo);
}
function getShowPagination() {
return unref(show);
}
async function setShowPagination(flag: boolean) {
show.value = flag;
}
return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination };
}

View File

@@ -1,127 +0,0 @@
import { isFunction } from '/@/utils/is';
import type { BasicTableProps, TableRowSelection } from '../types/table';
import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from 'vue';
import { ROW_KEY } from '../const';
import { omit } from 'lodash-es';
import { findNodeAll } from '/@/utils/helper/treeHelper';
export function useRowSelection(propsRef: ComputedRef<BasicTableProps>, tableData: Ref<Recordable[]>, emit: EmitType) {
const selectedRowKeysRef = ref<string[]>([]);
const selectedRowRef = ref<Recordable[]>([]);
const getRowSelectionRef = computed((): TableRowSelection | null => {
const { rowSelection } = unref(propsRef);
if (!rowSelection) {
return null;
}
return {
// AntDV3.0 之后使用远程加载数据进行分页时,
// 默认会清空上一页选择的行数据(导致无法跨页选择),
// 将此属性设置为 true 即可解决。
preserveSelectedRowKeys: true,
selectedRowKeys: unref(selectedRowKeysRef),
onChange: (selectedRowKeys: string[]) => {
setSelectedRowKeys(selectedRowKeys);
},
...omit(rowSelection, ['onChange']),
};
});
watch(
() => unref(propsRef).rowSelection?.selectedRowKeys,
(v: string[]) => {
setSelectedRowKeys(v);
}
);
watch(
() => unref(selectedRowKeysRef),
() => {
nextTick(() => {
const { rowSelection } = unref(propsRef);
if (rowSelection) {
const { onChange } = rowSelection;
if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows());
}
//update-begin---author:scott ---date:2023-06-19 for【issues/503】table行选择时卡顿明显 #503---
//table行选择时卡顿明显 #503
if (unref(tableData).length > 0) {
emit('selection-change', {
keys: getSelectRowKeys(),
rows: getSelectRows(),
});
}
//update-end---author:scott ---date::2023-06-19 for【issues/503】table行选择时卡顿明显 #503---
});
},
{ deep: true }
);
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
});
const getRowKey = computed(() => {
const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
function setSelectedRowKeys(rowKeys: string[]) {
selectedRowKeysRef.value = rowKeys;
const allSelectedRows = findNodeAll(
toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))),
(item) => rowKeys.includes(item[unref(getRowKey) as string]),
{
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);
});
selectedRowRef.value = trueSelectedRows;
}
function setSelectedRows(rows: Recordable[]) {
selectedRowRef.value = rows;
}
function clearSelectedRowKeys() {
selectedRowRef.value = [];
selectedRowKeysRef.value = [];
}
function deleteSelectRowByKey(key: string) {
const selectedRowKeys = unref(selectedRowKeysRef);
const index = selectedRowKeys.findIndex((item) => item === key);
if (index !== -1) {
unref(selectedRowKeysRef).splice(index, 1);
}
}
function getSelectRowKeys() {
return unref(selectedRowKeysRef);
}
function getSelectRows<T = Recordable>() {
// const ret = toRaw(unref(selectedRowRef)).map((item) => toRaw(item));
return unref(selectedRowRef) as T[];
}
function getRowSelection() {
return unref(getRowSelectionRef)!;
}
return {
getRowSelection,
getRowSelectionRef,
getSelectRows,
getSelectRowKeys,
setSelectedRowKeys,
clearSelectedRowKeys,
deleteSelectRowByKey,
setSelectedRows,
};
}

View File

@@ -1,168 +0,0 @@
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import type { DynamicProps } from '/#/utils';
import type { FormActionType } from '/@/components/Form';
import type { WatchStopHandle } from 'vue';
import { getDynamicProps } from '/@/utils';
import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
type Props = Partial<DynamicProps<BasicTableProps>>;
type UseTableMethod = TableActionType & {
getForm: () => FormActionType;
};
export function useTable(tableProps?: Props): [
(instance: TableActionType, formInstance: UseTableMethod) => void,
TableActionType & {
getForm: () => FormActionType;
}
] {
const tableRef = ref<Nullable<TableActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false);
const formRef = ref<Nullable<UseTableMethod>>(null);
let stopWatch: WatchStopHandle;
function register(instance: TableActionType, formInstance: UseTableMethod) {
isProdMode() &&
onUnmounted(() => {
tableRef.value = null;
loadedRef.value = null;
});
if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return;
tableRef.value = instance;
formRef.value = formInstance;
tableProps && instance.setProps(getDynamicProps(tableProps));
loadedRef.value = true;
stopWatch?.();
stopWatch = watch(
() => tableProps,
() => {
tableProps && instance.setProps(getDynamicProps(tableProps));
},
{
immediate: true,
deep: true,
}
);
}
function getTableInstance(): TableActionType {
const table = unref(tableRef);
if (!table) {
error('The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!');
}
return table as TableActionType;
}
function getTableRef(){
return tableRef;
}
const methods: TableActionType & {
getForm: () => FormActionType;
} & {
getTableRef: () => any;
} = {
reload: async (opt?: FetchParams) => {
return await getTableInstance().reload(opt);
},
setProps: (props: Partial<BasicTableProps>) => {
getTableInstance().setProps(props);
},
redoHeight: () => {
getTableInstance().redoHeight();
},
setLoading: (loading: boolean) => {
getTableInstance().setLoading(loading);
},
getDataSource: () => {
return getTableInstance().getDataSource();
},
getRawDataSource: () => {
return getTableInstance().getRawDataSource();
},
getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
return toRaw(columns);
},
setColumns: (columns: BasicColumn[]) => {
getTableInstance().setColumns(columns);
},
setTableData: (values: any[]) => {
return getTableInstance().setTableData(values);
},
setPagination: (info: Partial<PaginationProps>) => {
return getTableInstance().setPagination(info);
},
deleteSelectRowByKey: (key: string) => {
getTableInstance().deleteSelectRowByKey(key);
},
getSelectRowKeys: () => {
return toRaw(getTableInstance().getSelectRowKeys());
},
getSelectRows: () => {
return toRaw(getTableInstance().getSelectRows());
},
clearSelectedRowKeys: () => {
getTableInstance().clearSelectedRowKeys();
},
setSelectedRowKeys: (keys: string[] | number[]) => {
getTableInstance().setSelectedRowKeys(keys);
},
getPaginationRef: () => {
return getTableInstance().getPaginationRef();
},
getSize: () => {
return toRaw(getTableInstance().getSize());
},
updateTableData: (index: number, key: string, value: any) => {
return getTableInstance().updateTableData(index, key, value);
},
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
return getTableInstance().deleteTableDataRecord(rowKey);
},
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
return getTableInstance().insertTableDataRecord(record, index);
},
updateTableDataRecord: (rowKey: string | number, record: Recordable) => {
return getTableInstance().updateTableDataRecord(rowKey, record);
},
findTableDataRecord: (rowKey: string | number) => {
return getTableInstance().findTableDataRecord(rowKey);
},
getRowSelection: () => {
return toRaw(getTableInstance().getRowSelection());
},
getCacheColumns: () => {
return toRaw(getTableInstance().getCacheColumns());
},
getForm: () => {
return unref(formRef) as unknown as FormActionType;
},
setShowPagination: async (show: boolean) => {
getTableInstance().setShowPagination(show);
},
getShowPagination: () => {
return toRaw(getTableInstance().getShowPagination());
},
expandAll: () => {
getTableInstance().expandAll();
},
collapseAll: () => {
getTableInstance().collapseAll();
},
getTableRef: () => {
return getTableRef();
}
};
return [register, methods];
}

View File

@@ -1,22 +0,0 @@
import type { Ref } from 'vue';
import type { BasicTableProps, TableActionType } from '../types/table';
import { provide, inject, ComputedRef } from 'vue';
const key = Symbol('basic-table');
type Instance = TableActionType & {
wrapRef: Ref<Nullable<HTMLElement>>;
getBindValues: ComputedRef<Recordable>;
};
type RetInstance = Omit<Instance, 'getBindValues'> & {
getBindValues: ComputedRef<BasicTableProps>;
};
export function createTableContext(instance: Instance) {
provide(key, instance);
}
export function useTableContext(): RetInstance {
return inject(key) as RetInstance;
}

View File

@@ -1,54 +0,0 @@
import type { ComputedRef, Ref } from 'vue';
import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, toRaw } from 'vue';
import { ROW_KEY } from '../const';
export function useTableExpand(propsRef: ComputedRef<BasicTableProps>, tableData: Ref<Recordable[]>, emit: EmitType) {
const expandedRowKeys = ref<string[]>([]);
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
});
const getRowKey = computed(() => {
const { rowKey } = unref(propsRef);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
const getExpandOption = computed(() => {
const { isTreeTable } = unref(propsRef);
if (!isTreeTable) return {};
return {
expandedRowKeys: unref(expandedRowKeys),
onExpandedRowsChange: (keys: string[]) => {
expandedRowKeys.value = keys;
emit('expanded-rows-change', keys);
},
};
});
function expandAll() {
const keys = getAllKeys();
expandedRowKeys.value = keys;
}
function getAllKeys(data?: Recordable[]) {
const keys: string[] = [];
const { childrenColumnName } = unref(propsRef);
toRaw(data || unref(tableData)).forEach((item) => {
keys.push(item[unref(getRowKey) as string]);
const children = item[childrenColumnName || 'children'];
if (children?.length) {
keys.push(...getAllKeys(children));
}
});
return keys;
}
function collapseAll() {
expandedRowKeys.value = [];
}
return { getExpandOption, expandAll, collapseAll };
}

View File

@@ -1,62 +0,0 @@
import type { ComputedRef, Ref, Slots } from 'vue';
import type { BasicTableProps } from '../types/table';
import { unref, computed, h, nextTick, watchEffect } from 'vue';
import TableFooter from '../components/TableFooter.vue';
import { useEventListener } from '/@/hooks/event/useEventListener';
export function useTableFooter(
propsRef: ComputedRef<BasicTableProps>,
slots: Slots,
scrollRef: ComputedRef<{
x: string | number | true;
y: Nullable<number>;
scrollToFirstRowOnChange: boolean;
}>,
tableElRef: Ref<ComponentRef>,
getDataSourceRef: ComputedRef<Recordable>
) {
const getIsEmptyData = computed(() => {
return (unref(getDataSourceRef) || []).length === 0;
});
// 是否有展开行
const hasExpandedRow = computed(() => Object.keys(slots).includes('expandedRowRender'))
const getFooterProps = computed((): Recordable | undefined => {
const { summaryFunc, showSummary, summaryData, bordered } = unref(propsRef);
return showSummary && !unref(getIsEmptyData) ? () => h(TableFooter, {
bordered,
summaryFunc,
summaryData,
scroll: unref(scrollRef),
hasExpandedRow: hasExpandedRow.value
}) : undefined;
});
watchEffect(() => {
handleSummary();
});
function handleSummary() {
const { showSummary } = unref(propsRef);
if (!showSummary || unref(getIsEmptyData)) return;
nextTick(() => {
const tableEl = unref(tableElRef);
if (!tableEl) return;
const bodyDom = tableEl.$el.querySelector('.ant-table-content');
useEventListener({
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector('.ant-table-footer .ant-table-content') as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
},
wait: 0,
options: true,
});
});
}
return { getFooterProps };
}

View File

@@ -1,51 +0,0 @@
import type { ComputedRef, Slots } from 'vue';
import type { BasicTableProps, FetchParams } from '../types/table';
import { unref, computed } from 'vue';
import type { FormProps } from '/@/components/Form';
import { isFunction } from '/@/utils/is';
export function useTableForm(
propsRef: ComputedRef<BasicTableProps>,
slots: Slots,
fetch: (opt?: FetchParams | undefined) => Promise<void>,
getLoading: ComputedRef<boolean | undefined>
) {
const getFormProps = computed((): Partial<FormProps> => {
const { formConfig } = unref(propsRef);
const { submitButtonOptions, autoSubmitOnEnter} = formConfig || {};
return {
showAdvancedButton: true,
...formConfig,
submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions },
compact: true,
//update-begin-author:liusq---date:20230605--for: [issues/568]设置 autoSubmitOnEnter: false 不生效 ---
autoSubmitOnEnter: autoSubmitOnEnter,
//update-end-author:liusq---date:20230605--for: [issues/568]设置 autoSubmitOnEnter: false 不生效 ---
};
});
const getFormSlotKeys: ComputedRef<string[]> = computed(() => {
const keys = Object.keys(slots);
return keys.map((item) => (item.startsWith('form-') ? item : null)).filter((item) => !!item) as string[];
});
function replaceFormSlotKey(key: string) {
if (!key) return '';
return key?.replace?.(/form\-/, '') ?? '';
}
function handleSearchInfoChange(info: Recordable) {
const { handleSearchInfoFn } = unref(propsRef);
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
info = handleSearchInfoFn(info) || info;
}
fetch({ searchInfo: info, page: 1 });
}
return {
getFormProps,
replaceFormSlotKey,
getFormSlotKeys,
handleSearchInfoChange,
};
}

View File

@@ -1,58 +0,0 @@
import type { ComputedRef, Slots } from 'vue';
import type { BasicTableProps, InnerHandlers } from '../types/table';
import { unref, computed, h } from 'vue';
import TableHeader from '../components/TableHeader.vue';
import { isString } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper';
export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots, handlers: InnerHandlers) {
const getHeaderProps = computed((): Recordable => {
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
if (hideTitle && !isString(title)) {
return {};
}
return {
title: hideTitle
? null
: () =>
h(
TableHeader,
{
title,
titleHelpMessage,
showTableSetting,
tableSetting,
onColumnsChange: handlers.onColumnsChange,
} as Recordable,
{
...(slots.toolbar
? {
toolbar: () => getSlot(slots, 'toolbar'),
}
: {}),
...(slots.tableTitle
? {
tableTitle: () => getSlot(slots, 'tableTitle'),
}
: {}),
...(slots.headerTop
? {
headerTop: () => getSlot(slots, 'headerTop'),
}
: {}),
//添加tableTop插槽
...(slots.tableTop
? {
tableTop: () => getSlot(slots, 'tableTop'),
}
: {}),
// 添加alertAfter插槽
...(slots.alertAfter ? { alertAfter: () => getSlot(slots, 'alertAfter') } : {}),
}
),
};
});
return { getHeaderProps };
}

View File

@@ -1,199 +0,0 @@
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table';
import type { Ref, ComputedRef } from 'vue';
import { computed, unref, ref, 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 componentSetting from '/@/settings/componentSetting';
export function useTableScroll(
propsRef: ComputedRef<BasicTableProps>,
tableElRef: Ref<ComponentRef>,
columnsRef: ComputedRef<BasicColumn[]>,
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
getDataSourceRef: ComputedRef<Recordable[]>
) {
const tableHeightRef: Ref<Nullable<number>> = ref(null);
const modalFn = useModalContext();
// Greater than animation time 280
const debounceRedoHeight = useDebounceFn(redoHeight, 100);
const getCanResize = computed(() => {
const { canResize, scroll } = unref(propsRef);
return canResize && !(scroll || {}).y;
});
watch(
() => [unref(getCanResize), unref(getDataSourceRef)?.length],
() => {
debounceRedoHeight();
},
{
flush: 'post',
}
);
function redoHeight() {
nextTick(() => {
calcTableHeight();
});
}
function setHeight(heigh: number) {
tableHeightRef.value = heigh;
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
modalFn?.redoModalHeight?.();
}
// No need to repeat queries
let paginationEl: HTMLElement | null;
let footerEl: HTMLElement | null;
let bodyEl: HTMLElement | null;
async function calcTableHeight() {
const { resizeHeightOffset, pagination, maxHeight, minHeight } = unref(propsRef);
const tableData = unref(getDataSourceRef);
const table = unref(tableElRef);
if (!table) return;
const tableEl: Element = table.$el;
if (!tableEl) return;
if (!bodyEl) {
//update-begin-author:taoyan date:2023-2-11 for: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情
bodyEl = tableEl.querySelector('.ant-table-tbody');
//update-end-author:taoyan date:2023-2-11 for: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情
if (!bodyEl) return;
}
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
if (hasScrollBarY) {
tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.remove('hide-scrollbar-y');
} else {
!tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.add('hide-scrollbar-y');
}
if (hasScrollBarX) {
tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.remove('hide-scrollbar-x');
} else {
!tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x');
}
bodyEl!.style.height = 'unset';
if (!unref(getCanResize) || ( !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;
// Table height from bottom
const { bottomIncludeBody } = getViewportOffset(headEl);
// Table height from bottom height-custom offset
const paddingHeight = 32;
// Pager height
let paginationHeight = 2;
if (!isBoolean(pagination)) {
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
if (paginationEl) {
const offsetHeight = paginationEl.offsetHeight;
paginationHeight += offsetHeight || 0;
} else {
// TODO First fix 24
paginationHeight += 24;
}
} else {
paginationHeight = -8;
}
let footerHeight = 0;
// update-begin--author:liaozhiyang---date:20240424---for【issues/1137】BasicTable自适应高度计算没有减去尾部高度
footerEl = tableEl.querySelector('.ant-table-footer');
if (footerEl) {
const offsetHeight = footerEl.offsetHeight;
footerHeight = offsetHeight || 0;
}
// update-end--author:liaozhiyang---date:20240424---for【issues/1137】BasicTable自适应高度计算没有减去尾部高度
let headerHeight = 0;
if (headEl) {
headerHeight = (headEl as HTMLElement).offsetHeight;
}
let height = bottomIncludeBody - (resizeHeightOffset || 0) - paddingHeight - paginationHeight - footerHeight - headerHeight;
// update-begin--author:liaozhiyang---date:20240603---for【TV360X-861】列表查询区域不可往上滚动
// 10+6(外层边距padding:10 + 内层padding-bottom:6)
height -= 16;
// update-end--author:liaozhiyang---date:20240603---for【TV360X-861】列表查询区域不可往上滚动
height = (height < minHeight! ? (minHeight as number) : height) ?? height;
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
setHeight(height);
bodyEl!.style.height = `${height}px`;
}
useWindowSizeFn(calcTableHeight, 280);
onMountedOrActivated(() => {
calcTableHeight();
nextTick(() => {
debounceRedoHeight();
});
});
const getScrollX = computed(() => {
let width = 0;
// update-begin--author:liaozhiyang---date:20230922---for【QQYUN-6391】在线表单列表字段过多时,列头和数据对不齐
// if (unref(rowSelectionRef)) {
// width += 60;
// }
// update-end--author:liaozhiyang---date:20230922---for【QQYUN-6391】在线表单列表字段过多时,列头和数据对不齐
// update-begin--author:liaozhiyang---date:20230925---for【issues/5411】BasicTable 配置maxColumnWidth 未生效
const { maxColumnWidth } = unref(propsRef);
// TODO props ?? 0;
const NORMAL_WIDTH = maxColumnWidth ?? 150;
// update-end--author:liaozhiyang---date:20230925---for【issues/5411】BasicTable 配置maxColumnWidth 未生效
const columns = unref(columnsRef).filter((item) => !item.defaultHidden);
columns.forEach((item) => {
width += Number.parseInt(item.width as string) || 0;
});
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
const len = unsetWidthColumns.length;
if (len !== 0) {
width += len * NORMAL_WIDTH;
}
const table = unref(tableElRef);
const tableWidth = table?.$el?.offsetWidth ?? 0;
return tableWidth > width ? '100%' : width;
});
const getScrollRef = computed(() => {
const tableHeight = unref(tableHeightRef);
const { canResize, scroll } = unref(propsRef);
const { table } = componentSetting;
return {
x: unref(getScrollX),
y: canResize ? tableHeight : null,
// update-begin--author:liaozhiyang---date:20240424---for【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
scrollToFirstRowOnChange: table.scrollToFirstRowOnChange,
// update-end--author:liaozhiyang---date:20240424---for【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
...scroll,
};
});
return { getScrollRef, redoHeight };
}

View File

@@ -1,20 +0,0 @@
import type { ComputedRef } from 'vue';
import type { BasicTableProps, TableCustomRecord } from '../types/table';
import { unref } from 'vue';
import { isFunction } from '/@/utils/is';
export function useTableStyle(propsRef: ComputedRef<BasicTableProps>, prefixCls: string) {
function getRowClassName(record: TableCustomRecord, index: number) {
const { striped, rowClassName } = unref(propsRef);
const classNames: string[] = [];
if (striped) {
classNames.push((index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : '');
}
if (rowClassName && isFunction(rowClassName)) {
classNames.push(rowClassName(record, index));
}
return classNames.filter((cls) => !!cls).join(' ');
}
return { getRowClassName };
}

View File

@@ -1,147 +0,0 @@
import type { PropType } from 'vue';
import type { PaginationProps } from './types/pagination';
import type { BasicColumn, FetchSetting, TableSetting, SorterResult, TableCustomRecord, TableRowSelection, SizeType } from './types/table';
import type { FormProps } from '/@/components/Form';
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const';
import { propTypes } from '/@/utils/propTypes';
export const basicProps = {
clickToRowSelect: propTypes.bool.def(true),
isTreeTable: propTypes.bool.def(false),
tableSetting: propTypes.shape<TableSetting>({}),
inset: propTypes.bool,
sortFn: {
type: Function as PropType<(sortInfo: SorterResult) => any>,
default: DEFAULT_SORT_FN,
},
filterFn: {
type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
default: DEFAULT_FILTER_FN,
},
showTableSetting: propTypes.bool,
autoCreateKey: propTypes.bool.def(true),
striped: propTypes.bool.def(false),
showSummary: propTypes.bool,
summaryFunc: {
type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
default: null,
},
summaryData: {
type: Array as PropType<Recordable[]>,
default: null,
},
indentSize: propTypes.number.def(24),
canColDrag: propTypes.bool.def(true),
api: {
type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null,
},
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
handleSearchInfoFn: {
type: Function as PropType<Fn>,
default: null,
},
fetchSetting: {
type: Object as PropType<FetchSetting>,
default: () => {
return FETCH_SETTING;
},
},
// 立即请求接口
immediate: propTypes.bool.def(true),
emptyDataIsShowTable: propTypes.bool.def(true),
// 额外的请求参数
searchInfo: {
type: Object as PropType<Recordable>,
default: null,
},
// 默认的排序参数
defSort: {
type: Object as PropType<Recordable>,
default: null,
},
// 使用搜索表单
useSearchForm: propTypes.bool,
// 表单配置
formConfig: {
type: Object as PropType<Partial<FormProps>>,
default: null,
},
columns: {
type: [Array] as PropType<BasicColumn[]>,
default: () => [],
},
showIndexColumn: propTypes.bool.def(true),
indexColumnProps: {
type: Object as PropType<BasicColumn>,
default: null,
},
showActionColumn: {
type: Boolean,
default: true,
},
actionColumn: {
type: Object as PropType<BasicColumn>,
default: null,
},
ellipsis: propTypes.bool.def(true),
canResize: propTypes.bool.def(true),
clearSelectOnPageChange: propTypes.bool,
resizeHeightOffset: propTypes.number.def(0),
rowSelection: {
type: Object as PropType<TableRowSelection | null>,
default: null,
},
title: {
type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
default: null,
},
titleHelpMessage: {
type: [String, Array] as PropType<string | string[]>,
},
minHeight: propTypes.number,
maxHeight: propTypes.number,
// 统一设置列最大宽度
maxColumnWidth: propTypes.number,
dataSource: {
type: Array as PropType<Recordable[]>,
default: null,
},
rowKey: {
type: [String, Function] as PropType<string | ((record: Recordable) => string)>,
default: '',
},
bordered: propTypes.bool,
pagination: {
type: [Object, Boolean] as PropType<PaginationProps | boolean>,
default: null,
},
loading: propTypes.bool,
rowClassName: {
type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>,
},
scroll: {
// update-begin--author:liaozhiyang---date:20240424---for【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
type: Object as PropType<{ x?: number | true; y?: number; scrollToFirstRowOnChange?: boolean }>,
// update-end--author:liaozhiyang---date:20240424---for【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
default: null,
},
beforeEditSubmit: {
type: Function as PropType<(data: { record: Recordable; index: number; key: string | number; value: any }) => Promise<any>>,
},
size: {
type: String as PropType<SizeType>,
default: DEFAULT_SIZE,
},
expandedRowKeys: {
type: Array,
default: null,
},
};

View File

@@ -1,198 +0,0 @@
import { VNodeChild } from 'vue';
export interface ColumnFilterItem {
text?: string;
value?: string;
children?: any;
}
export declare type SortOrder = 'ascend' | 'descend';
export interface RecordProps<T> {
text: any;
record: T;
index: number;
}
export interface FilterDropdownProps {
prefixCls?: string;
setSelectedKeys?: (selectedKeys: string[]) => void;
selectedKeys?: string[];
confirm?: () => void;
clearFilters?: () => void;
filters?: ColumnFilterItem[];
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
visible?: boolean;
}
export declare type CustomRenderFunction<T> = (record: RecordProps<T>) => VNodeChild | JSX.Element;
export interface ColumnProps<T> {
/**
* specify how content is aligned
* @default 'left'
* @type string
*/
align?: 'left' | 'right' | 'center';
/**
* ellipsize cell content, not working with sorter and filters for now.
* tableLayout would be fixed when ellipsis is true.
* @default false
* @type boolean
*/
ellipsis?: boolean;
/**
* Span of this column's title
* @type number
*/
colSpan?: number;
/**
* Display field of the data record, could be set like a.b.c
* @type string
*/
dataIndex?: string;
/**
* Default filtered values
* @type string[]
*/
defaultFilteredValue?: string[];
/**
* Default order of sorted values: 'ascend' 'descend' null
* @type string
*/
defaultSortOrder?: SortOrder;
/**
* Customized filter overlay
* @type any (slot)
*/
filterDropdown?: VNodeChild | JSX.Element | ((props: FilterDropdownProps) => VNodeChild | JSX.Element);
/**
* Whether filterDropdown is visible
* @type boolean
*/
filterDropdownOpen?: boolean;
/**
* Whether the dataSource is filtered
* @default false
* @type boolean
*/
filtered?: boolean;
/**
* Controlled filtered value, filter icon will highlight
* @type string[]
*/
filteredValue?: string[];
/**
* Customized filter icon
* @default false
* @type any
*/
filterIcon?: boolean | VNodeChild | JSX.Element;
/**
* Whether multiple filters can be selected
* @default true
* @type boolean
*/
filterMultiple?: boolean;
/**
* Filter menu config
* @type object[]
*/
filters?: ColumnFilterItem[];
/**
* Set column to be fixed: true(same as left) 'left' 'right'
* @default false
* @type boolean | string
*/
fixed?: boolean | 'left' | 'right';
/**
* Unique key of this column, you can ignore this prop if you've set a unique dataIndex
* @type string
*/
key?: string;
/**
* Renderer of the table cell. The return value should be a VNode, or an object for colSpan/rowSpan config
* @type Function | ScopedSlot
*/
customRender?: CustomRenderFunction<T> | VNodeChild | JSX.Element;
/**
* Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true
* @type boolean | Function
*/
sorter?: boolean | Function;
/**
* Order of sorted values: 'ascend' 'descend' false
* @type boolean | string
*/
sortOrder?: boolean | SortOrder;
/**
* supported sort way, could be 'ascend', 'descend'
* @default ['ascend', 'descend']
* @type string[]
*/
sortDirections?: SortOrder[];
/**
* Title of this column
* @type any (string | slot)
*/
title?: VNodeChild | JSX.Element;
/**
* Width of this column
* @type string | number
*/
width?: string | number;
/**
* Set props on per cell
* @type Function
*/
customCell?: (record: T, rowIndex: number) => object;
/**
* Set props on per header cell
* @type object
*/
customHeaderCell?: (column: ColumnProps<T>) => object;
// update-begin--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
customSummaryRender?: CustomRenderFunction<T> | VNodeChild | JSX.Element;
// update-end--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
/**
* Callback executed when the confirm filter button is clicked, Use as a filter event when using template or jsx
* @type Function
*/
onFilter?: (value: any, record: T) => boolean;
/**
* Callback executed when filterDropdownOpen is changed, Use as a filterDropdownVisible event when using template or jsx
* @type Function
*/
onFilterDropdownVisibleChange?: (visible: boolean) => void;
/**
* When using columns, you can setting this property to configure the properties that support the slot,
* such as slots: { filterIcon: 'XXX'}
* @type object
*/
slots?: Recordable<string>;
}

View File

@@ -1 +0,0 @@
export type ComponentType = 'Input' | 'InputNumber' | 'Select' | 'ApiSelect' | 'ApiTreeSelect' | 'Checkbox' | 'Switch' | 'DatePicker' | 'TimePicker';

View File

@@ -1,99 +0,0 @@
import Pagination from 'ant-design-vue/lib/pagination';
import { VNodeChild } from 'vue';
interface PaginationRenderProps {
page: number;
type: 'page' | 'prev' | 'next';
originalElement: any;
}
export declare class PaginationConfig extends Pagination {
position?: 'top' | 'bottom' | 'both';
}
export interface PaginationProps {
/**
* total number of data items
* @default 0
* @type number
*/
total?: number;
/**
* default initial page number
* @default 1
* @type number
*/
defaultCurrent?: number;
/**
* current page number
* @type number
*/
current?: number;
/**
* default number of data items per page
* @default 10
* @type number
*/
defaultPageSize?: number;
/**
* number of data items per page
* @type number
*/
pageSize?: number;
/**
* Whether to hide pager on single page
* @default false
* @type boolean
*/
hideOnSinglePage?: boolean;
/**
* determine whether pageSize can be changed
* @default false
* @type boolean
*/
showSizeChanger?: boolean;
/**
* specify the sizeChanger options
* @default ['10', '20', '30', '40']
* @type string[]
*/
pageSizeOptions?: string[];
/**
* determine whether you can jump to pages directly
* @default false
* @type boolean
*/
showQuickJumper?: boolean | object;
/**
* to display the total number and range
* @type Function
*/
showTotal?: (total: number, range: [number, number]) => any;
/**
* specify the size of Pagination, can be set to small
* @default ''
* @type string
*/
size?: string;
/**
* whether to setting simple mode
* @type boolean
*/
simple?: boolean;
/**
* to customize item innerHTML
* @type Function
*/
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
}

View File

@@ -1,478 +0,0 @@
import type { VNodeChild } from 'vue';
import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form';
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
import type { ColumnProps } from 'ant-design-vue/lib/table';
import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes';
import { RoleEnum } from '/@/enums/roleEnum';
export declare type SortOrder = 'ascend' | 'descend';
export interface TableCurrentDataSource<T = Recordable> {
currentDataSource: T[];
}
export interface TableRowSelection<T = any> extends ITableRowSelection {
/**
* Callback executed when selected rows change
* @type Function
*/
onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any;
/**
* Callback executed when select/deselect one row
* @type Function
*/
onSelect?: (record: T, selected: boolean, selectedRows: Object[], nativeEvent: Event) => any;
/**
* Callback executed when select/deselect all rows
* @type Function
*/
onSelectAll?: (selected: boolean, selectedRows: T[], changeRows: T[]) => any;
/**
* Callback executed when row selection is inverted
* @type Function
*/
onSelectInvert?: (selectedRows: string[] | number[]) => any;
}
export interface TableCustomRecord<T> {
record?: T;
index?: number;
}
export interface ExpandedRowRenderRecord<T> extends TableCustomRecord<T> {
indent?: number;
expanded?: boolean;
}
export interface ColumnFilterItem {
text?: string;
value?: string;
children?: any;
}
export interface TableCustomRecord<T = Recordable> {
record?: T;
index?: number;
}
export interface SorterResult {
column: ColumnProps;
order: SortOrder;
field: string;
columnKey: string;
}
export interface FetchParams {
searchInfo?: Recordable;
page?: number;
sortInfo?: Recordable;
filterInfo?: Recordable;
}
export interface GetColumnsParams {
ignoreIndex?: boolean;
ignoreAction?: boolean;
sort?: boolean;
}
export type SizeType = 'middle' | 'small' | 'large';
export interface TableActionType {
reload: (opt?: FetchParams) => Promise<void>;
getSelectRows: <T = Recordable>() => T[];
clearSelectedRowKeys: () => void;
expandAll: () => void;
collapseAll: () => void;
getSelectRowKeys: () => string[];
deleteSelectRowByKey: (key: string) => 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;
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void;
findTableDataRecord: (rowKey: string | number) => Recordable | void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void;
getDataSource: <T = Recordable>() => T[];
getRawDataSource: <T = Recordable>() => T;
setLoading: (loading: boolean) => void;
setProps: (props: Partial<BasicTableProps>) => void;
redoHeight: () => void;
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
getPaginationRef: () => PaginationProps | boolean;
getSize: () => SizeType;
getRowSelection: () => TableRowSelection<Recordable>;
getCacheColumns: () => BasicColumn[];
emit?: EmitType;
updateTableData: (index: number, key: string, value: any) => Recordable;
setShowPagination: (show: boolean) => Promise<void>;
getShowPagination: () => boolean;
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
}
export interface FetchSetting {
// 请求接口当前页数
pageField: string;
// 每页显示多少条
sizeField: string;
// 请求结果列表字段 支持 a.b.c
listField: string;
// 请求结果总数字段 支持 a.b.c
totalField: string;
}
export interface TableSetting {
// 是否显示刷新按钮
redo?: boolean;
// 是否显示尺寸调整按钮
size?: boolean;
// 是否显示字段调整按钮
setting?: boolean;
// 缓存“字段调整”配置的key用于页面上有多个表格需要区分的情况
cacheKey?: string;
// 是否显示全屏按钮
fullScreen?: boolean;
}
export interface BasicTableProps<T = any> {
// 点击行选中
clickToRowSelect?: boolean;
isTreeTable?: boolean;
// 自定义排序方法
sortFn?: (sortInfo: SorterResult) => any;
// 排序方法
filterFn?: (data: Partial<Recordable<string[]>>) => any;
// 取消表格的默认padding
inset?: boolean;
// 显示表格设置
showTableSetting?: boolean;
// 表格上方操作按钮设置
tableSetting?: TableSetting;
// 斑马纹
striped?: boolean;
// 是否自动生成key
autoCreateKey?: boolean;
// 计算合计行的方法
summaryFunc?: (...arg: any) => Recordable[];
// 自定义合计表格内容
summaryData?: Recordable[];
// 是否显示合计行
showSummary?: boolean;
// 是否可拖拽列
canColDrag?: boolean;
// 接口请求对象
api?: (...arg: any) => Promise<any>;
// 请求之前处理参数
beforeFetch?: Fn;
// 自定义处理接口返回参数
afterFetch?: Fn;
// 查询条件请求之前处理
handleSearchInfoFn?: Fn;
// 请求接口配置
fetchSetting?: Partial<FetchSetting>;
// 立即请求接口
immediate?: boolean;
// 在开起搜索表单的时候,如果没有数据是否显示表格
emptyDataIsShowTable?: boolean;
// 额外的请求参数
searchInfo?: Recordable;
// 默认的排序参数
defSort?: Recordable;
// 使用搜索表单
useSearchForm?: boolean;
// 表单配置
formConfig?: Partial<FormProps>;
// 列配置
columns: BasicColumn[];
// 统一设置列最大宽度
maxColumnWidth?: number;
// 是否显示序号列
showIndexColumn?: boolean;
// 序号列配置
indexColumnProps?: BasicColumn;
// 是否显示操作列
showActionColumn?: boolean;
// 操作列配置
actionColumn?: BasicColumn;
// 文本超过宽度是否显示。。。
ellipsis?: boolean;
// 是否可以自适应高度
canResize?: boolean;
// 自适应高度偏移, 计算结果-偏移量
resizeHeightOffset?: number;
// 在分页改变的时候清空选项
clearSelectOnPageChange?: boolean;
//
rowKey?: string | ((record: Recordable) => string);
// 数据
dataSource?: Recordable[];
// 标题右侧提示
titleHelpMessage?: string | string[];
// 表格最小高度
minHeight?: number;
// 表格滚动最大高度
maxHeight?: number;
// 是否显示边框
bordered?: boolean;
// 分页配置
pagination?: PaginationProps | boolean;
// loading加载
loading?: boolean;
/**
* The column contains children to display
* @default 'children'
* @type string | string[]
*/
childrenColumnName?: string;
/**
* Override default table elements
* @type object
*/
components?: object;
/**
* Expand all rows initially
* @default false
* @type boolean
*/
defaultExpandAllRows?: boolean;
/**
* Initial expanded row keys
* @type string[]
*/
defaultExpandedRowKeys?: string[];
/**
* Current expanded row keys
* @type string[]
*/
expandedRowKeys?: string[];
/**
* Expanded container render for each row
* @type Function
*/
expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element;
/**
* Customize row expand Icon.
* @type Function | VNodeChild
*/
expandIcon?: Function | VNodeChild | JSX.Element;
/**
* Whether to expand row by clicking anywhere in the whole row
* @default false
* @type boolean
*/
expandRowByClick?: boolean;
/**
* The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0
*/
expandIconColumnIndex?: number;
/**
* Table footer renderer
* @type Function | VNodeChild
*/
footer?: Function | VNodeChild | JSX.Element;
/**
* Indent size in pixels of tree data
* @default 15
* @type number
*/
indentSize?: number;
/**
* i18n text including filter, sort, empty text, etc
* @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' }
* @type object
*/
locale?: object;
/**
* Row's className
* @type Function
*/
rowClassName?: (record: TableCustomRecord<T>, index: number) => string;
/**
* Row selection config
* @type object
*/
rowSelection?: TableRowSelection;
/**
* 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
*/
// update-begin--author:liaozhiyang---date:20240424---for【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
scroll?: { x?: number | true | 'max-content'; y?: number; scrollToFirstRowOnChange?: boolean };
// update-end--author:liaozhiyang---date:20240424---for【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
/**
* Whether to show table header
* @default true
* @type boolean
*/
showHeader?: boolean;
/**
* Size of table
* @default 'default'
* @type string
*/
size?: SizeType;
/**
* Table title renderer
* @type Function | ScopedSlot
*/
title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string);
/**
* Set props on per header row
* @type Function
*/
customHeaderRow?: (column: ColumnProps, index: number) => object;
/**
* Set props on per row
* @type Function
*/
customRow?: (record: T, index: number) => object;
/**
* `table-layout` attribute of table element
* `fixed` when header/columns are fixed, or using `column.ellipsis`
*
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout
* @version 1.5.0
*/
tableLayout?: 'auto' | 'fixed' | string;
/**
* the render container of dropdowns in table
* @param triggerNode
* @version 1.5.0
*/
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
/**
* Data can be changed again before rendering.
* The default configuration of general user empty data.
* You can configured globally through [ConfigProvider](https://antdv.com/components/config-provider-cn/)
*
* @version 1.5.4
*/
transformCellText?: Function;
/**
* Callback executed before editable cell submit value, not for row-editor
*
* The cell will not submit data while callback return false
*/
beforeEditSubmit?: (data: { record: Recordable; index: number; key: string | number; value: any }) => Promise<any>;
/**
* Callback executed when pagination, filters or sorter is changed
* @param pagination
* @param filters
* @param sorter
* @param currentDataSource
*/
onChange?: (pagination: any, filters: any, sorter: any, extra: any) => void;
/**
* Callback executed when the row expand icon is clicked
*
* @param expanded
* @param record
*/
onExpand?: (expande: boolean, record: T) => void;
/**
* Callback executed when the expanded rows change
* @param expandedRows
*/
onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
onColumnsChange?: (data: ColumnChangeParam[]) => void;
}
export type CellFormat = string | ((text: string, record: Recordable, index: number) => string | number) | Map<string | number, any>;
// @ts-ignore
export interface BasicColumn extends ColumnProps<Recordable> {
children?: BasicColumn[];
filters?: {
text: string;
value: string;
children?: unknown[] | (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]));
}[];
//
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
customTitle?: VueNode;
slots?: Recordable;
// slots的备份兼容老的写法转成新写法避免控制台警告
slotsBak?: Recordable;
// 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[];
format?: CellFormat;
// Editable
edit?: boolean;
editRow?: boolean;
editable?: boolean;
editComponent?: ComponentType;
editComponentProps?: Recordable;
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
editValueMap?: (value: any) => string;
onEditRow?: () => void;
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((column: BasicColumn) => boolean);
//compType-用于记录类型
compType?: string;
// update-begin--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
customSummaryRender?: (opt: {
value: any;
text: any;
record: Recordable;
index: number;
renderIndex?: number;
column: BasicColumn;
}) => any | VNodeChild | JSX.Element;
// update-end--author:liaozhiyang---date:20240425---for【pull/1201】添加antd的TableSummary功能兼容老的summary表尾合计
}
export type ColumnChangeParam = {
dataIndex: string;
fixed: boolean | 'left' | 'right' | undefined;
visible: boolean;
};
export interface InnerHandlers {
onColumnsChange: (data: ColumnChangeParam[]) => void;
}

View File

@@ -1,32 +0,0 @@
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
import { RoleEnum } from '/@/enums/roleEnum';
export interface ActionItem extends ButtonProps {
onClick?: Fn;
label?: string;
color?: 'success' | 'error' | 'warning';
icon?: string;
popConfirm?: PopConfirm;
disabled?: boolean;
divider?: boolean;
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean);
tooltip?: string | TooltipProps;
// 自定义类名
class?: string | Record<string, boolean> | any[];
// 自定义图标颜色
iconColor?: string;
}
export interface PopConfirm {
title: string;
okText?: string;
cancelText?: string;
confirm: Fn;
cancel?: Fn;
icon?: string;
placement?: string;
overlayClassName?: string;
}

View File

@@ -1,104 +0,0 @@
<template>
<a-modal :bodyStyle="{ padding: '24px'}" :visible="iconOpen" :width="800" @ok="iconHandleOk(iconChecked)" @cancel="closeOpenIcon" title="选择图标">
<a-input
v-model:value="searchValue"
placeholder="请输入英文关键词进行搜索"
@change="filterIcon"
/>
<div class="icon-box">
<div
v-for="(item,index) in iconArr"
:key="index"
@click="sendData(item)"
class="icon-content"
:style="{ background: iconChecked === item ? '#1890ff' : ''}"
>
<component :is="$icons[item]" />
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch,defineProps} from 'vue';
import { NsMessage } from '/nerv-lib/saas';
import icons from './icons.json';
const props = defineProps(['iconHandleOk']);
const iconArr = ref<string[]>(icons);
const searchValue = ref('');
const iconOpen = ref<boolean>(false);
const iconChecked =ref('');//选中数据
/**
* 进行搜索过滤
*/
const filterIcon = () => {
if (searchValue.value){
iconArr.value = icons.filter(item => item.toLowerCase().includes(searchValue.value.toLowerCase()) )
}else{
iconArr.value = icons;
}
}
watch(iconOpen,()=>{
searchValue.value = ''
iconArr.value = icons;
})
const sendData = (data:any) => {
iconChecked.value = data;
}
const iconHandleOk = (e: MouseEvent) => {
if(!iconChecked.value){
NsMessage.error('请选择图标');
return;
}else{
props.iconHandleOk(iconChecked.value);//调用父组件传递来的函数
iconOpen.value = false;
iconChecked.value = '';
}
};
// 关闭图标选择弹窗
const closeOpenIcon = () => {
iconOpen.value = false;
iconChecked.value = '';
};
// 暴露给父组件
const isIconOpenToggle = (data:any)=>{
if(data){
iconChecked.value = data;//记住上一次选择的数据
}
iconOpen.value = true;
}
const isIconData = ()=>{
if(!iconChecked.value){
NsMessage.warn('请先选择图标');
iconOpen.value = true;
}
}
defineExpose({
isIconOpenToggle,
isIconData
})
</script>
<style lang="less">
.icon-box{
overflow: auto;
font-size: 20px;
width: 100%;
height: 280px;
padding-top:10px;
}
.icon-content{
width: 45px;
height: 40px;
margin: 6px;
cursor: pointer;
text-align: center;
border-radius: 6px;
border: 1px solid #ccc;
display: inline-block;
}
.icon-content:hover{
background: rgba(26, 144, 255,.3);
}
</style>

View File

@@ -1,790 +0,0 @@
[
"AccountBookFilled",
"AccountBookOutlined",
"AccountBookTwoTone",
"AimOutlined",
"AlertFilled",
"AlertOutlined",
"AlertTwoTone",
"AlibabaOutlined",
"AlignCenterOutlined",
"AlignLeftOutlined",
"AlignRightOutlined",
"AlipayCircleFilled",
"AlipayCircleOutlined",
"AlipayOutlined",
"AlipaySquareFilled",
"AliwangwangFilled",
"AliwangwangOutlined",
"AliyunOutlined",
"AmazonCircleFilled",
"AmazonOutlined",
"AmazonSquareFilled",
"AndroidFilled",
"AndroidOutlined",
"AntCloudOutlined",
"AntDesignOutlined",
"ApartmentOutlined",
"ApiFilled",
"ApiOutlined",
"ApiTwoTone",
"AppleFilled",
"AppleOutlined",
"AppstoreAddOutlined",
"AppstoreFilled",
"AppstoreOutlined",
"AppstoreTwoTone",
"AreaChartOutlined",
"ArrowDownOutlined",
"ArrowLeftOutlined",
"ArrowRightOutlined",
"ArrowUpOutlined",
"ArrowsAltOutlined",
"AudioFilled",
"AudioMutedOutlined",
"AudioOutlined",
"AudioTwoTone",
"AuditOutlined",
"BackwardFilled",
"BackwardOutlined",
"BankFilled",
"BankOutlined",
"BankTwoTone",
"BarChartOutlined",
"BarcodeOutlined",
"BarsOutlined",
"BehanceCircleFilled",
"BehanceOutlined",
"BehanceSquareFilled",
"BehanceSquareOutlined",
"BellFilled",
"BellOutlined",
"BellTwoTone",
"BgColorsOutlined",
"BlockOutlined",
"BoldOutlined",
"BookFilled",
"BookOutlined",
"BookTwoTone",
"BorderBottomOutlined",
"BorderHorizontalOutlined",
"BorderInnerOutlined",
"BorderLeftOutlined",
"BorderOuterOutlined",
"BorderOutlined",
"BorderRightOutlined",
"BorderTopOutlined",
"BorderVerticleOutlined",
"BorderlessTableOutlined",
"BoxPlotFilled",
"BoxPlotOutlined",
"BoxPlotTwoTone",
"BranchesOutlined",
"BugFilled",
"BugOutlined",
"BugTwoTone",
"BuildFilled",
"BuildOutlined",
"BuildTwoTone",
"BulbFilled",
"BulbOutlined",
"BulbTwoTone",
"CalculatorFilled",
"CalculatorOutlined",
"CalculatorTwoTone",
"CalendarFilled",
"CalendarOutlined",
"CalendarTwoTone",
"CameraFilled",
"CameraOutlined",
"CameraTwoTone",
"CarFilled",
"CarOutlined",
"CarTwoTone",
"CaretDownFilled",
"CaretDownOutlined",
"CaretLeftFilled",
"CaretLeftOutlined",
"CaretRightFilled",
"CaretRightOutlined",
"CaretUpFilled",
"CaretUpOutlined",
"CarryOutFilled",
"CarryOutOutlined",
"CarryOutTwoTone",
"CheckCircleFilled",
"CheckCircleOutlined",
"CheckCircleTwoTone",
"CheckOutlined",
"CheckSquareFilled",
"CheckSquareOutlined",
"CheckSquareTwoTone",
"ChromeFilled",
"ChromeOutlined",
"CiCircleFilled",
"CiCircleOutlined",
"CiCircleTwoTone",
"CiOutlined",
"CiTwoTone",
"ClearOutlined",
"ClockCircleFilled",
"ClockCircleOutlined",
"ClockCircleTwoTone",
"CloseCircleFilled",
"CloseCircleOutlined",
"CloseCircleTwoTone",
"CloseOutlined",
"CloseSquareFilled",
"CloseSquareOutlined",
"CloseSquareTwoTone",
"CloudDownloadOutlined",
"CloudFilled",
"CloudOutlined",
"CloudServerOutlined",
"CloudSyncOutlined",
"CloudTwoTone",
"CloudUploadOutlined",
"ClusterOutlined",
"CodeFilled",
"CodeOutlined",
"CodeSandboxCircleFilled",
"CodeSandboxOutlined",
"CodeSandboxSquareFilled",
"CodeTwoTone",
"CodepenCircleFilled",
"CodepenCircleOutlined",
"CodepenOutlined",
"CodepenSquareFilled",
"CoffeeOutlined",
"ColumnHeightOutlined",
"ColumnWidthOutlined",
"CommentOutlined",
"CompassFilled",
"CompassOutlined",
"CompassTwoTone",
"CompressOutlined",
"ConsoleSqlOutlined",
"ContactsFilled",
"ContactsOutlined",
"ContactsTwoTone",
"ContainerFilled",
"ContainerOutlined",
"ContainerTwoTone",
"ControlFilled",
"ControlOutlined",
"ControlTwoTone",
"CopyFilled",
"CopyOutlined",
"CopyTwoTone",
"CopyrightCircleFilled",
"CopyrightCircleOutlined",
"CopyrightCircleTwoTone",
"CopyrightOutlined",
"CopyrightTwoTone",
"CreditCardFilled",
"CreditCardOutlined",
"CreditCardTwoTone",
"CrownFilled",
"CrownOutlined",
"CrownTwoTone",
"CustomerServiceFilled",
"CustomerServiceOutlined",
"CustomerServiceTwoTone",
"DashOutlined",
"DashboardFilled",
"DashboardOutlined",
"DashboardTwoTone",
"DatabaseFilled",
"DatabaseOutlined",
"DatabaseTwoTone",
"DeleteColumnOutlined",
"DeleteFilled",
"DeleteOutlined",
"DeleteRowOutlined",
"DeleteTwoTone",
"DeliveredProcedureOutlined",
"DeploymentUnitOutlined",
"DesktopOutlined",
"DiffFilled",
"DiffOutlined",
"DiffTwoTone",
"DingdingOutlined",
"DingtalkCircleFilled",
"DingtalkOutlined",
"DingtalkSquareFilled",
"DisconnectOutlined",
"DislikeFilled",
"DislikeOutlined",
"DislikeTwoTone",
"DollarCircleFilled",
"DollarCircleOutlined",
"DollarCircleTwoTone",
"DollarOutlined",
"DollarTwoTone",
"DotChartOutlined",
"DoubleLeftOutlined",
"DoubleRightOutlined",
"DownCircleFilled",
"DownCircleOutlined",
"DownCircleTwoTone",
"DownOutlined",
"DownSquareFilled",
"DownSquareOutlined",
"DownSquareTwoTone",
"DownloadOutlined",
"DragOutlined",
"DribbbleCircleFilled",
"DribbbleOutlined",
"DribbbleSquareFilled",
"DribbbleSquareOutlined",
"DropboxCircleFilled",
"DropboxOutlined",
"DropboxSquareFilled",
"EditFilled",
"EditOutlined",
"EditTwoTone",
"EllipsisOutlined",
"EnterOutlined",
"EnvironmentFilled",
"EnvironmentOutlined",
"EnvironmentTwoTone",
"EuroCircleFilled",
"EuroCircleOutlined",
"EuroCircleTwoTone",
"EuroOutlined",
"EuroTwoTone",
"ExceptionOutlined",
"ExclamationCircleFilled",
"ExclamationCircleOutlined",
"ExclamationCircleTwoTone",
"ExclamationOutlined",
"ExpandAltOutlined",
"ExpandOutlined",
"ExperimentFilled",
"ExperimentOutlined",
"ExperimentTwoTone",
"ExportOutlined",
"EyeFilled",
"EyeInvisibleFilled",
"EyeInvisibleOutlined",
"EyeInvisibleTwoTone",
"EyeOutlined",
"EyeTwoTone",
"FacebookFilled",
"FacebookOutlined",
"FallOutlined",
"FastBackwardFilled",
"FastBackwardOutlined",
"FastForwardFilled",
"FastForwardOutlined",
"FieldBinaryOutlined",
"FieldNumberOutlined",
"FieldStringOutlined",
"FieldTimeOutlined",
"FileAddFilled",
"FileAddOutlined",
"FileAddTwoTone",
"FileDoneOutlined",
"FileExcelFilled",
"FileExcelOutlined",
"FileExcelTwoTone",
"FileExclamationFilled",
"FileExclamationOutlined",
"FileExclamationTwoTone",
"FileFilled",
"FileGifOutlined",
"FileImageFilled",
"FileImageOutlined",
"FileImageTwoTone",
"FileJpgOutlined",
"FileMarkdownFilled",
"FileMarkdownOutlined",
"FileMarkdownTwoTone",
"FileOutlined",
"FilePdfFilled",
"FilePdfOutlined",
"FilePdfTwoTone",
"FilePptFilled",
"FilePptOutlined",
"FilePptTwoTone",
"FileProtectOutlined",
"FileSearchOutlined",
"FileSyncOutlined",
"FileTextFilled",
"FileTextOutlined",
"FileTextTwoTone",
"FileTwoTone",
"FileUnknownFilled",
"FileUnknownOutlined",
"FileUnknownTwoTone",
"FileWordFilled",
"FileWordOutlined",
"FileWordTwoTone",
"FileZipFilled",
"FileZipOutlined",
"FileZipTwoTone",
"FilterFilled",
"FilterOutlined",
"FilterTwoTone",
"FireFilled",
"FireOutlined",
"FireTwoTone",
"FlagFilled",
"FlagOutlined",
"FlagTwoTone",
"FolderAddFilled",
"FolderAddOutlined",
"FolderAddTwoTone",
"FolderFilled",
"FolderOpenFilled",
"FolderOpenOutlined",
"FolderOpenTwoTone",
"FolderOutlined",
"FolderTwoTone",
"FolderViewOutlined",
"FontColorsOutlined",
"FontSizeOutlined",
"ForkOutlined",
"FormOutlined",
"FormatPainterFilled",
"FormatPainterOutlined",
"ForwardFilled",
"ForwardOutlined",
"FrownFilled",
"FrownOutlined",
"FrownTwoTone",
"FullscreenExitOutlined",
"FullscreenOutlined",
"FunctionOutlined",
"FundFilled",
"FundOutlined",
"FundProjectionScreenOutlined",
"FundTwoTone",
"FundViewOutlined",
"FunnelPlotFilled",
"FunnelPlotOutlined",
"FunnelPlotTwoTone",
"GatewayOutlined",
"GifOutlined",
"GiftFilled",
"GiftOutlined",
"GiftTwoTone",
"GithubFilled",
"GithubOutlined",
"GitlabFilled",
"GitlabOutlined",
"GlobalOutlined",
"GoldFilled",
"GoldOutlined",
"GoldTwoTone",
"GoldenFilled",
"GoogleCircleFilled",
"GoogleOutlined",
"GooglePlusCircleFilled",
"GooglePlusOutlined",
"GooglePlusSquareFilled",
"GoogleSquareFilled",
"GroupOutlined",
"HddFilled",
"HddOutlined",
"HddTwoTone",
"HeartFilled",
"HeartOutlined",
"HeartTwoTone",
"HeatMapOutlined",
"HighlightFilled",
"HighlightOutlined",
"HighlightTwoTone",
"HistoryOutlined",
"HomeFilled",
"HomeOutlined",
"HomeTwoTone",
"HourglassFilled",
"HourglassOutlined",
"HourglassTwoTone",
"Html5Filled",
"Html5Outlined",
"Html5TwoTone",
"IdcardFilled",
"IdcardOutlined",
"IdcardTwoTone",
"IeCircleFilled",
"IeOutlined",
"IeSquareFilled",
"ImportOutlined",
"InboxOutlined",
"InfoCircleFilled",
"InfoCircleOutlined",
"InfoCircleTwoTone",
"InfoOutlined",
"InsertRowAboveOutlined",
"InsertRowBelowOutlined",
"InsertRowLeftOutlined",
"InsertRowRightOutlined",
"InstagramFilled",
"InstagramOutlined",
"InsuranceFilled",
"InsuranceOutlined",
"InsuranceTwoTone",
"InteractionFilled",
"InteractionOutlined",
"InteractionTwoTone",
"IssuesCloseOutlined",
"ItalicOutlined",
"KeyOutlined",
"LaptopOutlined",
"LayoutFilled",
"LayoutOutlined",
"LayoutTwoTone",
"LeftCircleFilled",
"LeftCircleOutlined",
"LeftCircleTwoTone",
"LeftOutlined",
"LeftSquareFilled",
"LeftSquareOutlined",
"LeftSquareTwoTone",
"LikeFilled",
"LikeOutlined",
"LikeTwoTone",
"LineChartOutlined",
"LineHeightOutlined",
"LineOutlined",
"LinkOutlined",
"LinkedinFilled",
"LinkedinOutlined",
"Loading3QuartersOutlined",
"LoadingOutlined",
"LockFilled",
"LockOutlined",
"LockTwoTone",
"LoginOutlined",
"LogoutOutlined",
"MacCommandFilled",
"MacCommandOutlined",
"MailFilled",
"MailOutlined",
"MailTwoTone",
"ManOutlined",
"MedicineBoxFilled",
"MedicineBoxOutlined",
"MedicineBoxTwoTone",
"MediumCircleFilled",
"MediumOutlined",
"MediumSquareFilled",
"MediumWorkmarkOutlined",
"MehFilled",
"MehOutlined",
"MehTwoTone",
"MenuFoldOutlined",
"MenuOutlined",
"MenuUnfoldOutlined",
"MergeCellsOutlined",
"MessageFilled",
"MessageOutlined",
"MessageTwoTone",
"MinusCircleFilled",
"MinusCircleOutlined",
"MinusCircleTwoTone",
"MinusOutlined",
"MinusSquareFilled",
"MinusSquareOutlined",
"MinusSquareTwoTone",
"MobileFilled",
"MobileOutlined",
"MobileTwoTone",
"MoneyCollectFilled",
"MoneyCollectOutlined",
"MoneyCollectTwoTone",
"MonitorOutlined",
"MoreOutlined",
"NodeCollapseOutlined",
"NodeExpandOutlined",
"NodeIndexOutlined",
"NotificationFilled",
"NotificationOutlined",
"NotificationTwoTone",
"NumberOutlined",
"OneToOneOutlined",
"OrderedListOutlined",
"PaperClipOutlined",
"PartitionOutlined",
"PauseCircleFilled",
"PauseCircleOutlined",
"PauseCircleTwoTone",
"PauseOutlined",
"PayCircleFilled",
"PayCircleOutlined",
"PercentageOutlined",
"PhoneFilled",
"PhoneOutlined",
"PhoneTwoTone",
"PicCenterOutlined",
"PicLeftOutlined",
"PicRightOutlined",
"PictureFilled",
"PictureOutlined",
"PictureTwoTone",
"PieChartFilled",
"PieChartOutlined",
"PieChartTwoTone",
"PlayCircleFilled",
"PlayCircleOutlined",
"PlayCircleTwoTone",
"PlaySquareFilled",
"PlaySquareOutlined",
"PlaySquareTwoTone",
"PlusCircleFilled",
"PlusCircleOutlined",
"PlusCircleTwoTone",
"PlusOutlined",
"PlusSquareFilled",
"PlusSquareOutlined",
"PlusSquareTwoTone",
"PoundCircleFilled",
"PoundCircleOutlined",
"PoundCircleTwoTone",
"PoundOutlined",
"PoweroffOutlined",
"PrinterFilled",
"PrinterOutlined",
"PrinterTwoTone",
"ProfileFilled",
"ProfileOutlined",
"ProfileTwoTone",
"ProjectFilled",
"ProjectOutlined",
"ProjectTwoTone",
"PropertySafetyFilled",
"PropertySafetyOutlined",
"PropertySafetyTwoTone",
"PullRequestOutlined",
"PushpinFilled",
"PushpinOutlined",
"PushpinTwoTone",
"QqCircleFilled",
"QqOutlined",
"QqSquareFilled",
"QrcodeOutlined",
"QuestionCircleFilled",
"QuestionCircleOutlined",
"QuestionCircleTwoTone",
"QuestionOutlined",
"RadarChartOutlined",
"RadiusBottomleftOutlined",
"RadiusBottomrightOutlined",
"RadiusSettingOutlined",
"RadiusUpleftOutlined",
"RadiusUprightOutlined",
"ReadFilled",
"ReadOutlined",
"ReconciliationFilled",
"ReconciliationOutlined",
"ReconciliationTwoTone",
"RedEnvelopeFilled",
"RedEnvelopeOutlined",
"RedEnvelopeTwoTone",
"RedditCircleFilled",
"RedditOutlined",
"RedditSquareFilled",
"RedoOutlined",
"ReloadOutlined",
"RestFilled",
"RestOutlined",
"RestTwoTone",
"RetweetOutlined",
"RightCircleFilled",
"RightCircleOutlined",
"RightCircleTwoTone",
"RightOutlined",
"RightSquareFilled",
"RightSquareOutlined",
"RightSquareTwoTone",
"RiseOutlined",
"RobotFilled",
"RobotOutlined",
"RocketFilled",
"RocketOutlined",
"RocketTwoTone",
"RollbackOutlined",
"RotateLeftOutlined",
"RotateRightOutlined",
"SafetyCertificateFilled",
"SafetyCertificateOutlined",
"SafetyCertificateTwoTone",
"SafetyOutlined",
"SaveFilled",
"SaveOutlined",
"SaveTwoTone",
"ScanOutlined",
"ScheduleFilled",
"ScheduleOutlined",
"ScheduleTwoTone",
"ScissorOutlined",
"SearchOutlined",
"SecurityScanFilled",
"SecurityScanOutlined",
"SecurityScanTwoTone",
"SelectOutlined",
"SendOutlined",
"SettingFilled",
"SettingOutlined",
"SettingTwoTone",
"ShakeOutlined",
"ShareAltOutlined",
"ShopFilled",
"ShopOutlined",
"ShopTwoTone",
"ShoppingCartOutlined",
"ShoppingFilled",
"ShoppingOutlined",
"ShoppingTwoTone",
"ShrinkOutlined",
"SignalFilled",
"SisternodeOutlined",
"SketchCircleFilled",
"SketchOutlined",
"SketchSquareFilled",
"SkinFilled",
"SkinOutlined",
"SkinTwoTone",
"SkypeFilled",
"SkypeOutlined",
"SlackCircleFilled",
"SlackOutlined",
"SlackSquareFilled",
"SlackSquareOutlined",
"SlidersFilled",
"SlidersOutlined",
"SlidersTwoTone",
"SmallDashOutlined",
"SmileFilled",
"SmileOutlined",
"SmileTwoTone",
"SnippetsFilled",
"SnippetsOutlined",
"SnippetsTwoTone",
"SolutionOutlined",
"SortAscendingOutlined",
"SortDescendingOutlined",
"SoundFilled",
"SoundOutlined",
"SoundTwoTone",
"SplitCellsOutlined",
"StarFilled",
"StarOutlined",
"StarTwoTone",
"StepBackwardFilled",
"StepBackwardOutlined",
"StepForwardFilled",
"StepForwardOutlined",
"StockOutlined",
"StopFilled",
"StopOutlined",
"StopTwoTone",
"StrikethroughOutlined",
"SubnodeOutlined",
"SwapLeftOutlined",
"SwapOutlined",
"SwapRightOutlined",
"SwitcherFilled",
"SwitcherOutlined",
"SwitcherTwoTone",
"SyncOutlined",
"TableOutlined",
"TabletFilled",
"TabletOutlined",
"TabletTwoTone",
"TagFilled",
"TagOutlined",
"TagTwoTone",
"TagsFilled",
"TagsOutlined",
"TagsTwoTone",
"TaobaoCircleFilled",
"TaobaoCircleOutlined",
"TaobaoOutlined",
"TaobaoSquareFilled",
"TeamOutlined",
"ThunderboltFilled",
"ThunderboltOutlined",
"ThunderboltTwoTone",
"ToTopOutlined",
"ToolFilled",
"ToolOutlined",
"ToolTwoTone",
"TrademarkCircleFilled",
"TrademarkCircleOutlined",
"TrademarkCircleTwoTone",
"TrademarkOutlined",
"TransactionOutlined",
"TranslationOutlined",
"TrophyFilled",
"TrophyOutlined",
"TrophyTwoTone",
"TwitterCircleFilled",
"TwitterOutlined",
"TwitterSquareFilled",
"UnderlineOutlined",
"UndoOutlined",
"UngroupOutlined",
"UnlockFilled",
"UnlockOutlined",
"UnlockTwoTone",
"UnorderedListOutlined",
"UpCircleFilled",
"UpCircleOutlined",
"UpCircleTwoTone",
"UpOutlined",
"UpSquareFilled",
"UpSquareOutlined",
"UpSquareTwoTone",
"UploadOutlined",
"UsbFilled",
"UsbOutlined",
"UsbTwoTone",
"UserAddOutlined",
"UserDeleteOutlined",
"UserOutlined",
"UserSwitchOutlined",
"UsergroupAddOutlined",
"UsergroupDeleteOutlined",
"VerifiedOutlined",
"VerticalAlignBottomOutlined",
"VerticalAlignMiddleOutlined",
"VerticalAlignTopOutlined",
"VerticalLeftOutlined",
"VerticalRightOutlined",
"VideoCameraAddOutlined",
"VideoCameraFilled",
"VideoCameraOutlined",
"VideoCameraTwoTone",
"WalletFilled",
"WalletOutlined",
"WalletTwoTone",
"WarningFilled",
"WarningOutlined",
"WarningTwoTone",
"WechatFilled",
"WechatOutlined",
"WeiboCircleFilled",
"WeiboCircleOutlined",
"WeiboOutlined",
"WeiboSquareFilled",
"WeiboSquareOutlined",
"WhatsAppOutlined",
"WifiOutlined",
"WindowsFilled",
"WindowsOutlined",
"WomanOutlined",
"YahooFilled",
"YahooOutlined",
"YoutubeFilled",
"YoutubeOutlined",
"YuqueFilled",
"YuqueOutlined",
"ZhihuCircleFilled",
"ZhihuOutlined",
"ZhihuSquareFilled",
"ZoomInOutlined",
"ZoomOutOutlined"
]

View File

@@ -1,6 +1,7 @@
<template>
<ns-modal
ref="modalRef"
centered
v-bind="extraModalConfig"
destroyOnClose
v-model:visible="visible"
@@ -66,14 +67,10 @@
httpRequest({ api, params: data, pathParams: params, requestConfig })
.then((res) => {
if (res.msg === 'success') {
NsMessage.success('操作成功', 1, () => {
toggle();
success && success(res);
});
} else {
NsMessage.error(res.msg);
}
NsMessage.success('操作成功', 1, () => {
toggle();
success && success(res);
});
})
.finally(() => {
setLoading(false);

View File

@@ -1,41 +1,48 @@
<template>
<a-steps direction="vertical" :current="size">
<template v-for="(item, index) in dataSource" :key="index">
<a-step title="">
<a-step>
<template #icon>
<ns-icon size="24" :name="item.src" />
<img :style="{ width: item.status === '2' ? '19px' : '20px' }" :src="getSrc(item)" />
</template>
<template #description>
<div class="card">
<div class="card-title">
<div class="name">{{ item.realName }}</div>
<div
style="
width: 400px;
min-height: 0px;
background-color: #f8fafc;
margin-left: 20px;
border-radius: 4px; /* 设置圆角半径 */
padding: 12px;
">
<div style="width: 100%; height: 30px; display: flex; position: relative">
<a-tag
class="card-title-tag"
:style="{
'background-color': item.bgColor,
border: '1px solid ' + item.color,
color: item.color,
}"
>{{ item.stateName }}
</a-tag>
<div class="time">{{ item.createTime }}</div>
style="width: 60px; height: 20px; text-align: center"
:color="getColor(item)"
>{{ getStatus(item) }}</a-tag
>
<div
style="
position: absolute;
left: 35%;
top: -2px;
transform: translateX(-50%);
color: #3a3a3a;
"
>{{ item.name }}</div
>
<div style="position: absolute; right: 10px; top: -2px; color: #ff7602"
>2024-03-11 11:30:06</div
>
</div>
<div
style="
width: 100%;
color: rgba(0, 0, 0, 0.45);
max-height: 35px;
overflow-y: scroll;
padding: 7px 0px;
">
{{ item.remarks }}</div
<div style="width: 100%; color: #3a3a3a; height: 25px; overflow: auto">
工单已完成并通过验收</div
>
</div>
</template>
</a-step>
</template>
</a-steps>
<a-empty v-if="dataSource.length === 0" :description="'暂无数据'" />
</template>
<script lang="ts" setup>
@@ -46,6 +53,37 @@
size: any;
};
const props = withDefaults(defineProps<Props>(), {});
const getColor = (item: any) => {
switch (item.status) {
case '0':
return '#ff7602';
case '1':
return '#00a1e6';
case '2':
return '#04d919';
case '3':
return '#d9001b';
case '4':
return '#a6a6a6';
}
};
const getSrc = (item: any) => {
return '../../../../src/icon/status-' + item.status + '.svg';
};
const getStatus = (item: any) => {
switch (item.status) {
case '0':
return '待处理';
case '1':
return '处理中';
case '2':
return '已完成';
case '3':
return '超时';
case '4':
return '已关闭';
}
};
const { dataSource } = toRefs(props);
const { size } = toRefs(props);
@@ -54,54 +92,16 @@
<style lang="less" scoped>
.ant-steps-vertical {
margin-left: 20px;
margin-top: 20px;
}
.card {
width: 450px;
height: 90px;
background: rgba(191, 205, 226, 0.1);
border-radius: 4px; /* 设置圆角半径 */
padding: 12px;
margin-left: 8px;
.card-title {
width: 100%;
height: 30px;
display: flex;
position: relative;
.card-title-tag {
width: 50px;
height: 24px;
text-align: center;
line-height: 24px;
margin-left: 12px;
font-size: 12px;
}
}
}
.name {
left: 12px;
top: -2px;
font-size: 16px;
color: rgba(153, 153, 153, 1);
}
.time {
position: absolute;
right: 10px;
top: -2px;
color: rgba(0, 0, 0, 0.45);
margin-top: 10px;
}
:deep(.ant-steps-item-tail) {
position: absolute !important;
// top: -10px !important;
top: -10px !important;
left: 16px !important;
width: 1px !important;
height: 120% !important;
height: 150% !important;
}
:deep(.ant-steps-item) {
margin-top: 8px !important;
}
.ant-empty {
margin-top: 50%;
transform: translateY(-50%);
margin-top: 20px !important;
}
</style>

View File

@@ -3,19 +3,11 @@ import { permission } from '/@/api/origanizemanage';
import { appConfigStore } from '/nerv-lib/saas/store/modules/app-config';
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
import { get } from 'lodash-es';
import { computed, ref, toRef} from 'vue';
import { computed, ref, toRef } from 'vue';
import { router } from '/nerv-lib/saas/router';
import { replyRoutesButton } from '/@/util/dynamicRoutes';
import { replyDynamRoutesPath } from '/nerv-lib/util/dynamicRoutesss';
import { useTags } from '/nerv-base/store/modules/tags';
const ORGID = sessionStorage.getItem('ORGID') ? Number(sessionStorage.getItem('ORGID')) : '';
const ROLEID = sessionStorage.getItem('ROLEID') ? Number(sessionStorage.getItem('ROLEID')) : '';
// const isAdmin = sessionStorage.getItem('ISADMIN')
// ? Boolean(sessionStorage.getItem('ISADMIN'))
// : false;
const selectDefaultValue = ref(ROLEID);
// const selectDefaultDisabled = ref(isAdmin);
const selectDefaultValue = ref(ORGID);
const transform = (data, map) => {
return Object.keys(map).reduce((pre, cur) => {
pre[cur] = data[map[cur]];
@@ -27,7 +19,7 @@ export const appConfig = {
projectType: 'web',
baseApi: '/api',
projectName: '济阳站_AI智能BAS系统',
enablePermissions: false,
enablePermissions: true,
// themeColor: '#eee',
siderPosition: 'left',
baseHeader: '/parkingManage',
@@ -51,25 +43,21 @@ export const appConfig = {
api: '/carbon-smart/user/login/logInInfo',
size: 'large',
defaultValue: selectDefaultValue,
disabled:false,
// disabled: selectDefaultDisabled,
// autoSelectFirst: true,
placeholder: '请选择',
onSelect: async (cur:any, record:any) => {
onSelect: async (cur, record) => {
console.log(cur, record);
const configStore = appConfigStore();
const useAuthorization = authorizationService();
sessionStorage.setItem('ORGID', record.orgId);
sessionStorage.setItem('ROLEID', record.roleId);
const res = await configStore.userResource({ data: record });
useAuthorization.updateUserResource(res.data);
setTimeout(() => {
window.location.reload();
}, 200);
// value.value = cur;
},
resultField: 'data.userRoles',
resultField: 'data.linkList',
labelField: 'orgName',
valueField: 'roleId',
valueField: 'orgId',
immediate: true,
dropdownReload: true,
},
@@ -79,33 +67,15 @@ export const appConfig = {
return http.post('/carbon-smart/user/login', { ...params });
},
userResourceApi: (params) => {
const { roleId } = get(params, 'data') || { roleId: '' };
const finalId = roleId || ROLEID;
const { orgId } = get(params, 'data') || { orgId: '' };
const finalId = orgId || ORGID;
// 解决初始化登录select无初始值的问题
selectDefaultValue.value = finalId;
// const userInfo = JSON.parse(sessionStorage.getItem(import.meta.env.VITE_PUBLIC_PATH)!);
// const ownOrgInfo = userInfo.userRoles?.filter(({ roleId: id }) => id === finalId)[0];
// return http.post('/carbon-smart/user/login/logInPermission', ownOrgInfo).then((res) => {
// return res;
// });
// 切换角色时获取新的权限菜单
return http.post('/carbon-smart/user/login/role/permission/'+finalId).then((res) => {
//切换后重新处理动态菜单数据
let resetDynamicRouteList = replyRoutesButton(res.data);
sessionStorage.setItem("dynamicRouteList",JSON.stringify(resetDynamicRouteList));
if(resetDynamicRouteList && resetDynamicRouteList.length>0){
let addDynamicRoutes = replyDynamRoutesPath(resetDynamicRouteList);
addDynamicRoutes.forEach(item => {
if (item.children && !item.component) {
item.component = () => import('/nerv-lib/saas/view/system/application.vue');//hx-ai-intelligent项目下的首页
}
router.addRoute(item);
})
}
//删除所有已经打开的tag标签
useTags().clearTags();
router.replace({ name: 'home' });
const userInfo = JSON.parse(sessionStorage.getItem(import.meta.env.VITE_PUBLIC_PATH)!);
const ownOrgInfo = userInfo.linkList?.filter(({ orgId: id }) => id === finalId)[0];
return http.post('/carbon-smart/user/login/logInPermission', ownOrgInfo).then((res) => {
return res;
});
},
@@ -121,26 +91,13 @@ export const appConfig = {
projectId: 'projectId',
linkList: 'linkList',
permissionVos: 'permissionVos',
adminFlag: 'adminFlag',
userRoles:'userRoles',
});
sessionStorage.setItem('ORGID', info.orgId);
sessionStorage.setItem('LINKLIST', JSON.stringify(info.linkList));
// 头部“企业角色”切换暂不需要以下屏蔽的两个参数
// sessionStorage.setItem('ISADMIN', trD?.adminFlag === '1');
// selectDefaultDisabled.value = info?.adminFlag === '1';
if(info.userRoles && info.userRoles.length>0){
sessionStorage.setItem('ROLEID', info.userRoles[0].roleId);
sessionStorage.setItem('USERROLES', JSON.stringify(info.userRoles));
selectDefaultValue.value = info.userRoles[0].roleId;
//路由接口数据暂时组装
let getRoutesList = replyRoutesButton(info.userRoles[0].routes);
sessionStorage.setItem("dynamicRouteList",JSON.stringify(getRoutesList))
sessionStorage.setItem("isRefrech",'1')
}
return { data: { ...trD } };
});
selectDefaultValue.value = info.orgId;
return { data: { ...trD } };
});
},
useHistoryTag: false,
// 修改密码配置
updatePassWordInfo: {

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