release: 2.2.0
* fix: 优化docker * fix: 修复用户管理查询条件在..中,不在..中无效问题 * fix: 删除FRuleEditor多余文件 * fix: 修复产品进行规则属性调试时,部分内容无法调试成功 * fix: bug#25001 * fix: 优化物联卡详情仪表盘显示异常;修改菜单拖拽排序 * fix:bug#25001 * fix: 优化物联卡详情仪表盘显示异常 * fix: 修改菜单拖拽排序 * fix: 24641、24804、25111、22958、24806、25377、25597、24628、25704、24545、24479 * fix: 优化物联卡批量导入数量统计;优化物联卡状态查询列表 * fix: 24641、24804、25111、22958、24806、25377、25597 * fix: 24628 * feat: 修改区域管理删除逻辑 * fix: 24628、25704 * fix: 24545 * fix: bug#25414 * fix: bug#24479 * fix: bug#23410 * fix: bug#24404 * fix: bug#25374 * fix: bug#25062 * fix: bug#25062 * fix: bug#22409 * fix: bug#25525、26083 * fix: bug#25525、26083 Co-authored-by: leiqiaochu <leiqiaochu@aossci.com> * fix: bug#26186 * fix: bug#26233 * fix: bug#26233 * feat: 物模型-功能定义、事件定义添加【其它配置】 * feat: 新增metadataTable组件;新增虚拟滚动table功能;优化物模型右键菜单;优化物模型搜索;优化物模型拓展配置 * feat: 新增metadataTable组件 * feat: 新增虚拟滚动table功能 * fix: 优化虚拟滚动 * fix: 优化物模型属性、功能、事件、标签组件 * fix: 优化物模型右键菜单 * feat: 新增右键功能 * fix: 优化虚拟滚动 * fix: 优化物模型其它配置参数 * fix: 优化物模型搜索;优化物模型拓展配置 * fix: 优化存储方式导致物模型无法新增、修改提示语 * fix: 优化存储方式导致物模型无法新增、修改提示语 * feat: 新增日历维护管理 * feat: 日历维护日历组件 * feat: 日历维护新增标签 * feat: 日历维护左侧标签树新增功能 * feat: 日历维护左侧标签树 * feat: 日历维护左侧标签功能 * feat: 日历维护 * feat: 日历维护日历功能 * feat: 日历维护添加标签功能 * feat: 日历维护模块 * feat: 修改日历组件交互逻辑 * feat: 修改日历维护组件 * feat: 日历维护tips * feat: 封装日历预览组件 * feat: 日历维护数据请求逻辑 * feat: 修改请求逻辑及快速跳转功能 * feat: 远程升级交互修改 * style: 远程升级交互修改 * style: 远程升级模块交互修改 * style: 远程升级关闭单个升级记录 * feat: 新增批量操作后更新状态逻辑 * fix: 修改任务详情交互逻辑 * fix: 修改远程升级权限按钮 * style: 远程升级抽屉无数据样式 * style: 修改升级任务详情滚动样式 * fix: 新建远程升级任务接口传参 * update: 场景联动-时间添加Calendar组件;优化场景联动多条件;优化场景联动样式 * feat: 场景联动-时间添加Calendar组件 * fix: 优化场景联动多条件 * fix: 优化场景联动绑定告警配置 * update: 对接场景联动-执行动作绑定告警新接口 * update: 场景联动-定时触发新增自定义日历 * fix: 优化场景联动-自定义日历标签下拉筛选 * fix: 优化场景联动-自定义日历标签数据回显异常 * fix: 优化场景联动-说明排版及样式 * fix: 优化场景联动样式 * fix: 优化物模型编辑状态校验逻辑;优化场景联动-执行动作绑定告警查询条件 * fix: 优化物模型编辑状态校验逻辑 * fix: 优化场景联动-执行动作绑定告警查询条件 * fix: 优化物模型其它配置tag显示 * fix: 优化设备物模型-继承属性删除限制 * update: 告警记录优化 * feat: 告警记录 * feat: 抽屉蒙层关闭 * feat: 告警记录 * feat: 告警记录功能开发 * feat: 告警记录基础功能 * feat: 告警日志跳转设备逻辑 * feat: 提取计算时间差组件 * feat: 删除冗余代码 * fix: 修复执行动作-设备-无法选择内置属性 * fix: 修复执行动作-通知-无法选择固定邮箱 * fix: 修复执行动作-通知-固定邮箱校验提示 * feat: 新增设备详情告警列表 * feat: 抽屉蒙层关闭 * feat: 告警记录功能开发 * feat: 告警记录基础功能 * feat: 告警日志跳转设备逻辑 * feat: 设备详情告警列表 * feat: 提取计算时间差组件 * feat: 产品告警列表和设备告警列表 * feat: 产品请求数据逻辑 * feat: 基础配置增加部分配置项 * update: 取消地图不填写key兼容 * feat: 告警等级请求优化 * feat: 新增视频插件接入和视频插件网关 * feat: 视频接入 * feat: 视频插件接入和视频插件网关 * update: 优化告警配置新增场景联动逻辑 * update: 优化告警配置新增场景联动逻辑 * update: 优化告警配置新增场景联动逻辑 * update: 优化告警配置关联场景联动 * update: 优化告警配置关联场景联动; 修复部分场景联动bug * update: 优化告警配置-关联场景联动取消关联操作 * feat: 新增设备物联卡 * feat: 物联网卡 * feat: 设备物联卡 * fix: bug#26868、26876、26860、26869 * fix: bug#26868、26876、26876、26860 * fix: bug#26869 * update: 优化地区管理 * update: 优化地区管理 * bug#27400、27423、27035、26867、27395 * fix: bug#27395 * fix: bug#27400、27423 * fix: bug#27035 * fix: bug#26867 * fix: 优化代码逻辑 * fix: 优化登录页面挂载久了之后无法登录;修复执行动作-触发告警-关联告警,关联告警弹窗中搜索项未调用接口 * fix: 优化登录页面挂载久了之后无法登录 * fix: 修复执行动作-触发告警-关联告警,关联告警弹窗中搜索项未调用接口 * fix: bug#27018 * fix: bug#27019 * fix: 优化告警卡片样式 * fix: bug#27029 * fix: 优化主题色 * fix: 修复设备物模型无法保存 * update: 优化告警配置删除交互逻辑 * fix: bug#26877 * feat: 修改告警配置删除交互逻辑 * fix: 修改请求判断条件及提示语句 * fix: bug#27220物模型-标签 删除读写类型;bug#27217修复设备物模型保存之后继承的物模型变为可编辑状态问题 * fix: bug#27220物模型-标签 删除读写类型 * fix: bug#27217修复设备物模型保存之后继承的物模型变为可编辑状态问题 * fix: bug#27230修复物模型-全屏状态下,无法显示属性详情弹窗 * fix: bug#27230修复物模型-全屏状态下,无法显示保存操作提示 * fix: bug#27368修复告警配置查询条件 * fix: bug#27415场景联动-关联告警:弹窗隐藏footer * fix: 优化登录密码 * fix: 修复物模型新增行属性来源不显示编辑按钮 * fix: 修复物模型复制行-粘贴行,不能编辑 * fix: 修复物模型删除行,错误提示依旧存在 * fix: bug#27152修复物模型保存页面无提示 * fix: bug#27155修复物模型存在相同标识提示异常 * fix: bug#27155修复物模型数据类型为object时联动配置中进行多次配置数据类型界面异常 * fix: bug#27173优化物模型读写类型样式 * fix: bug#26862日历新增后删除失败问题;Bacnet新增广播端口限制1-65535;优化日历维护可拖拽区域;空白日期快速作用覆盖 * fix: bug#27347新增onvif和插件视频接入图标 * fix: bug#26862日历新增后删除失败问题 * fix: bug#27035日历组件渲染宽高不固定问题 * fix: bug#27234 Bacnet新增广播端口限制1-65535 * fix: bug#27373修复告警记录告警持续时间错误问题 * fix: bug#27374 优化高景源设备ID * fix: bug#26860优化日历维护可拖拽区域、bug#27528空白日期快速作用覆盖 * fix: bug#27222修复物模型查看属性详情时,详情中存储方式显示错误;修复物模型-编辑规则-规则属性详情显示标识 * fix: bug#27425修复物模型,配置单位可搜索 * fix: bug#27182优化物模型编辑规则样式 * fix: bug#27184修改物模型编辑规则不能选择规则属性 * fix: 修复切换产品,物模型显示异常 * fix: bug#27218修复物模型-编辑规则-规则属性详情显示标识 * fix: bug#27222修复物模型查看属性详情时,详情中存储方式显示错误 * fix: 修复告警配置查询结果异常 * fix: bug修改数采表单校验、个人中心站内信json无法展开、修改告警记录处理方式取值 * fix: bug修改数采表单校验、bug#27384个人中心站内信json无法展开 * feat: 修改告警记录处理方式取值 * feat: 手动触发展示优化 * fix: 优化函数引用 * fix: 修改引用和查询条件 告警手动触发 * fix: 修复物模型详情精度显示;bug#27225修复物模型复制粘贴操作逻辑;修复物模型编辑规则-查看属性,是否只读展示错误 * fix: 修复物模型详情精度显示 * fix: bug#27224修复物模型详情指标显示 * fix: bug#27225修复物模型复制粘贴操作逻辑 * fix: bug#27226修复物模型复制跨标签粘贴逻辑 * fix: bug#27425修复物模型,单位无法进行搜索 * fix: bug#27565修复物模型,单位无法回显 * fix: 优化物模型详情数据类型展示 * revert: 撤销物模型-编辑规则过滤条件 * fix: bug#27569修复物模型编辑规则-查看属性,是否只读展示错误 * fix: bug#27216修复编辑规则属性赋值为空时进行运行需进行提示 * fix: bug#27182优化编辑规则样式 * fix: bug#27223修复物模型详情,属性来源后方无查看按钮 * fix: bug#27506优化场景联动触发规则样式 * fix: bug#27434修复场景联动新增执行动作后,在告警配置新增添加时,变更为了无效的数据 * fix: bug#27263修复场景联动添加设备下发指令读操作,保存后查看,提示数据发生更改 * fix: bug#27365修复场景联动添加设备下发指选择内置参数为设备名称,upperKey异常 * fix: bug#27365修复场景联动条件下拉没有字段名称 * fix: bug#27090修复场景联动触发条件参数类型为array时显示异常 * fix: 优化物联网卡ui,远程升级ui * fix: 修改告警记录ui * feat: 告警记录ui修改 * feat: 修改日历维护ui * feat: 远程升级ui * feat: 物联网卡ui * fix: bug#27530IEC104采集器,在点位中设置点位死区“百分比”时,过滤异常 * fix: 日历事件新增tooltip * fix: bug#27521新增BACNet设备实例号只输入正整数 * fix: bug#27534IEC104采集批量导入,文案没有实时更新 * fix: bug#27530IEC104采集器,在点位中设置点位死区“百分比”时,过滤异常 * fix: bug#27688设备导出不选择产品,未导出全部设备 * feat: 视频设备新增配置是否支持录像和云台控制功能 * fix: bug#27696当阿里云关联付费实例时,无法绑定成功,界面报错 * fix: 屏蔽bug27696修改代码 * fix: bug#27696当阿里云关联付费实例时,无法绑定成功,界面报错,服务器异常 * fix: bug#27701MODBUS_TCP点位,配置中寄存器过大时,卡片中显示优化 * fix: bug#27543视频设备>>>插件视频,无法进行录制(本地和云端都不行) * fix: 控制录像显隐 * fix: bug#27618 OPC_UA点位进行批量操作-编辑时,只推送变化的数据在不进行选择时,是否对当前点位的数据进行修改 * fix: 日历事件新增tooltip * fix: bug#27521新增BACNet设备实例号只输入正整数 * fix: bug#27534IEC104采集批量导入,文案没有实时更新 * fix: bug#27530IEC104采集器,在点位中设置点位死区“百分比”时,过滤异常 * fix: bug#27688设备导出不选择产品,未导出全部设备 * feat: 视频设备新增配置是否支持录像和云台控制功能 * fix: bug#27696当阿里云关联付费实例时,无法绑定成功,界面报错 * fix: 屏蔽bug27696修改代码 * fix: bug#27696当阿里云关联付费实例时,无法绑定成功,界面报错,服务器异常 * fix: bug#27701MODBUS_TCP点位,配置中寄存器过大时,卡片中显示优化 * fix: bug#27543视频设备>>>插件视频,无法进行录制(本地和云端都不行) * fix: 控制录像显隐 * fix: bug#27618 OPC_UA点位进行批量操作-编辑时,只推送变化的数据在不进行选择时,是否对当前点位的数据进行修改 * fix: bug#27741产品(设备)快速导入时,复制的其它产品物模型TSL在另外一个产品导入时,无法导入成功 * fix: 远程升级ui * fix: bug#27698修复设备标签-地图无法正确回显点位;修复视频设备通道管理无法修改厂商;优化场景联动-触发条件提示语 * fix: bug#27350修复告警配置查看关联的场景联动显示异常 * fix: bug#27053修复场景联动名称没有长度限制 * fix: bug#27693修复告警配置-定时触发界面展示错误 * fix: bug#27432修复告警配置-过滤执行动作中没有关联告警的场景联动 * fix: 优化告警配置-场景联动交互 * fix: bug#27071修复场景联动-触发告警动作删除,同步删除关联告警 * fix: 优化场景联动-触发规则弹窗宽度 * fix: bug#27051修复场景联动条件删除提示 * fix: 修改ICCID查询条件 * fix: 优化物模型条件数组输入框 * fix: bug#27542优化物模型条件数组输入框 * fix: bug#27580修复地区管理下级区域没有地图边界 * fix: bug#27595修复地区管理无法拖拽操作 * fix: bug#27189优化场景联动-手动触发绑定告警查询参数 * fix: bug#27090优化场景联动-触发条件提示语 * fix: bug#27755修复视频设备通道管理无法修改厂商 * fix: bug#27758修复视频播放界面样式 * fix: bug#27698修复设备标签-地图无法正确回显点位 * fix: bug#27742告警记录>>>设备tab页进行搜索,搜索结果展示错误;删除采集器后,右侧该采集器下的点位仍然显示在页面 * fix: bug#26613当认证配置填入为数值类型时,填写完成后保存,再次查看未回显 * fix: bug#26399规则编排列表模式下查看,界面展示需优化 * fix: bug#26527视频中心仪表盘,进行跨年搜索时,界面展示需优化 * fix: bug#26423物联卡详情界面查看,平台类型和运营商显示错误 * fix: bug#26542边缘网关>>>网关设备>>>快速,接入网关选择鼠标放上去后,没有浮窗展示省略的内容 * fix: bug#27822删除采集器后,右侧该采集器下的点位仍然显示在页面 * fix: bug#27742告警记录>>>设备tab页进行搜索,搜索结果展示错误 * fix: bug#27837修复场景联动属性为array,输入值校验无法通过;【地区管理】修复无法把下级区域拖动到上一级 * fix: bug#26716修复场景联动-执行动作-通知方式样式 * fix: bug#27042修复场景联动-多条件保存校验无错误提示 * fix: bug#27744修复物模型-规则编排标签多出的按钮 * fix: bug#27756修复视频设备-无法清除用户名和密码 * fix: 修复地区管理下级区域无法显示范围 * fix: 修复地区管理无法拖拽 * fix: bug#27740修复物模型-array类型元素类型为enum无校验提示 * fix: bug#27832屏蔽视频分享 * fix: bug#27837修复场景联动属性为array,输入值校验无法通过 * fix: bug#27848修复设备功能样式错位 * fix: bug#27850【地区管理】修复新增勾选下一级区域无法新增 * fix: bug#27852【地区管理】修复无法把下级区域拖动到上一级 * feat: 2.2版本运行状态优化 * feat: 运行状态2.2 * feat: 设备运行状态列表高级搜索 * feat: 新增列表原始值查询 * feat: 2.2版本运行状态优化 * feat: 删除冗余代码 * fix: bug#18167应用管理>>>第三方应用>>>API服务,seucreKey,提高密码强度 Co-authored-by: leiqiaochu <leiqiaochu@aossci.com> * fix: bug#2785720【告警配置】优化绑定场景联动详情展示防抖配置 * fix: bug#27100【场景联动】执行动作,功能执行内置参数添加columns值 * fix: bug#27579【地区管理】修复编辑弹窗关闭后,地图区域消失 * fix: bug#27714【告警配置】关联场景联动样式优化 * fix: bug#27859【地区管理】修复拖拽区域后排序错乱 * fix: bug#27859【地区管理】修复切换路由页面异常 * fix: bug#27859【地区管理】优化自定义区域自适应地图可视范围 * fix: bug#27851【场景联动】修改设备功能时间类型为时间插件 * fix: bug#2785720【告警配置】优化绑定场景联动详情展示防抖配置 * feat: 新增国际化配置 * fix: bug#27891 物联卡进行运营商搜索时,无法搜索出正确结果 * fix: bug#27992【物联卡管理】ctwingCmp平台类型在停机后,运营商状态后面感叹号无提示;【设备】物联网卡绑定设备后,设备详情中上方多了导航栏 * fix: bug#27911编辑标签弹框文案和标签颜色优化 * fix: bug#27920新增onvif页,部分输入框红色提示语错误 bug#27919新增onvif页,部分输入框内无说明文案,需优化 * fix: bug#27928onvif通道列表页,可点击新增通道 * fix: bug#27924新增onvif页,所属产品下拉框没有默认选中产品 * fix: bug#27931插件视频设备通道列表-搜索下拉框多了“厂商” bug#27932插件视频设备通道列表-新增通道弹框缺少“视频地址”输入框 * fix: bug#27934插件视频设备通道列表-编辑通道展示内容需要同新增一致 * fix: bug#27918onvif查看通道页页面布局与需求不一致 * fix: bug#27935插件视频设备通道列表回放-本地下载到云端后,下载按钮已经变成了查看按钮,文案优化 * fix: bug#27945详情页,图表下方错误显示暂无数据图标 * fix: bug#27879【采集器】colector_gateway协议-批量导入的下载模板报错 * fix: bug#27938产品(设备)导入物模型TSL导出的json文件时,无法导入成功(复制json进行导入时,可以导入,但是界面有错误提示) * fix: bug#27980升级任务-任务详情 列表中状态的展示文案与需求不符 * fix: 删除远程升级老版代码 * fix: bug#数采gateway协议兼容导入数据 * fix: bug#27995【设备】物联网卡绑定设备后,设备详情中上方多了导航栏 * fix: bug#27992【物联卡管理】ctwingCmp平台类型在停机后,运营商状态后面感叹号无提示 * fix: bug#27926修复物模型规则属性弹窗不展示读写类型;bug#27940优化GeoJson自适应显示范围;bug#27978优化告警配置-关联场景联动卡片样式 * fix: bug#27926修复物模型规则属性弹窗不展示读写类型 * fix: 物模型-编辑规则代码优化 * fix: bug#27944优化上传GeoJson提示语 * fix: bug#27940优化GeoJson自适应显示范围 * fix: 【地区管理】优化GeoJSON销毁函数 * fix: bug#27976 告警配置卡片优化 * fix: bug#27978优化告警配置-关联场景联动卡片样式 * fix: bug#28027【日历维护】新增标签,红色必填文案优化;【采集器】当opc_ua和BacNet新增点位时,可以进行批量添加 * fix: 通道管理查询筛选值 * fix: bug#28027【日历维护】新增标签,红色必填文案优化 * fix: bug#27911【日历维护】编辑标签弹框文案和标签颜色优化 * fix: bug#国标级联解绑通道选择部分通道导致当页全部解绑bug * fix: bug#27889【采集器】当opc_ua和BacNet新增点位时,可以进行批量添加 * fix: bug#27931【视频设备】插件视频设备通道列表-搜索下拉框多了“厂商” * fix: bug运营商状态查询优化 * fix: bug#27887修复告警配置条件回显异常;修复数据字典内置数据操作限制;修复场景联动-无法删除执行动作;修复物模型-编辑规则,无法选择属性 * fix: 优化按需引入配置 * fix: bug#27887修复告警配置条件回显异常 * fix: bug#27913修复物模型滚动事件异常导致枚举数量显示不对 * fix: bug#27974修复数据字典内置数据操作限制 * feat: bug#28012场景联动-编辑执行动作新增校验 * fix: bug#28015修复场景联动-设备触发无法选择日历 * fix: bug#28030修复场景联动-无法删除执行动作 * fix: bug#28057修复物模型-编辑规则,无法选择属性 * bug#28051修复初始化数据没有个人中心菜单;优化个人中心菜单展示;修复场景联动-手动触发,执行动作没有按”标签“ * fix: bug#28051修复初始化数据没有个人中心菜单;优化个人中心菜单展示 * fix: bug#28034修复场景联动-手动触发,执行动作没有按”标签“ * fix: bug#28058优化视频通道左侧树没有数据时隐藏 * feat: 优化二次确认请求逻辑 * feat: 二次确认交互修改 * feat: 修改二次确认组件,替换popConfirm * feat: 修改二次确认请求 * feat: 请求中loading逻辑修改 * feat: 当被删除设备的网关类型为Ctwing设备接入时,提醒弹窗内容与其他设备有区别 * feat: 优化二次弹窗 * feat: 优化二次确认弹窗逻辑 * feat: 优化二次弹窗逻辑 * feat: 优化二次确认请求逻辑 * fix: 优化日历维护权限问题 * fix: 优化日历维护 * fix: 优化日历维护权限问题 * fix: 修改错误请求 * feat: 优化远程升级ui;优化二次确认弹窗 * feat: 产品和设备详情页新增远程升级页面tab * feat: 修改设备远程升级过滤逻辑 * feat: 升级任务详情新增设备版本列 * feat: 优化二次确认弹窗 * feat: 优化远程升级ui * fix: 优化物模型复制显示;优化物模型hooks代码;优化场景联动执行动作icon;优化物模型分组新增逻辑;优化全局样式 * fix: 物模型优化复制显示;优化物模型hooks代码 * fix: 优化场景联动执行动作icon * remove: 删除多余文件 * fix: 优化物模型分组新增逻辑 * fix: 优化全局样式 * fix: 修复Ctwing和OneNet保存时transport传值为HTTP;物模型列表新增筛选 * fix: 修复Ctwing和OneNet保存时transport传值为HTTP * feat: 物模型列表新增筛选 * fix: bug#28077视频设备查看通道,通道新增按钮展示需优化;【Dueros】动作映射-指令类型为修改属性,参数类型为date时,重新点击指令类型下拉框显示异常;【组织管理】资产解绑二次确认弹框和组织删除二次确认弹框文案优化 * fix: bug#28077视频设备查看通道,通道新增按钮展示需优化 * fix: bug#28088【产品】产品查看详情,选择物模型映射页面后切换到产品列表页面,再次点击没有物模型映射的产品查看详情,没有物模型映射tab的也展示了物模型映射 * fix: bug#28102 选择API后点击第二次保存时提示错误 * fix: bug#28119【Dueros】动作映射-指令类型为修改属性,参数类型为date时,重新点击指令类型下拉框显示异常 * fix: bug#28118【Dueros】动作映射-指令类型为调用功能,参数列表值输入框与类型不一致 * fix: bug#28121 【远程升级】远程升级>>>升级任务,任务详情列表展示当前设备版本 * fix: bug#28169【产品】产品、设备、远程升级,查看升级任务,界面展示需优化 * fix: bug#28174【产品】产品查看远程升级界面展示需优化(设备界面也需要优化) * fix: bug#28173【组织管理】资产解绑二次确认弹框和组织删除二次确认弹框文案优化 * fix: bug#28172启用/禁用二次确认弹框提示语优化 * fix: 列表模式下操作栏间距优化 * fix: bug#28165【通道管理】 正常状态通道删除按钮提示语优化 * fix: bug#28164【远程升级】设备拉取任务的任务详情中,[全部暂停]按钮的逻辑错误 * fix: bug#28156【网络组件】列表形式,删除按钮超出操作列 * fix: bug#28155【设备】设备诊断tab-【忽略】二次弹框优化 * fix: bug#28124【采集器】modbus_tcp新增点位失败 * fix: bug#28161【告警配置】删除二次弹框,缺少图标 * feat: 优化北向输出样式 * feat: 北向输出交互修改 * feat: 优化北向输出样式 * feat: 修改视频设备概要抽屉样式 * feat: 视频设备点击概要说明抽屉 * feat: “用户名”列中的数据添加背景色,原判断规则不变(仍支持中文) * feat: 优化用户系统ui * feat: 修改视频设备概要抽屉样式 * feat: 采集器Bacnet写入新增写入优先级表单项 * feat: 修改场景联动执行动作选择方式显示逻辑 * feat: 规则引擎-场景联动-设备触发 * feat: 采集器Bacnet写入新增写入优先级表单项 * feat: 新增物模型-属性导入 * feat: 新增物模型-属性导入 * feat: 设备接入网关详情 * feat: 设备接入网关详情 * fix: bug#28230修复场景联动-执行动作-设备无法选择标签;优化设备映射查询接口;修改物模型导入接口以及相关处理逻辑 * fix: bug#28119修复zIndex导致布局错乱 * fix: bug#28230修复场景联动-执行动作-设备无法选择标签 * fix: 优化设备映射查询接口 * fix: 修改物模型导入接口以及相关处理逻辑 * fix: bug#24828修复插件设备ID映射不回显已映射的设备;修复场景联动触发规则选择自定义日历未触发;修复通知管理-通知记录回显之前输入的筛选值 * fix: bug#24828修复插件设备ID映射不回显已映射的设备 * fix: bug#24062修复场景联动触发规则选择自定义日历未触发 * fix: bug#24085优化菜单配置,保存后刷新页面 * fix: bug#28113 修复通知管理-通知记录回显之前输入的筛选值 * fix: bug#28243修复场景联动设备触发默认条件名称显示错误 * fix: bug#28233修复场景联动执行动作-设备触发-首个执行动作不可选择 * fix: bug#28248修复物模型属性-array数据类型回显异常 * fix: bug#28250 优化物模型全屏退出导致popover无法关闭 * fix: bug#28234 优化物模型复制提示显示数值错误 * fix: bug#28239 修复物模型保存时,校验成功后,异常提示还存在 * fix: bug#28196【阿里云】新增阿里云时,名称输入框回显了上一次选中阿里云的名称;【数据源管理】管理页面中左侧新增后,进行切换到存在字段的表后,在切换到新增的表,右侧页面未同步刷新;【设备接入网关】设备接入网关,当说明内容过长,查看详情时,界面展示需优化;【产品】产品导入物模型时,导入失败后,界面提示语需优化 * fix: bug#28196【阿里云】新增阿里云时,名称输入框回显了上一次选中阿里云的名称 * fix: bug#28197新增阿里云时,触发了必填校验,切换到其他阿里云数据时,必填红色文案仍然显示 * fix: bug#28195【阿里云】所选择的平台产品被禁用时,页面上方没有展示横幅 * fix: bug#28194【DuerOS】左侧数据进行启/禁用时按钮文案未进行更新 * fix: bug#28193【阿里云】搜索的数据不存在时,页面右侧展示优化 * fix: bug#28192【DuerOS】数据名称过长时,左侧展示异常 * fix: bug#28191【DuerOS】启用状态的数据,应不能进行删除 * fix: bug#28186【规则编排】启用二次确认弹框文案错误 * fix: bug#28203 【设备接入网关】创建onvif设备接入网关时,右侧说明内容错误 * fix: bug#28204【仪表盘】昨日流量统计时间统计错误,统计成了今日 * fix: bug#28187 【采集器】数据类型为16进制时,进行输入数据解密展示异常 * fix: bug#28208【视频设备】视频设备展示接入方式同原型不一致 * fix: bug#28209【视频设备】视频设备查看详情,接入密码展示同需求不符 * fix: bug#28206【插件管理】进行新增时,上传插件后,下发还提示的“请上传文件” * fix: bug#28164 全部暂停时状态文案显示错误多了0% * fix: bug#28158【设备】多次点击删除按钮,没有弹出弹框 * fix: bug#28146设备运行状态>>>详情查看,图标搜索展示结果错误(ClickHouse-行式存储) * fix: 优化运行状态图表模式时间轴取值问题 * fix: 产品设备接入网关物模型兼容问题 * fix: bug#28205【视频设备】视频设备,选择接入方式为插件时,快速新增产品失败 * fix: bug#28033 【远程升级】设备拉取中已升级设备进行二次添加到升级中时,列表的状态显示优化 * fix: bug#11092 【仪表盘】功能缺失同需求不符 * fix: bug#28092 【数据源管理】管理页面中左侧新增后,进行切换到存在字段的表后,在切换到新增的表,右侧页面未同步刷新 * fix: bug#28252【设备接入网关】设备接入网关,当说明内容过长,查看详情时,界面展示需优化 * fix: bug#28255【采集器】新增S7协议的点位时,字符串长度校验异常 * fix: bug#28089【产品】产品导入物模型时,导入失败后,界面提示语需优化 * feat: 新增物模型属性阈值 * feat: 物模型属性阈值 * feat: 物模型属性阈值限制功能 * feat: 阈值功能传值优化 * fix: bug#28187【采集器】数据类型为16进制时,进行输入数据解密展示异常;【插件管理】进行新增时,上传插件后,下发还提示的“请上传文件”;【设备】物模型-属性定义-其他配置,不填写任何配置项,点击确认,页面报错 * fix: bug#28187【采集器】数据类型为16进制时,进行输入数据解密展示异常 * fix: bug#28317、28308、28307、28306、28305、28303 * fix: bug#28206 【插件管理】进行新增时,上传插件后,下发还提示的“请上传文件” * feat: 新增新Onenet * fix: bug#28331【设备】物模型-属性定义-其他配置,不填写任何配置项,点击确认,页面报错 * fix: 产品设备告警详情删除原始值项 * feat: 【场景联动】执行动作(告警)-过滤条件新增告警下拉列表 * fix: 修复物模型属性导入请求取消异常 * fix: 修复场景联动执行动作-设备触发无法选择设置属性 * feat: 【场景联动】执行动作(告警)-过滤条件新增告警下拉列表 * fix: 【场景联动】优化执行动作(告警)- 过滤条件告警下拉列表无法隐藏;修复可编辑表格虚拟滚动逻辑 * fix: 【场景联动】优化执行动作(告警)- 过滤条件告警下拉列表无法隐藏 * fix: 【告警配置】-关联场景联动-优化卡片样式 * fix: 修复可编辑表格虚拟滚动逻辑 * fix: bug#27692 【采集器】采集器的点位数据数组类型时,写入数据不能进行写入数组数据;物模型-属性定义-其他配置-拓展配置,处理方式选择“记录”或“告警”点击确认无响应 * fix: bug#28350【告警记录】告警记录列表中,处理类型缺失,导致列表展示内容错位(系统处理的缺少类型导致) * fix: bacnet批量添加 * fix: bug#28365 物模型-属性定义-其他配置-拓展配置,处理方式选择“记录”或“告警”点击确认无响应 * fix: bug#27692 【采集器】采集器的点位数据数组类型时,写入数据不能进行写入数组数据 * fix: bug#27974、28378、28380【数据字典】内置的数据字典应不能进行编辑,删除,禁用 * fix: 修改详情告警无效数据接口 * fix: bug#28368、28369、28317仪表盘时间轴格式 * fix: bug#27974、28378、28380【数据字典】内置的数据字典应不能进行编辑,删除,禁用 * fix: bug#28399物模型-属性定义-其他配置-指标配置,删除操作无响应,点击确认按钮也无响应;【采集器】采集器可以扫描后批量选择点位 * fix: bug#28387、28392、28393 * fix: bug#28399物模型-属性定义-其他配置-指标配置,删除操作无响应,点击确认按钮也无响应 * fix: bug#28026 【采集器】采集器可以扫描后批量选择点位 * fix: bug#28403产品(设备)页面中的告警记录展示需要有告警记录(告警中心)查询权限,告警记录处理按钮需要有告警记录(告警中心)中的操作权限 * fix: bug#28413、28415【应用管理】编辑内部集成应用,清空接入地址也能校验成功、应用类型字段为空 * fix: bug#28409 【应用管理】启用或禁用时,整个页面会进行刷新操作,使用感不太好 * fix: bug#28440 【证书管理】证书管理上传对应的证书文件后,依然后错误提示;【设备接入网关】新的OneNet网关查看详情中无数据 * fix: bug#28403、28423、28427 * fix: bug#物模型属性定义其他配置,阈值限制不展开渲染导致校验逻辑不通过,确定按钮没有效果的bug * fix: bug#28422 【设备接入网关】新的OneNet网关查看详情中无数据 * fix: bug#28440 【证书管理】证书管理上传对应的证书文件后,依然后错误提示 * update: 替换播放器插件为xgplayer * fix: 【场景联动】-过滤条件优化告警下拉选择 * fix: 【场景联动】修复执行动作-标签没有下拉数据 * fix: 【告警配置】优化卡片数据展示 * update: 替换播放器插件为xgplayer * update: 更新相关播放器逻辑 * fix: bug#28443、284444 * fix: bug 修改预处理数据相关bug * fix: bug#28443、284444 * fix: 修复物模型-规则编辑开始运行参数添加类型 * fix: 更换全局AlarmLevelIcon组件 * fix: bug#28434 修复编辑未绘制范围也能进行保存 * fix: 优化物模型-规则编辑ts类型 * fix: 修复物模型-规则编辑开始运行参数添加类型 * fix: 更新monaco-editor版本,修复高亮报错异常 * fix: 修改2.2版本ui * fix: 优化场景联动、告警配置ui * fix: 更新可编辑表格右键菜单 * fix: 优化场景联动、告警配置ui * fix: 【场景联动】兼容上传物模型没有expands属性 * fix: 告警配置搜索条件优化;【设备】预处理数据-告警数据,列表排序错乱 * fix: 修改2.2版本bug和ui * fix: bug#28462【产品】预处理数据-告警数据-告警日志 下方的文案需进行优化 * fix: bug#28464【设备】预处理数据-告警数据,列表排序错乱 * fix: 告警配置搜索条件优化 * fix: 隐藏告警配置场景联动列表项 Co-authored-by: leiqiaochu <leiqiaochu@aossci.com> * fix: 展示设备和产品详情告警日志的告警等级 * fix: 优化全局modal样式 * fix: 【设备管理】优化仪表盘x轴时间格式 * fix: 优化全局滚动条样式 * fix: 【场景联动】优化执行动作-选择方式样式 * fix: 优化全局modal样式 * fix: 修改设备和产品详情告警查询接口 * fix: 【场景联动】优化条件按钮颜色 * fix: bug#28467、28466 * fix: 修改设备和产品详情告警查询接口 * fix: 修改预处理数据ui样式 * fix: bug#28467、28466 * fix: bug#27001【远程升级】升级进度展示数据与需求不符 * fix: bug#28471【产品】拓展配置处理方式说明文案优化 * release: 2.2.0 * update: 更新docker版本号
This commit is contained in:
parent
a28ac3bc85
commit
0beea4d461
|
@ -15,6 +15,8 @@ module.exports = {
|
||||||
'style', // 格式(不影响代码变动)
|
'style', // 格式(不影响代码变动)
|
||||||
'revert', // 撤销commit 回滚上一版本
|
'revert', // 撤销commit 回滚上一版本
|
||||||
'perf', // 性能优化
|
'perf', // 性能优化
|
||||||
|
'remove', //删除
|
||||||
|
'release', //
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'scope-case': [0],
|
'scope-case': [0],
|
||||||
|
@ -24,7 +26,7 @@ module.exports = {
|
||||||
rules: {
|
rules: {
|
||||||
"commit-rule": ({ raw }) => {
|
"commit-rule": ({ raw }) => {
|
||||||
return [
|
return [
|
||||||
/^\[(build|feat|fix|update|refactor|docs|chore|style|revert|perf)].+/g.test(raw),
|
/^\[(build|feat|fix|update|refactor|docs|chore|style|revert|perf|remove|release)].+/g.test(raw),
|
||||||
`commit备注信息格式错误,格式为 <[type] 修改内容>,type支持${types.join(",")}`
|
`commit备注信息格式错误,格式为 <[type] 修改内容>,type支持${types.join(",")}`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,3 +25,4 @@ components.d.ts
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.history
|
.history
|
||||||
|
du-i18n.config.json
|
||||||
|
|
4
build.sh
4
build.sh
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.2.0-SNAPSHOT .
|
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.3.0-SNAPSHOT .
|
||||||
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.2.0-SNAPSHOT
|
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.3.0-SNAPSHOT
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
theme: {
|
theme: {
|
||||||
'primary-color': '#1d39c4',
|
'primary-color': '#1677FF',
|
||||||
},
|
},
|
||||||
logo: '/favicon.ico', // 浏览器标签页logo
|
logo: '/favicon.ico', // 浏览器标签页logo
|
||||||
title: 'Jetlinks', // 浏览器标签页title
|
title: 'Jetlinks', // 浏览器标签页title
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"quoteKeys": [
|
||||||
|
"$t",
|
||||||
|
"$t",
|
||||||
|
"i18n.t"
|
||||||
|
],
|
||||||
|
"defaultLang": "zh",
|
||||||
|
"tempLangs": [
|
||||||
|
"zh",
|
||||||
|
"en"
|
||||||
|
],
|
||||||
|
"langPaths": "**/src/i18n/locale/**",
|
||||||
|
"transSourcePaths": "**/src/i18n/source/**",
|
||||||
|
"tempPaths": "**/src/i18n/temp/**",
|
||||||
|
"tempFileName": "",
|
||||||
|
"multiFolders": [
|
||||||
|
"src",
|
||||||
|
"views"
|
||||||
|
],
|
||||||
|
"uncheckMissKeys": [],
|
||||||
|
"isSingleQuote": true,
|
||||||
|
"isOnlineTrans": true,
|
||||||
|
"baiduAppid": "20240704002091621",
|
||||||
|
"baiduSecrectKey": "CQdjuZ1v8AaZtx1NQQsW"
|
||||||
|
}
|
24
package.json
24
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "jetlinks-vue",
|
"name": "jetlinks-vue",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "2.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --mode develop",
|
"dev": "vite --mode develop",
|
||||||
"dev:force": "vite --force --mode develop",
|
"dev:force": "vite --force --mode develop",
|
||||||
|
@ -14,19 +14,27 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
|
"@fullcalendar/core": "^6.1.13",
|
||||||
|
"@fullcalendar/daygrid": "^6.1.13",
|
||||||
|
"@fullcalendar/interaction": "^6.1.13",
|
||||||
|
"@fullcalendar/vue3": "^6.1.13",
|
||||||
"@liveqing/liveplayer-v3": "^3.7.10",
|
"@liveqing/liveplayer-v3": "^3.7.10",
|
||||||
|
"@types/axios": "^0.14.0",
|
||||||
"@types/marked": "^4.0.8",
|
"@types/marked": "^4.0.8",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||||
"@vuemap/vue-amap": "^1.1.20",
|
"@vuemap/vue-amap": "^2.1.2",
|
||||||
"@vueuse/core": "^9.10.0",
|
"@vueuse/core": "^9.10.0",
|
||||||
"ant-design-vue": "^3.2.15",
|
"ant-design-vue": "^3.2.15",
|
||||||
|
"async-validator": "^4.2.5",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.2.1",
|
||||||
|
"colorpicker-v3": "^2.10.2",
|
||||||
|
"cronstrue": "^2.50.0",
|
||||||
"driver.js": "^0.9.8",
|
"driver.js": "^0.9.8",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.4.1",
|
||||||
"event-source-polyfill": "^1.0.31",
|
"event-source-polyfill": "^1.0.31",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0",
|
||||||
"jetlinks-store": "^0.0.3",
|
"jetlinks-store": "^0.0.3",
|
||||||
"jetlinks-ui-components": "^1.0.38",
|
"jetlinks-ui-components": "^1.0.47",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"less-loader": "^11.1.0",
|
"less-loader": "^11.1.0",
|
||||||
|
@ -46,9 +54,10 @@
|
||||||
"markdown-it-toc-done-right": "^4.2.0",
|
"markdown-it-toc-done-right": "^4.2.0",
|
||||||
"marked": "^4.2.12",
|
"marked": "^4.2.12",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"monaco-editor": "^0.36.0",
|
"monaco-editor": "^0.50.0",
|
||||||
"nrm": "^1.2.5",
|
"nrm": "^1.2.5",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"rollup-plugin-copy": "^3.4.0",
|
"rollup-plugin-copy": "^3.4.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"unplugin-auto-import": "^0.12.1",
|
"unplugin-auto-import": "^0.12.1",
|
||||||
|
@ -57,10 +66,15 @@
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vue": "3.3.4",
|
"vue": "3.3.4",
|
||||||
"vue-cropper": "^1.0.9",
|
"vue-cropper": "^1.0.9",
|
||||||
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-json-viewer": "^3.0.4",
|
"vue-json-viewer": "^3.0.4",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vue3-json-viewer": "^2.2.2",
|
"vue3-json-viewer": "^2.2.2",
|
||||||
"vue3-ts-jsoneditor": "^2.7.1"
|
"vue3-ts-jsoneditor": "^2.7.1",
|
||||||
|
"xgplayer": "^3.0.19",
|
||||||
|
"xgplayer-flv": "^3.0.20-beta.0",
|
||||||
|
"xgplayer-hls": "^3.0.19",
|
||||||
|
"xgplayer-hls.js": "2.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.4.1",
|
"@commitlint/cli": "^17.4.1",
|
||||||
|
|
|
@ -295,7 +295,6 @@ function getSideEffects(compName: string, options: JetlinksVueResolverOptions, _
|
||||||
return
|
return
|
||||||
const lib = options.cjs ? 'lib' : 'es'
|
const lib = options.cjs ? 'lib' : 'es'
|
||||||
const packageName = options?.packageName || 'jetlinks-ui-components'
|
const packageName = options?.packageName || 'jetlinks-ui-components'
|
||||||
|
|
||||||
if (importStyle === 'less' || importLess) {
|
if (importStyle === 'less' || importLess) {
|
||||||
const styleDir = getStyleDir(compName, _isAntd)
|
const styleDir = getStyleDir(compName, _isAntd)
|
||||||
return `${packageName}/${lib}/${styleDir}/style`
|
return `${packageName}/${lib}/${styleDir}/style`
|
||||||
|
@ -319,8 +318,25 @@ const primitiveNames = ['AIcon','Affix', 'Anchor', 'AnchorLink', 'message', 'Not
|
||||||
'DataTableObject',
|
'DataTableObject',
|
||||||
'CheckButton',
|
'CheckButton',
|
||||||
]
|
]
|
||||||
|
|
||||||
const prefix = 'J'
|
const prefix = 'J'
|
||||||
|
|
||||||
|
const proComponents = [
|
||||||
|
'ProTable', 'Search', 'AdvancedSearch', 'Ellipsis', 'MonacoEditor', 'ProLayout', 'ScrollTable', 'TableCard', 'Scrollbar', 'CardSelect', 'PopconfirmModal', 'DataTable',
|
||||||
|
'DataTableArray',
|
||||||
|
'DataTableString',
|
||||||
|
'DataTableInteger',
|
||||||
|
'DataTableDouble',
|
||||||
|
'DataTableBoolean',
|
||||||
|
'DataTableEnum',
|
||||||
|
'DataTableFile',
|
||||||
|
'DataTableDate',
|
||||||
|
'DataTableTypeSelect',
|
||||||
|
'DataTableObject',
|
||||||
|
'CheckButton',
|
||||||
|
'ValueItem'
|
||||||
|
]
|
||||||
|
|
||||||
let jetlinksNames: Set<string>
|
let jetlinksNames: Set<string>
|
||||||
|
|
||||||
function genJetlinksNames(primitiveNames: string[]): void {
|
function genJetlinksNames(primitiveNames: string[]): void {
|
||||||
|
@ -356,11 +372,20 @@ export function JetlinksVueResolver(options: JetlinksVueResolverOptions = {}): a
|
||||||
}
|
}
|
||||||
const _isJetlinks = isJetlinks(name)
|
const _isJetlinks = isJetlinks(name)
|
||||||
const _isAntd = isAntdv(name)
|
const _isAntd = isAntdv(name)
|
||||||
|
|
||||||
if ((_isJetlinks || _isAntd) && !options?.exclude?.includes(name)) {
|
if ((_isJetlinks || _isAntd) && !options?.exclude?.includes(name)) {
|
||||||
|
// const importName = filterName.includes(name) ? name : name.slice(1)
|
||||||
|
//
|
||||||
|
// options.packageName = proComponents.includes(importName) ? 'jetlinks-ui-components' : 'ant-design-vue'
|
||||||
|
//
|
||||||
|
// const path = `${options.packageName}/${options.cjs ? 'lib' : 'es'}`
|
||||||
|
// const stylePath = getSideEffects(importName, options, !proComponents.includes(importName))
|
||||||
|
|
||||||
const importName = filterName.includes(name) ? name : name.slice(1)
|
const importName = filterName.includes(name) ? name : name.slice(1)
|
||||||
options.packageName = _isJetlinks ? 'jetlinks-ui-components' : 'ant-design-vue'
|
options.packageName = _isJetlinks ? 'jetlinks-ui-components' : 'ant-design-vue'
|
||||||
const path = `${options.packageName}/${options.cjs ? 'lib' : 'es'}`
|
const path = `${options.packageName}/${options.cjs ? 'lib' : 'es'}`
|
||||||
const stylePath = getSideEffects(importName, options, _isAntd)
|
const stylePath = getSideEffects(importName, options, _isAntd)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: importName,
|
name: importName,
|
||||||
from: path,
|
from: path,
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<ConfigProvider :locale='zhCN'>
|
<ConfigProvider
|
||||||
|
:locale='zhCN'
|
||||||
|
:IconConfig="{
|
||||||
|
scriptUrl: '//at.alicdn.com/t/c/font_4035907_i1jazcune3.js'
|
||||||
|
}"
|
||||||
|
>
|
||||||
<router-view />
|
<router-view />
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</template>
|
</template>
|
||||||
|
@ -9,7 +14,6 @@ import { ConfigProvider } from 'jetlinks-ui-components'
|
||||||
import zhCN from 'jetlinks-ui-components/es/locale/zh_CN';
|
import zhCN from 'jetlinks-ui-components/es/locale/zh_CN';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useSystem } from './store/system';
|
import { useSystem } from './store/system';
|
||||||
import DefaultSetting from '../config/config'
|
|
||||||
import {LocalStore} from "@/utils/comm";
|
import {LocalStore} from "@/utils/comm";
|
||||||
import {TOKEN_KEY} from "@/utils/variable";
|
import {TOKEN_KEY} from "@/utils/variable";
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { SearchHistoryList } from 'components/Search/types'
|
||||||
|
|
||||||
export const FILE_UPLOAD = `${BASE_API_PATH}/file/static`;
|
export const FILE_UPLOAD = `${BASE_API_PATH}/file/static`;
|
||||||
|
|
||||||
|
export const FileUpload = `${BASE_API_PATH}/file/upload`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存查询记录
|
* 保存查询记录
|
||||||
* @param data
|
* @param data
|
||||||
|
|
|
@ -83,5 +83,15 @@ export const getBacnetObjectList = (channelId: string, instanceNumber: string) =
|
||||||
*/
|
*/
|
||||||
export const getBacnetPropertyIdNotUse = (data: any) => server.post(`/collect/bacnet/${data.collectorId}/unused/ids`, data)
|
export const getBacnetPropertyIdNotUse = (data: any) => server.post(`/collect/bacnet/${data.collectorId}/unused/ids`, data)
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 查询所有属性id
|
||||||
|
// */
|
||||||
|
// export const getBacnetAllPropertyId = () => server.get('/collect/bacnet/property/ids')
|
||||||
|
|
||||||
/**查询bacnet值类型*/
|
/**查询bacnet值类型*/
|
||||||
export const getBacnetValueType = () => server.get(`/collect/bacnet/value/types`)
|
export const getBacnetValueType = () => server.get(`/collect/bacnet/value/types`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出点位数据
|
||||||
|
*/
|
||||||
|
export const exportPoint = (collectorId:string,provider:string) => server.get(`/data-collect/point/${collectorId}/${provider}/export.xlsx`, {}, {responseType: 'blob'})
|
|
@ -11,12 +11,16 @@ export const remove = (id: string) => server.remove(`/firmware/${id}`);
|
||||||
|
|
||||||
export const query = (data: object) => server.post(`/firmware/_query/`, data);
|
export const query = (data: object) => server.post(`/firmware/_query/`, data);
|
||||||
|
|
||||||
|
export const queryPaginateNot = (data: object) => server.post('/firmware/_query/no-paging',data)
|
||||||
|
|
||||||
export const querySystemApi = (data?: object) =>
|
export const querySystemApi = (data?: object) =>
|
||||||
server.post(`/system/config/scopes`, data);
|
server.post(`/system/config/scopes`, data);
|
||||||
|
|
||||||
export const task = (data: Record<string, unknown>) =>
|
export const task = (data: Record<string, unknown>) =>
|
||||||
server.post(`/firmware/upgrade/task/detail/_query`, data);
|
server.post(`/firmware/upgrade/task/detail/_query`, data);
|
||||||
|
|
||||||
|
export const queryTaskPaginateNot = (data:any)=> server.post('/firmware/upgrade/task/detail/_query/no-paging',data)
|
||||||
|
|
||||||
export const taskById = (id: string) =>
|
export const taskById = (id: string) =>
|
||||||
server.get(`/firmware/upgrade/task/${id}`);
|
server.get(`/firmware/upgrade/task/${id}`);
|
||||||
|
|
||||||
|
@ -29,6 +33,10 @@ export const deleteTask = (id: string) =>
|
||||||
export const history = (data: Record<string, unknown>) =>
|
export const history = (data: Record<string, unknown>) =>
|
||||||
server.post(`/firmware/upgrade/history/_query`, data);
|
server.post(`/firmware/upgrade/history/_query`, data);
|
||||||
|
|
||||||
|
export const historyPaginateNot =(data:Record<string,unknown>) =>
|
||||||
|
server.post('/firmware/upgrade/history/_query/no-paging',data)
|
||||||
|
|
||||||
|
|
||||||
export const historyCount = (data: Record<string, unknown>) =>
|
export const historyCount = (data: Record<string, unknown>) =>
|
||||||
server.post(`/firmware/upgrade/history/_count`, data);
|
server.post(`/firmware/upgrade/history/_count`, data);
|
||||||
|
|
||||||
|
@ -41,6 +49,8 @@ export const stopTask = (id: string) =>
|
||||||
export const startOneTask = (data: string[]) =>
|
export const startOneTask = (data: string[]) =>
|
||||||
server.post(`/firmware/upgrade/task/_start`, data);
|
server.post(`/firmware/upgrade/task/_start`, data);
|
||||||
|
|
||||||
|
export const stopOneTask = (data: string[]) =>
|
||||||
|
server.post('/firmware/upgrade/task/_stop',data)
|
||||||
// export const queryProduct = (data?: any) =>
|
// export const queryProduct = (data?: any) =>
|
||||||
// server.post(`/device-product/_query/no-paging`, data);
|
// server.post(`/device-product/_query/no-paging`, data);
|
||||||
export const queryProduct = (data?: any) =>
|
export const queryProduct = (data?: any) =>
|
||||||
|
|
|
@ -636,3 +636,50 @@ export const queryTypescript = (deviceId:string) => server.get(`/device/${device
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const queryProductTs = (productId:string) => server.get(`/product/${productId}/virtual-property.d.ts`)
|
export const queryProductTs = (productId:string) => server.get(`/product/${productId}/virtual-property.d.ts`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阈值限制-新增/修改-产品
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const updateProductThreshold = (productId:string,propertyId:string,data: any) => server.put(`/message/preprocessor/product/${productId}/property/${propertyId}`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阈值限制-新增/修改-设备
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const updateDeviceThreshold = (productId:string,deviceId:string,propertyId:string,data: any) => server.put(`/message/preprocessor/device/${productId}/${deviceId}/property/${propertyId}`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阈值限制-设备物模型阈值限制
|
||||||
|
* @param productId
|
||||||
|
* @param deviceId
|
||||||
|
* @param propertyId
|
||||||
|
*/
|
||||||
|
export const queryDeviceThreshold = (productId: string, deviceId: string, propertyId: string) => server.get(`/message/preprocessor/device/${productId}/${deviceId}/property/${propertyId}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阈值限制-产品物模型阈值限制
|
||||||
|
* @param productId
|
||||||
|
* @param propertyId
|
||||||
|
*/
|
||||||
|
export const queryProductThreshold = (productId: string, propertyId: string) => server.get(`/message/preprocessor/product/${productId}/property/${propertyId}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阈值限制-删除产品物模型的阈值
|
||||||
|
* @param productId
|
||||||
|
* @param propertyId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteProductThreshold = (productId:string,propertyId:string,data:any) => server.remove(`/message/preprocessor/product/${productId}/property/${propertyId}`,data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阈值限制-删除产品物模型的阈值
|
||||||
|
* @param productId
|
||||||
|
* @param propertyId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteDeviceThreshold = (productId:string,deviceId:string,propertyId:string,data:any) => server.remove(`/message/preprocessor/device/${productId}/${deviceId}/property/${propertyId}`,data)
|
||||||
|
|
||||||
|
export const getTemplate = (id: string, format: string) => `${BASE_API_PATH}/device/instance/${id}/property-metadata/template.${format}`
|
||||||
|
|
||||||
|
export const uploadAnalyzeMetadata = (data: any) => server.post('/device/instance/property-metadata/file/analyze', data)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { OperatorItem } from '@/components/FRuleEditor/Operator/typings'
|
import { OperatorItem } from '@/components/FRuleEditor/Operator/typings'
|
||||||
import server from '@/utils/request'
|
import server from '@/utils/request'
|
||||||
import { DeviceMetadata, ProductItem, DepartmentItem, MetadataType } from '@/views/device/Product/typings'
|
import { DeviceMetadata, ProductItem, DepartmentItem, MetadataType } from '@/views/device/Product/typings'
|
||||||
|
import {BASE_API_PATH} from "@/utils/variable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据条件查询产品(不带翻页)
|
* 根据条件查询产品(不带翻页)
|
||||||
|
@ -219,5 +220,7 @@ export const saveProductVirtualProperty = (productId: string, data: any[]) => se
|
||||||
|
|
||||||
export const queryProductVirtualProperty = (productId: string, propertyId: string) => server.get(`/virtual/property/product/${productId}/${propertyId}`)
|
export const queryProductVirtualProperty = (productId: string, propertyId: string) => server.get(`/virtual/property/product/${productId}/${propertyId}`)
|
||||||
|
|
||||||
|
export const getTemplate = (id: string, format: string) => `${BASE_API_PATH}/device/product/${id}/property-metadata/template.${format}`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,11 @@ import server from '@/utils/request'
|
||||||
*/
|
*/
|
||||||
export const query = (data: Record<string, any>) => server.post('/device/aliyun/bridge/_query', data)
|
export const query = (data: Record<string, any>) => server.post('/device/aliyun/bridge/_query', data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询数据不分页
|
||||||
|
*/
|
||||||
|
export const queryPaginateNot = (data: any) => server.post('/device/aliyun/bridge/_query/no-paging', data)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询产品列表
|
* 查询产品列表
|
||||||
* @param data
|
* @param data
|
||||||
|
|
|
@ -7,6 +7,11 @@ import server from '@/utils/request'
|
||||||
*/
|
*/
|
||||||
export const query = (data: Record<string, any>) => server.post('/dueros/product/_query', data)
|
export const query = (data: Record<string, any>) => server.post('/dueros/product/_query', data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询数据不分页
|
||||||
|
*/
|
||||||
|
export const queryPaginateNot = (data:any) => server.post('/dueros/product/_query/no-paging',data)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询产品列表
|
* 查询产品列表
|
||||||
* @param id
|
* @param id
|
||||||
|
|
|
@ -46,7 +46,15 @@ export const detail = (id:string) => server.get(`/alarm/config/${id}`);
|
||||||
|
|
||||||
export const unbindScene = (id:string,data:any) => server.post(`/alarm/rule/bind/${id}/_delete`,data);
|
export const unbindScene = (id:string,data:any) => server.post(`/alarm/rule/bind/${id}/_delete`,data);
|
||||||
|
|
||||||
|
export const unBindAlarm = (id: string, alarmId: string, data: any) => server.post(`/alarm/rule/bind/${alarmId}/${id}/_delete`, data)
|
||||||
|
export const unBindAlarmMultiple = (data: any) => server.post(`/alarm/rule/bind/_delete`, data)
|
||||||
/**
|
/**
|
||||||
* 保存关联场景
|
* 保存关联场景
|
||||||
*/
|
*/
|
||||||
export const bindScene = (data:any) => server.patch("/alarm/rule/bind",data)
|
export const bindScene = (data:any) => server.patch("/alarm/rule/bind",data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询关联的场景执行动作id
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const queryBindScene = (data:any) => server.post("/alarm/rule/bind/_query",data)
|
||||||
|
|
|
@ -25,6 +25,13 @@ export const getOrgList = (parmas?:any) => server.get('/organization/_query/no-p
|
||||||
*/
|
*/
|
||||||
export const query = (data:any) => server.post('/alarm/record/_query/',data);
|
export const query = (data:any) => server.post('/alarm/record/_query/',data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备产品专用查询
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryByDevice = (data:any) => server.post(`/alarm/record/device/_query`,data)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 告警处理
|
* 告警处理
|
||||||
*/
|
*/
|
||||||
|
@ -50,3 +57,13 @@ export const queryHistoryList = (data:any) => server.post('/alarm/history/_query
|
||||||
* 获取告警处理结果
|
* 获取告警处理结果
|
||||||
*/
|
*/
|
||||||
export const queryHandleHistory = (data:any) => server.post('/alarm/record/handle-history/_query',data);
|
export const queryHandleHistory = (data:any) => server.post('/alarm/record/handle-history/_query',data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取告警日志(新)
|
||||||
|
*/
|
||||||
|
export const queryLogList = (alarmConfigId:any,data:any) => server.post(`/alarm/history/${alarmConfigId}/_query`,data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询无效数据
|
||||||
|
*/
|
||||||
|
export const queryInvalidData = (data:any) => server.post('/message/preprocessor/invalid/_query',data)
|
||||||
|
|
|
@ -27,4 +27,7 @@ export const queryBuiltInParams = (data: any, params?: any) => server.post(`/sce
|
||||||
|
|
||||||
export const getParseTerm = (data: Record<string, any>) => server.post(`/scene/parse-term-column`, data)
|
export const getParseTerm = (data: Record<string, any>) => server.post(`/scene/parse-term-column`, data)
|
||||||
|
|
||||||
export const queryAlarmList = (data: Record<string, any>) => server.post(`/alarm/config/_query/`, data)
|
export const queryAlarmPage = (data: Record<string, any>) => server.post(`/alarm/config/_query`, data)
|
||||||
|
|
||||||
|
export const queryAlarmList = (data: Record<string, any>) => server.post(`/alarm/config/_query/no-paging`, data)
|
||||||
|
export const queryAlarmCount = (data: Record<string, any>) => server.post(`/alarm/config/_count`, data)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
//编辑标签
|
||||||
|
export const saveTag = (data:any) => server.patch('/calendar/tags',data)
|
||||||
|
//查询标签列表
|
||||||
|
export const queryTags = () => server.get('/calendar/tags')
|
||||||
|
//删除标签
|
||||||
|
export const deleteTags = (ids:any) => server.remove('/calendar/tags',{},{data:ids})
|
||||||
|
//保存标签颜色
|
||||||
|
export const saveTagsColor = (data:any) => server.post('/system/config/calendar-tag-color',data)
|
||||||
|
//查询标签颜色
|
||||||
|
export const getTagsColor = () => server.get('/system/config/calendar-tag-color');
|
||||||
|
//查询指定日期内的日历
|
||||||
|
export const queryEvents = (dateFrom:any,dateTo:any) => server.get(`/calendar/${dateFrom}/${dateTo}`)
|
||||||
|
//批量保存指定日期的日历
|
||||||
|
export const saveEvents = (data:any) => server.patch('/calendar',data)
|
||||||
|
//清空日历
|
||||||
|
export const clearAll = () => server.remove('/calendar/mine/_all')
|
|
@ -0,0 +1,116 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useMap } from './useMap'
|
||||||
|
import {pick} from "lodash-es";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'DistrictSearch'
|
||||||
|
})
|
||||||
|
|
||||||
|
const instance = useMap()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
subdistrict: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
extensions: {
|
||||||
|
type: String,
|
||||||
|
default: 'all'
|
||||||
|
},
|
||||||
|
level: {
|
||||||
|
type: String,
|
||||||
|
default: 'district'
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
styles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
adcode: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let district
|
||||||
|
let polygon
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
if (polygon && instance.$amapComponent) {
|
||||||
|
if (instance.$amapComponent.getLayers().length) {
|
||||||
|
instance.$amapComponent.remove(polygon)
|
||||||
|
}
|
||||||
|
polygon = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawBounds = (paths) => {
|
||||||
|
if (polygon && instance.$amapComponent?.remove) {
|
||||||
|
instance.$amapComponent.remove(polygon)
|
||||||
|
polygon = null
|
||||||
|
}
|
||||||
|
for (var i = 0; i < paths.length; i += 1) {//构造MultiPolygon的path
|
||||||
|
paths[i] = [paths[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _styles = Object.assign({
|
||||||
|
strokeWeight: 1,
|
||||||
|
path: paths,
|
||||||
|
fillOpacity: 0.25,
|
||||||
|
fillColor: '#80d8ff',
|
||||||
|
strokeColor: '#0091ea'
|
||||||
|
},props.styles)
|
||||||
|
|
||||||
|
polygon = new AMap.Polygon(_styles);
|
||||||
|
|
||||||
|
instance.$amapComponent.add(polygon)
|
||||||
|
|
||||||
|
if (props.view) {
|
||||||
|
instance.$amapComponent.setFitView(polygon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryDistrict = (code) => {
|
||||||
|
const opts = {
|
||||||
|
subdistrict: 0, //获取边界不需要返回下级行政区
|
||||||
|
extensions: 'all', //返回行政区边界坐标组等具体信息
|
||||||
|
level: 'district' //查询行政级别为 市
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = Object.assign(opts, pick(props, ['subdistrict', 'extensions', 'level']))
|
||||||
|
|
||||||
|
if (!district) {
|
||||||
|
district = new AMap.DistrictSearch(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!code) return
|
||||||
|
district.search(code, (status, result) => {
|
||||||
|
if (!result || !result.districtList || !result.districtList[0]) {
|
||||||
|
console.warn('请正确填写名称或更新其他名称');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const bounds = result.districtList[0].boundaries;
|
||||||
|
drawBounds(bounds)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.adcode, () =>{
|
||||||
|
queryDistrict(props.adcode)
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useMap } from './useMap'
|
||||||
|
import { max, min } from 'lodash-es'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'GeoJson'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
geo: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const instance = useMap()
|
||||||
|
|
||||||
|
let geoJsonLayer
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
if (geoJsonLayer && instance.$amapComponent) {
|
||||||
|
if (instance.$amapComponent.getLayers().length) {
|
||||||
|
instance.$amapComponent.remove(geoJsonLayer)
|
||||||
|
}
|
||||||
|
geoJsonLayer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const drawBounds = () => {
|
||||||
|
remove()
|
||||||
|
|
||||||
|
if (!props.geo) return
|
||||||
|
|
||||||
|
geoJsonLayer = new AMap.GeoJSON({
|
||||||
|
geoJSON: props.geo,
|
||||||
|
getPolygon: (geojson, lnglats) => {
|
||||||
|
return new AMap.Polygon({
|
||||||
|
path: lnglats,
|
||||||
|
fillOpacity: 0.25,// 面积越大透明度越高
|
||||||
|
strokeColor: '#0091ea',
|
||||||
|
fillColor: '#80d8ff'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
instance.$amapComponent.add(geoJsonLayer)
|
||||||
|
|
||||||
|
if (props.view) {
|
||||||
|
|
||||||
|
const points = props.geo.features.reduce((prev, next) => {
|
||||||
|
const coordinates = next.geometry.coordinates
|
||||||
|
prev.push(...coordinates[0])
|
||||||
|
return prev
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (points.length) {
|
||||||
|
const lngArr = points.map(lnglat => lnglat[0])
|
||||||
|
const latArr = points.map(lnglat => lnglat[1])
|
||||||
|
|
||||||
|
const maxLng = max(lngArr)
|
||||||
|
const maxLat = max(latArr)
|
||||||
|
const minLng = min(lngArr)
|
||||||
|
const minLat = min(latArr)
|
||||||
|
const southWest = new AMap.LngLat(maxLng, maxLat)
|
||||||
|
const northEast = new AMap.LngLat(minLng, minLat)
|
||||||
|
const bounds = new AMap.Bounds(southWest, northEast)
|
||||||
|
instance.$amapComponent.setBounds(bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
remove()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => JSON.stringify(props.geo), () =>{
|
||||||
|
drawBounds()
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
import AMap from './AMap.vue'
|
||||||
|
|
||||||
|
export * from './useMap'
|
||||||
|
export { default as DistrictSearch } from './DistrictSearch.vue'
|
||||||
|
export { default as GeoJson } from './GeoJson.vue'
|
||||||
|
|
||||||
|
export default AMap
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const useMap = () => {
|
||||||
|
return inject('parentInstance')
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<img :src="src" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'LevelIcon'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
level: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const src = computed(() => {
|
||||||
|
return `/images/alarm/alarm${props.level}.png`
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -5,12 +5,6 @@ const color = {
|
||||||
'success': '36, 178, 118',
|
'success': '36, 178, 118',
|
||||||
'warning': '255, 144, 0',
|
'warning': '255, 144, 0',
|
||||||
'default': '102, 102, 102',
|
'default': '102, 102, 102',
|
||||||
//告警颜色
|
|
||||||
'level1': '229, 0, 18',
|
|
||||||
'level2': '255, 148, 87',
|
|
||||||
'level3': '250, 189, 71',
|
|
||||||
'level4': '153, 153, 153',
|
|
||||||
'level5': '196, 196, 196'
|
|
||||||
}
|
}
|
||||||
export const getHexColor = (code: string, pe: number = 0.1) => {
|
export const getHexColor = (code: string, pe: number = 0.1) => {
|
||||||
const _color = color[code] || color.default
|
const _color = color[code] || color.default
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { PropType } from 'vue';
|
import { PropType } from 'vue';
|
||||||
import { BatchActionsType } from './types';
|
import { BatchActionsType } from './types';
|
||||||
|
import { defineExpose } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -90,14 +91,17 @@ const reload = () => {
|
||||||
|
|
||||||
const onPopConfirm = (e: any, fun: any) => {
|
const onPopConfirm = (e: any, fun: any) => {
|
||||||
if (fun) {
|
if (fun) {
|
||||||
fun(e);
|
|
||||||
onPopCancel();
|
onPopCancel();
|
||||||
|
return fun(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPopCancel = () => {
|
const onPopCancel = () => {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
};
|
};
|
||||||
|
defineExpose({
|
||||||
|
reload
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
background: getBackgroundColor(statusNames[status]),
|
background: getBackgroundColor(statusNames[status]),
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
<div style="display: flex">
|
<div class="card-content-body">
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<div class="card-item-avatar">
|
<div class="card-item-avatar">
|
||||||
<slot name="img"> </slot>
|
<slot name="img"> </slot>
|
||||||
|
@ -249,6 +249,12 @@ const handleClick = () => {
|
||||||
padding: 30px 12px 30px 30px;
|
padding: 30px 12px 30px 30px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.card-content-body {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
.card-item-avatar {
|
.card-item-avatar {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -317,6 +323,7 @@ const handleClick = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.card-content-top-line {
|
.card-content-top-line {
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
<template>
|
||||||
|
<div :class="{ 'j-card-panel': true, 'no-column': noColumn }">
|
||||||
|
<j-row v-if="!noColumn" :gutter="[16, 16]">
|
||||||
|
<j-col
|
||||||
|
v-for="item in itemOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:span="24 / column"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="{
|
||||||
|
'j-card-item': true,
|
||||||
|
active: activeKeys.includes(item.value),
|
||||||
|
disabled: disabled || item.disabled,
|
||||||
|
horizontal: type === 'horizontal',
|
||||||
|
vertical: type === 'vertical',
|
||||||
|
right: float === 'right',
|
||||||
|
left: float === 'left',
|
||||||
|
}"
|
||||||
|
@click="() => handleSelect(item.value, item)"
|
||||||
|
>
|
||||||
|
<div class="j-card-title-warp">
|
||||||
|
<div class="title">
|
||||||
|
<slot
|
||||||
|
name="title"
|
||||||
|
:title="item.label"
|
||||||
|
:option="item"
|
||||||
|
>
|
||||||
|
<j-ellipsis>
|
||||||
|
{{ item.label }}
|
||||||
|
</j-ellipsis>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.subLabel && showSubLabel"
|
||||||
|
class="sub-title"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="subLabel"
|
||||||
|
:sub-label="item.subLabel"
|
||||||
|
:option="item"
|
||||||
|
>
|
||||||
|
{{ item.subLabel }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showImage" class="j-card-image">
|
||||||
|
<slot name="image" :image="item.iconUrl" :option="item">
|
||||||
|
<j-avatar
|
||||||
|
class="icon box-shadow"
|
||||||
|
:src="item.iconUrl"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</j-col>
|
||||||
|
</j-row>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="item in itemOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:class="{
|
||||||
|
'j-card-item': true,
|
||||||
|
active: activeKeys.includes(item.value),
|
||||||
|
disabled: disabled || item.disabled,
|
||||||
|
horizontal: type === 'horizontal',
|
||||||
|
vertical: type === 'vertical',
|
||||||
|
right: float === 'right',
|
||||||
|
left: float === 'left',
|
||||||
|
}"
|
||||||
|
@click="() => handleSelect(item.value, item)"
|
||||||
|
>
|
||||||
|
<div class="j-card-title-warp">
|
||||||
|
<div class="title">
|
||||||
|
<slot name="title" :title="item.label" :option="item">
|
||||||
|
<j-ellipsis>
|
||||||
|
{{ item.label }}
|
||||||
|
</j-ellipsis>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.subLabel && showSubLabel" class="sub-title">
|
||||||
|
<slot
|
||||||
|
name="subLabel"
|
||||||
|
:sub-label="item.subLabel"
|
||||||
|
:option="item"
|
||||||
|
>
|
||||||
|
{{ item.subLabel }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showImage" class="j-card-image">
|
||||||
|
<slot name="image" :image="item.iconUrl" :option="item">
|
||||||
|
<j-avatar class="icon box-shadow" :src="item.iconUrl" />
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, PropType, ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
|
interface CardOption {
|
||||||
|
value: string | number;
|
||||||
|
label: string;
|
||||||
|
subLabel?: string;
|
||||||
|
iconUrl: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String as PropType<'vertical' | 'horizontal'>,
|
||||||
|
default: 'horizontal',
|
||||||
|
},
|
||||||
|
float: {
|
||||||
|
type: String as PropType<'left' | 'right'>,
|
||||||
|
default: 'left',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array as PropType<Array<CardOption>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: Number,
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
noColumn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showImage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showSubLabel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
allowClear: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { multiple, type, disabled, float } = toRefs(props);
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:value', 'change', 'select']);
|
||||||
|
const activeKeys = ref<Array<string | number>>([]);
|
||||||
|
const itemOptions = computed(() => props.options);
|
||||||
|
const isAllowClear = computed(() => {
|
||||||
|
return props.allowClear !== false;
|
||||||
|
});
|
||||||
|
const getOptions = (keys: Array<string | number>): CardOption[] => {
|
||||||
|
return itemOptions.value.filter((item) => {
|
||||||
|
return keys.includes(item.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (key: string | number, item: CardOption) => {
|
||||||
|
if (disabled.value || item.disabled) return;
|
||||||
|
let cloneActiveKeys = new Set(activeKeys.value);
|
||||||
|
|
||||||
|
const isActive = cloneActiveKeys.has(key);
|
||||||
|
|
||||||
|
// 已选中,并且单选,allowClear为false,则return
|
||||||
|
if (isActive && !multiple.value && isAllowClear.value === false) return;
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
// 选中
|
||||||
|
cloneActiveKeys.delete(key);
|
||||||
|
} else {
|
||||||
|
// 添加
|
||||||
|
multiple.value
|
||||||
|
? cloneActiveKeys.add(key)
|
||||||
|
: (cloneActiveKeys = new Set([key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
activeKeys.value = [...cloneActiveKeys.keys()];
|
||||||
|
const options = multiple.value ? getOptions(activeKeys.value) : item;
|
||||||
|
|
||||||
|
const values = props.multiple ? activeKeys.value : activeKeys.value[0]
|
||||||
|
|
||||||
|
emits('update:value', values);
|
||||||
|
emits('change', values, options);
|
||||||
|
emits('select', values, key, !isActive)
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
activeKeys.value = Array.isArray(props.value)
|
||||||
|
? props.value
|
||||||
|
: [props.value];
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@card-border: #e6e6e6;
|
||||||
|
.j-card-panel {
|
||||||
|
.j-card-item {
|
||||||
|
border: 1px solid @card-border;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @black;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.j-card-title-warp {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
word-break: keep-all;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
padding: 22px 4px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.j-card-image {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
color: rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-column {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.j-card-item {
|
||||||
|
min-width: 36px;
|
||||||
|
width: unset;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border: 1px solid var(--ant-primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<div class="radio-button" :style="styles">
|
||||||
|
<div v-for="item in options" @click="onClick(item)" class="radio-button-item" :class="{'active': myValue === item.value }">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'RadioButton',
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value'])
|
||||||
|
|
||||||
|
const myValue = ref(props.value)
|
||||||
|
|
||||||
|
const styles = computed(() => {
|
||||||
|
return {
|
||||||
|
'grid-template-columns': `repeat(${props.columns}, 1fr)`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onClick = (record) => {
|
||||||
|
if (myValue.value !== record.value) {
|
||||||
|
myValue.value = record.value
|
||||||
|
emit('update:value', record.value)
|
||||||
|
emit('select', record.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, () => {
|
||||||
|
myValue.value = props.value
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.radio-button {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.radio-button-item {
|
||||||
|
padding: 6px 12px;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
import CardSelect from './CardSelect.vue';
|
||||||
|
import type { App } from 'vue';
|
||||||
|
|
||||||
|
CardSelect.name = 'JCardSelect';
|
||||||
|
|
||||||
|
CardSelect.install = function (app: App) {
|
||||||
|
app.component('JCardSelect', CardSelect);
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardSelect;
|
|
@ -0,0 +1,163 @@
|
||||||
|
<template>
|
||||||
|
<div :class="['j-check-button', props.class]" :style="styles">
|
||||||
|
<div
|
||||||
|
v-for="item in _options"
|
||||||
|
:key="item.value"
|
||||||
|
:class="{
|
||||||
|
'j-check-button-item': true,
|
||||||
|
'selected': myValue.includes(item.value),
|
||||||
|
'disabled': item.disabled
|
||||||
|
}"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
selected(item.value, item.disabled);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, CSSProperties, PropType, ref, watch } from 'vue';
|
||||||
|
import { isArray } from 'lodash-es';
|
||||||
|
import { Form } from 'ant-design-vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'CheckButton'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
class: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: Object as PropType<CSSProperties>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const emit = defineEmits(['update:value', 'change', 'select']);
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
const myValue = ref();
|
||||||
|
const optionsMap = ref(new Map());
|
||||||
|
|
||||||
|
const styles = computed(() => {
|
||||||
|
return {
|
||||||
|
'grid-template-columns': `repeat(${props.columns}, 1fr)`,
|
||||||
|
...props.style
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const _options = computed(() => {
|
||||||
|
props.options.forEach((item: any) => {
|
||||||
|
if (props.disabled) {
|
||||||
|
item.disabled = props.disabled
|
||||||
|
}
|
||||||
|
optionsMap.value.set(item.value, item);
|
||||||
|
});
|
||||||
|
return props.options;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selected = (key: string | number, disabeld: boolean) => {
|
||||||
|
if (disabeld || props.disabled) return;
|
||||||
|
|
||||||
|
const values = new Set(myValue.value);
|
||||||
|
|
||||||
|
if (values.has(key)) {
|
||||||
|
values.delete(key);
|
||||||
|
} else {
|
||||||
|
if (!props.multiple) {
|
||||||
|
values.clear();
|
||||||
|
}
|
||||||
|
values.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
myValue.value = [...values.values()];
|
||||||
|
|
||||||
|
const optionsItems = myValue.value.map((_key) => {
|
||||||
|
return optionsMap.value.get(_key);
|
||||||
|
});
|
||||||
|
|
||||||
|
const _value = props.multiple ? myValue.value : myValue.value[0];
|
||||||
|
|
||||||
|
emit('update:value', _value);
|
||||||
|
emit('change', _value, props.multiple ? optionsItems : optionsItems[0]);
|
||||||
|
emit('select', _value, props.multiple ? optionsItems : optionsItems[0]);
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
if (props.value) {
|
||||||
|
myValue.value = isArray(props.value) ? props.value : [props.value];
|
||||||
|
} else {
|
||||||
|
myValue.value = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.j-check-button {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.j-check-button-item {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
background-color: #f2f3f5;
|
||||||
|
transition: all 0.3s;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: @primary-color;
|
||||||
|
opacity: 0.85;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: @primary-color;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #00000040;
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import CheckButton from "./CheckButton.vue";
|
||||||
|
|
||||||
|
export default CheckButton
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<!-- <a-tooltip v-if="toolTip" v-bind="toolTip">
|
||||||
|
<span @click="showConfirm" :class="props.className" v-show="show">
|
||||||
|
{{ props.class }}
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
</a-tooltip> -->
|
||||||
|
<span @click="showConfirm" :class="props.className" v-show="show">
|
||||||
|
{{ props.class }}
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Modal } from 'ant-design-vue';
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
onConfirm: {
|
||||||
|
type: Object,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
toolTip: {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// const confirmLoading = ref(false);
|
||||||
|
// const modalVisible = ref(false);
|
||||||
|
// const modalConfirm = async() => {
|
||||||
|
// if (typeof props.onConfirm === 'function') {
|
||||||
|
// confirmLoading.value = true;
|
||||||
|
// const res = await props.onConfirm()?.finally(()=>{
|
||||||
|
// confirmLoading.value = false;
|
||||||
|
// modalVisible.value = false;
|
||||||
|
// return
|
||||||
|
// });
|
||||||
|
// if(!res?.finally){
|
||||||
|
// confirmLoading.value = false;
|
||||||
|
// modalVisible.value = false;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// modalVisible.value = false;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
const showConfirm = () => {
|
||||||
|
if (props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Modal.confirm({
|
||||||
|
title: props.title,
|
||||||
|
content: props?.content,
|
||||||
|
onOk() {
|
||||||
|
return props?.onConfirm();
|
||||||
|
},
|
||||||
|
onCancel() {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.modalContent {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,163 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="operator-box">
|
|
||||||
<j-input-search @search="search" allow-clear placeholder="搜索关键字" />
|
|
||||||
<div class="tree">
|
|
||||||
<j-tree @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent
|
|
||||||
:tree-data="data"
|
|
||||||
:showLine="{ showLeafIcon: false }"
|
|
||||||
:show-icon="true">
|
|
||||||
<template #title="node">
|
|
||||||
<div class="node">
|
|
||||||
<div style="max-width: 180px"><Ellipsis>{{ node.name }}</Ellipsis></div>
|
|
||||||
<div :class="node.children?.length > 0 ? 'parent' : 'add'">
|
|
||||||
<j-popover v-if="node.type === 'property'" placement="right" title="请选择使用值">
|
|
||||||
<template #content>
|
|
||||||
<j-space direction="vertical">
|
|
||||||
<j-tooltip placement="right" title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值">
|
|
||||||
<j-button type="text" @click="recentClick(node)">
|
|
||||||
$recent实时值
|
|
||||||
</j-button>
|
|
||||||
</j-tooltip>
|
|
||||||
<j-tooltip placement="right" title="实时值的上一有效值">
|
|
||||||
<j-button @click="lastClick(node)" type="text">
|
|
||||||
上一值
|
|
||||||
</j-button>
|
|
||||||
</j-tooltip>
|
|
||||||
</j-space>
|
|
||||||
</template>
|
|
||||||
<a>添加</a>
|
|
||||||
</j-popover>
|
|
||||||
|
|
||||||
<a v-else @click="addClick(node)">
|
|
||||||
添加
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</j-tree>
|
|
||||||
</div>
|
|
||||||
<div class="explain">
|
|
||||||
<Markdown :source="item?.description || ''"></Markdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts" name="Operator">
|
|
||||||
import { useProductStore } from '@/store/product';
|
|
||||||
import type { OperatorItem } from './typings';
|
|
||||||
import { treeFilter } from '@/utils/tree'
|
|
||||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
|
||||||
import { getOperator } from '@/api/device/product'
|
|
||||||
import Markdown from '@/components/Markdown'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
id: String
|
|
||||||
})
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: 'addOperatorValue', data: string): void;
|
|
||||||
}
|
|
||||||
const emit = defineEmits<Emits>();
|
|
||||||
|
|
||||||
const item = ref<Partial<OperatorItem>>()
|
|
||||||
const data = ref<OperatorItem[]>([])
|
|
||||||
const dataRef = ref<OperatorItem[]>([])
|
|
||||||
|
|
||||||
const search = (value: string) => {
|
|
||||||
if (value) {
|
|
||||||
const nodes = treeFilter(dataRef.value, value, 'name') as OperatorItem[];
|
|
||||||
data.value = nodes;
|
|
||||||
} else {
|
|
||||||
data.value = dataRef.value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectTree = (k: any, info: any) => {
|
|
||||||
item.value = info.node as unknown as OperatorItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recentClick = (node: OperatorItem) => {
|
|
||||||
emit('addOperatorValue', `$recent("${node.id}")`)
|
|
||||||
}
|
|
||||||
const lastClick = (node: OperatorItem) => {
|
|
||||||
emit('addOperatorValue', `$lastState("${node.id}")`)
|
|
||||||
}
|
|
||||||
const addClick = (node: OperatorItem) => {
|
|
||||||
emit('addOperatorValue', node.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
const productStore = useProductStore()
|
|
||||||
|
|
||||||
const getData = async (id?: string) => {
|
|
||||||
const metadata = productStore.current.metadata || '{}';
|
|
||||||
const _properties = JSON.parse(metadata).properties || [] as PropertyMetadata[]
|
|
||||||
const properties = {
|
|
||||||
id: 'property',
|
|
||||||
name: '属性',
|
|
||||||
description: '',
|
|
||||||
code: '',
|
|
||||||
children: _properties
|
|
||||||
.filter((p: PropertyMetadata) => p.id !== id)
|
|
||||||
.map((p: PropertyMetadata) => ({
|
|
||||||
id: p.id,
|
|
||||||
name: p.name,
|
|
||||||
description: `### ${p.name}
|
|
||||||
\n 数据类型: ${p.valueType?.type}
|
|
||||||
\n 是否只读: ${p.expands?.readOnly || 'false'}
|
|
||||||
\n 可写数值范围: `,
|
|
||||||
type: 'property',
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
const response = await getOperator();
|
|
||||||
if (response.status === 200) {
|
|
||||||
data.value = [properties as OperatorItem, ...response.result];
|
|
||||||
dataRef.value = [properties as OperatorItem, ...response.result];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(() => props.id,
|
|
||||||
(val) => {
|
|
||||||
getData(val)
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.border {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
border-top: 1px solid lightgray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operator-box {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.explain {
|
|
||||||
.border;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree {
|
|
||||||
.border;
|
|
||||||
|
|
||||||
height: 350px;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.node {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 220px;
|
|
||||||
|
|
||||||
//.add {
|
|
||||||
// display: none;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//&:hover .add {
|
|
||||||
// display: block;
|
|
||||||
//}
|
|
||||||
|
|
||||||
.parent {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -3,6 +3,11 @@
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<j-tabs v-model:activeKey="headerType">
|
<j-tabs v-model:activeKey="headerType">
|
||||||
|
<template #rightExtra>
|
||||||
|
<a v-if="virtualRule?.script && isBeginning" @click="beginAction">
|
||||||
|
开始运行
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
<j-tab-pane key="property">
|
<j-tab-pane key="property">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<span class="title">
|
<span class="title">
|
||||||
|
@ -18,44 +23,6 @@
|
||||||
</template>
|
</template>
|
||||||
</j-tab-pane>
|
</j-tab-pane>
|
||||||
</j-tabs>
|
</j-tabs>
|
||||||
<!-- <div>
|
|
||||||
<j-dropdown>
|
|
||||||
<div class="title" @click.prevent>
|
|
||||||
{{
|
|
||||||
headerType === 'property'
|
|
||||||
? '属性赋值'
|
|
||||||
: '标签赋值'
|
|
||||||
}}
|
|
||||||
<div class="description">
|
|
||||||
{{
|
|
||||||
`请对上方规则使用的${
|
|
||||||
headerType === 'property'
|
|
||||||
? '属性'
|
|
||||||
: '标签'
|
|
||||||
}进行赋值`
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #overlay>
|
|
||||||
<j-menu>
|
|
||||||
<j-menu-item>
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
@click="headerType = 'property'"
|
|
||||||
>属性赋值</a
|
|
||||||
>
|
|
||||||
</j-menu-item>
|
|
||||||
<j-menu-item>
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
@click="headerType = 'tag'"
|
|
||||||
>标签赋值</a
|
|
||||||
>
|
|
||||||
</j-menu-item>
|
|
||||||
</j-menu>
|
|
||||||
</template>
|
|
||||||
</j-dropdown>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
{{
|
{{
|
||||||
|
@ -71,7 +38,7 @@
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
bordered
|
bordered
|
||||||
size="small"
|
size="small"
|
||||||
:scroll="{ y: 200 }"
|
:scroll="{ y: 180 }"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record, index }">
|
<template #bodyCell="{ column, record, index }">
|
||||||
<template v-if="column.key === 'id'">
|
<template v-if="column.key === 'id'">
|
||||||
|
@ -79,8 +46,13 @@
|
||||||
showSearch
|
showSearch
|
||||||
:options="options"
|
:options="options"
|
||||||
v-model:value="record.id"
|
v-model:value="record.id"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 100%; z-index: 1400 !important"
|
style="width: 100%;"
|
||||||
|
:virtual="true"
|
||||||
|
:dropdownStyle="{
|
||||||
|
zIndex: 1072
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'current'">
|
<template v-if="column.key === 'current'">
|
||||||
|
@ -131,7 +103,12 @@
|
||||||
:options="tagOptions"
|
:options="tagOptions"
|
||||||
v-model:value="record.id"
|
v-model:value="record.id"
|
||||||
size="small"
|
size="small"
|
||||||
style="width: 100%; z-index: 1400 !important"
|
style="width: 100%;"
|
||||||
|
:virtual="true"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
:dropdownStyle="{
|
||||||
|
zIndex: 1072
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'current'">
|
<template v-if="column.key === 'current'">
|
||||||
|
@ -175,13 +152,13 @@
|
||||||
class="action"
|
class="action"
|
||||||
@click="runScriptAgain"
|
@click="runScriptAgain"
|
||||||
>
|
>
|
||||||
<a style="margin-left: 75px">发送数据</a>
|
<a>发送数据</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="virtualRule?.script">
|
<div v-if="virtualRule?.script && !isBeginning">
|
||||||
<a v-if="isBeginning" @click="beginAction">
|
<!-- <a v-if="isBeginning" @click="beginAction">-->
|
||||||
开始运行
|
<!-- 开始运行-->
|
||||||
</a>
|
<!-- </a>-->
|
||||||
<a v-else @click="stopAction"> 停止运行 </a>
|
<a v-if="!isBeginning" @click="stopAction"> 停止运行 </a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a @click="clearAction"> 清空 </a>
|
<a @click="clearAction"> 清空 </a>
|
||||||
|
@ -219,16 +196,17 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="Debug">
|
<script setup lang="ts" name="Debug">
|
||||||
import { PropType, Ref } from 'vue';
|
import { PropType, Ref } from 'vue';
|
||||||
import { useProductStore } from '@/store/product';
|
|
||||||
import { useRuleEditorStore } from '@/store/ruleEditor';
|
import { useRuleEditorStore } from '@/store/ruleEditor';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { getWebSocket } from '@/utils/websocket';
|
import { getWebSocket } from '@/utils/websocket';
|
||||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
import { onlyMessage } from '@/utils/comm';
|
import { onlyMessage } from '@/utils/comm';
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
virtualRule: Object as PropType<Record<any, any>>,
|
virtualRule: Object as PropType<Record<any, any>>,
|
||||||
id: String,
|
id: String,
|
||||||
|
propertiesOptions: Array,
|
||||||
});
|
});
|
||||||
const emits = defineEmits(['success']);
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
|
@ -238,21 +216,13 @@ type propertyType = {
|
||||||
id?: string;
|
id?: string;
|
||||||
current?: string;
|
current?: string;
|
||||||
last?: string;
|
last?: string;
|
||||||
|
|
||||||
|
type?: string
|
||||||
};
|
};
|
||||||
const property = ref<propertyType[]>([]);
|
const property = ref<propertyType[]>([]);
|
||||||
const tag = ref<Array<any>>([]);
|
const tag = ref<Array<any>>([]);
|
||||||
const headerOptions = [
|
const tableWrapperRef = useTableWrapper()
|
||||||
{
|
|
||||||
key: 'property',
|
|
||||||
label: '属性赋值',
|
|
||||||
title: '属性赋值',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tag',
|
|
||||||
label: '标签赋值',
|
|
||||||
title: '标签赋值',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: '属性名称',
|
title: '属性名称',
|
||||||
|
@ -310,21 +280,20 @@ const deleteTagItem = (index: number) => {
|
||||||
const ws = ref();
|
const ws = ref();
|
||||||
|
|
||||||
const virtualIdRef = ref(new Date().getTime());
|
const virtualIdRef = ref(new Date().getTime());
|
||||||
const medataSource = inject<Ref<any[]>>('_dataSource');
|
const medataSource = inject<Ref<any[]>>('metadataSource');
|
||||||
const tagsSource = inject<Ref<any[]>>('_tagsDataSource');
|
const tagsSource = inject<Ref<any[]>>('_tagsDataSource');
|
||||||
const productStore = useProductStore();
|
|
||||||
const ruleEditorStore = useRuleEditorStore();
|
const ruleEditorStore = useRuleEditorStore();
|
||||||
|
|
||||||
const time = ref<number>(0);
|
const time = ref<number>(0);
|
||||||
const timer = ref<any>(null);
|
const timer = ref<any>(null);
|
||||||
|
|
||||||
const runScript = () => {
|
const runScript = () => {
|
||||||
const metadata = productStore.current.metadata || '{}';
|
const propertiesList = medataSource?.value || []
|
||||||
const propertiesList = JSON.parse(metadata).properties || [];
|
|
||||||
const _properties = property.value.map((item: any) => {
|
const _properties = property.value.map((item: any) => {
|
||||||
const _item = propertiesList.find((i: any) => i.id === item.id);
|
const _item = propertiesList.find((i: any) => i.id === item.id);
|
||||||
return { ...item, type: _item?.valueType?.type };
|
return { ...item, type: _item?.valueType?.type };
|
||||||
});
|
});
|
||||||
|
console.log('runScript', _properties, propertiesList)
|
||||||
let _tags = {};
|
let _tags = {};
|
||||||
tag.value.forEach((item) => {
|
tag.value.forEach((item) => {
|
||||||
_tags[item.id] = item.current;
|
_tags[item.id] = item.current;
|
||||||
|
@ -334,6 +303,11 @@ const runScript = () => {
|
||||||
}
|
}
|
||||||
if (!props.virtualRule?.script) {
|
if (!props.virtualRule?.script) {
|
||||||
isBeginning.value = true;
|
isBeginning.value = true;
|
||||||
|
message.config({
|
||||||
|
getContainer() {
|
||||||
|
return tableWrapperRef.value || document.body
|
||||||
|
}
|
||||||
|
})
|
||||||
onlyMessage('请编辑规则', 'warning');
|
onlyMessage('请编辑规则', 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -385,12 +359,12 @@ const runScriptAgain = async () => {
|
||||||
if (wsAgain.value) {
|
if (wsAgain.value) {
|
||||||
wsAgain.value.unsubscribe?.();
|
wsAgain.value.unsubscribe?.();
|
||||||
}
|
}
|
||||||
const metadata = productStore.current.metadata || '{}';
|
const propertiesList = medataSource?.value || []
|
||||||
const propertiesList = JSON.parse(metadata).properties || [];
|
|
||||||
const _properties = property.value.map((item: any) => {
|
const _properties = property.value.map((item: any) => {
|
||||||
const _item = propertiesList.find((i: any) => i.id === item.id);
|
const _item = propertiesList.find((i: any) => i.id === item.id);
|
||||||
return { ...item, type: _item?.valueType?.type };
|
return { ...item, type: _item?.valueType?.type };
|
||||||
});
|
});
|
||||||
|
console.log('runScriptAgain', _properties, propertiesList)
|
||||||
|
|
||||||
wsAgain.value = getWebSocket(
|
wsAgain.value = getWebSocket(
|
||||||
`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
||||||
|
@ -414,6 +388,14 @@ const getTime = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const beginAction = () => {
|
const beginAction = () => {
|
||||||
|
if (!property.value.length || property.value.some(item => !item.id || !(item.current || item.last) )) {
|
||||||
|
message.config({
|
||||||
|
getContainer() {
|
||||||
|
return tableWrapperRef.value || document.body
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return onlyMessage('请填写属性值', 'warning')
|
||||||
|
}
|
||||||
isBeginning.value = false;
|
isBeginning.value = false;
|
||||||
runScript();
|
runScript();
|
||||||
getTime();
|
getTime();
|
||||||
|
@ -435,13 +417,18 @@ onUnmounted(() => {
|
||||||
ws.value.unsubscribe?.();
|
ws.value.unsubscribe?.();
|
||||||
}
|
}
|
||||||
clearAction();
|
clearAction();
|
||||||
|
|
||||||
|
message.config({
|
||||||
|
getContainer() {
|
||||||
|
return document.body
|
||||||
|
},
|
||||||
|
})
|
||||||
window.clearInterval(timer.value);
|
window.clearInterval(timer.value);
|
||||||
timer.value = null;
|
timer.value = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
return (medataSource.value || [])
|
return (props.propertiesOptions || [])
|
||||||
.filter((p) => p.id !== props.id)
|
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id,
|
value: item.id,
|
||||||
|
@ -455,6 +442,9 @@ const tagOptions = computed(() => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
beginAction
|
||||||
|
})
|
||||||
// const getProperty = () => {
|
// const getProperty = () => {
|
||||||
// // const metadata = productStore.current.metadata || '{}';
|
// // const metadata = productStore.current.metadata || '{}';
|
||||||
// // const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
|
// // const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
|
||||||
|
@ -465,10 +455,11 @@ const tagOptions = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.debug-container {
|
.debug-container {
|
||||||
// display: flex;
|
display: flex;
|
||||||
// width: 100%;
|
// width: 100%;
|
||||||
// height: 340px;
|
// height: 340px;
|
||||||
// margin-top: 20px;
|
// margin-top: 20px;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
// min-width: 0;
|
// min-width: 0;
|
||||||
|
@ -476,35 +467,36 @@ const tagOptions = computed(() => {
|
||||||
// overflow-y: auto;
|
// overflow-y: auto;
|
||||||
height: 350px;
|
height: 350px;
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
margin-bottom: 10px;
|
//margin-bottom: 10px;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
//display: flex;
|
||||||
align-items: center;
|
//align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 46px;
|
||||||
border-bottom: 1px solid lightgray;
|
border-bottom: 1px solid lightgray;
|
||||||
|
padding: 0 12px;
|
||||||
//justify-content: space-around;
|
//justify-content: space-around;
|
||||||
|
|
||||||
div {
|
//div {
|
||||||
display: flex;
|
// display: flex;
|
||||||
//width: 100%;
|
// //width: 100%;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
justify-content: flex-start;
|
// justify-content: flex-start;
|
||||||
height: 100%;
|
// height: 100%;
|
||||||
|
//
|
||||||
.title {
|
// .title {
|
||||||
margin: 0 10px;
|
// margin: 0 10px;
|
||||||
font-weight: 600;
|
// font-weight: 600;
|
||||||
font-size: 16px;
|
// font-size: 16px;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
.description {
|
// .description {
|
||||||
margin-left: 10px;
|
// margin-left: 10px;
|
||||||
color: lightgray;
|
// color: lightgray;
|
||||||
font-size: 12px;
|
// font-size: 12px;
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
|
@ -512,6 +504,11 @@ const tagOptions = computed(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.top-bottom {
|
.top-bottom {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
node.children?.length > 0
|
!node.isLeaf
|
||||||
? 'parent'
|
? 'parent'
|
||||||
: 'add'
|
: 'add'
|
||||||
"
|
"
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
}"
|
}"
|
||||||
placement="right"
|
placement="right"
|
||||||
title="请选择使用值"
|
title="请选择使用值"
|
||||||
|
:getPopupContainer="getPopupContainer"
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<j-space direction="vertical">
|
<j-space direction="vertical">
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
}"
|
}"
|
||||||
placement="right"
|
placement="right"
|
||||||
title="请选择使用值"
|
title="请选择使用值"
|
||||||
|
:getPopupContainer="getPopupContainer"
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<j-space direction="vertical">
|
<j-space direction="vertical">
|
||||||
|
@ -119,7 +121,7 @@ import { treeFilter } from '@/utils/tree';
|
||||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
import { PropertyMetadata } from '@/views/device/Product/typings';
|
||||||
import { getOperator } from '@/api/device/product';
|
import { getOperator } from '@/api/device/product';
|
||||||
import { inject } from 'vue';
|
import { inject } from 'vue';
|
||||||
import { Descriptions } from 'ant-design-vue';
|
import {useTableWrapper, useTableFullScreen} from "@/components/Metadata/Table/context";
|
||||||
import Markdown from '@/components/Markdown'
|
import Markdown from '@/components/Markdown'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -136,6 +138,8 @@ const item = ref<Partial<OperatorItem>>();
|
||||||
const data = ref<OperatorItem[]>([]);
|
const data = ref<OperatorItem[]>([]);
|
||||||
const dataRef = ref<OperatorItem[]>([]);
|
const dataRef = ref<OperatorItem[]>([]);
|
||||||
const tagsMetadata: any = inject('_tagsDataSource');
|
const tagsMetadata: any = inject('_tagsDataSource');
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const fullScreen = useTableFullScreen()
|
||||||
const search = (value: string) => {
|
const search = (value: string) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
const nodes = treeFilter(
|
const nodes = treeFilter(
|
||||||
|
@ -176,29 +180,38 @@ const getData = async (id?: string) => {
|
||||||
name: '属性',
|
name: '属性',
|
||||||
description: '',
|
description: '',
|
||||||
code: '',
|
code: '',
|
||||||
|
isLeaf: false,
|
||||||
children: _properties
|
children: _properties
|
||||||
.filter((p: PropertyMetadata) => p.id !== id)
|
.filter((p: PropertyMetadata) => p.id !== id)
|
||||||
.map((p: PropertyMetadata) => ({
|
.map((p: PropertyMetadata) => {
|
||||||
|
const readOnly = p.expands.type.length === 1 && p.expands.type[0] === 'read' ? '是' : '否'
|
||||||
|
|
||||||
|
return {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
|
isLeaf: true,
|
||||||
description: `### ${p.name}
|
description: `### ${p.name}
|
||||||
|
\n 标识: ${p.id}
|
||||||
\n 数据类型: ${p.valueType?.type}
|
\n 数据类型: ${p.valueType?.type}
|
||||||
\n 是否只读: ${p.expands?.readOnly || 'false'}
|
\n 是否只读: ${readOnly}
|
||||||
\n 可写数值范围: `,
|
\n 可写数值范围: `,
|
||||||
type: 'property',
|
type: 'property',
|
||||||
})),
|
}
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
const tags = {
|
const tags = {
|
||||||
id: 'tags',
|
id: 'tags',
|
||||||
name: '标签',
|
name: '标签',
|
||||||
Description: '',
|
Description: '',
|
||||||
code: '',
|
code: '',
|
||||||
|
isLeaf: false,
|
||||||
children: tagsMetadata.value.map((i: any) => ({
|
children: tagsMetadata.value.map((i: any) => ({
|
||||||
id: i.id,
|
id: i.id,
|
||||||
name: i.name,
|
name: i.name,
|
||||||
|
isLeaf: true,
|
||||||
description: `### ${i.name}
|
description: `### ${i.name}
|
||||||
|
\n 标识: ${i.id}
|
||||||
\n 数据类型: ${i.valueType?.type}
|
\n 数据类型: ${i.valueType?.type}
|
||||||
\n 是否只读: ${i.expands?.readOnly || 'false'}
|
|
||||||
\n 可写数值范围: `,
|
\n 可写数值范围: `,
|
||||||
type: 'tags',
|
type: 'tags',
|
||||||
})),
|
})),
|
||||||
|
@ -218,6 +231,14 @@ const getData = async (id?: string) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPopupContainer = (node: any) => {
|
||||||
|
if (fullScreen.value) {
|
||||||
|
return tableWrapperRef.value || node
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.body
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.id,
|
() => props.id,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
@ -236,17 +257,21 @@ watch(
|
||||||
.operator-box {
|
.operator-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.left,
|
.left,
|
||||||
.right {
|
.right {
|
||||||
width: 50%;
|
//width: 50%;
|
||||||
height: 350px;
|
//height: 350px;
|
||||||
|
height: calc(50% - 7px);
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-right: 10px;
|
//margin-right: 10px;
|
||||||
.tree {
|
.tree {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
//overflow-y: auto;
|
//overflow-y: auto;
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<j-modal
|
<j-modal
|
||||||
:zIndex="1030"
|
:zIndex="1072"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
visible
|
visible
|
||||||
width="70vw"
|
width="1300px"
|
||||||
title="编辑规则"
|
title="编辑规则"
|
||||||
:getContainer="(node) => fullRef || node"
|
|
||||||
@cancel="handleCancel"
|
|
||||||
:destroyOnClose="true"
|
:destroyOnClose="true"
|
||||||
|
:dialogStyle="{
|
||||||
|
zIndex: 1072
|
||||||
|
}"
|
||||||
|
:getContainer="(node) => tableWrapperRef || node"
|
||||||
|
@cancel="handleCancel"
|
||||||
>
|
>
|
||||||
<div class="header" v-if="virtualRule?.windowType && virtualRule?.windowType !== 'undefined'">
|
<div class="header" v-if="virtualRule?.windowType && virtualRule?.windowType !== 'undefined'">
|
||||||
<div class="header-item">
|
<div class="header-item">
|
||||||
|
@ -24,9 +27,6 @@
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div>
|
<div>
|
||||||
<Operator :id="id" :propertiesOptions="propertiesOptions" @add-operator-value="addOperatorValue" />
|
|
||||||
</div>
|
|
||||||
<div style="margin-top: 10px;">
|
|
||||||
<Editor
|
<Editor
|
||||||
ref="editor"
|
ref="editor"
|
||||||
mode="advance"
|
mode="advance"
|
||||||
|
@ -35,18 +35,21 @@
|
||||||
:tips="tips"
|
:tips="tips"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div style="margin-top: 10px;">
|
||||||
<div class="right">
|
|
||||||
<Debug
|
<Debug
|
||||||
:virtualRule="{
|
:virtualRule="{
|
||||||
...virtualRule,
|
...virtualRule,
|
||||||
script: _value,
|
script: _value,
|
||||||
}"
|
}"
|
||||||
|
:propertiesOptions="propertiesOptions"
|
||||||
:id="id"
|
:id="id"
|
||||||
@success="onSuccess"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<Operator :id="id" :propertiesOptions="propertiesOptions" @add-operator-value="addOperatorValue" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<j-space>
|
<j-space>
|
||||||
<j-button @click="handleCancel">取消</j-button>
|
<j-button @click="handleCancel">取消</j-button>
|
||||||
|
@ -59,9 +62,8 @@
|
||||||
import Editor from './Editor/index.vue';
|
import Editor from './Editor/index.vue';
|
||||||
import Debug from './Debug/index.vue';
|
import Debug from './Debug/index.vue';
|
||||||
import Operator from './Operator/index.vue';
|
import Operator from './Operator/index.vue';
|
||||||
import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable'
|
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'save', data: string | undefined): void;
|
(e: 'save', data: string | undefined): void;
|
||||||
(e: 'close'): void;
|
(e: 'close'): void;
|
||||||
|
@ -77,8 +79,7 @@ const props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const _value = ref<string | undefined>(props.value);
|
const _value = ref<string | undefined>(props.value);
|
||||||
const _disabled = ref<boolean>(true);
|
const tableWrapperRef = useTableWrapper()
|
||||||
const fullRef = inject(FULL_CODE);
|
|
||||||
const tips = ref<any[]>([])
|
const tips = ref<any[]>([])
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
emit('close');
|
emit('close');
|
||||||
|
@ -151,11 +152,11 @@ getAllCrud()
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
width: 60%;
|
width: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
width: 40%;
|
flex: 1 1 0;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
border-left: 1px solid lightgray;
|
border-left: 1px solid lightgray;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<j-input allowClear v-model:value="inputPoint">
|
<j-input allowClear v-model:value="inputPoint">
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<environment-outlined @click="modalVis = true" />
|
<environment-outlined @click="showMap" />
|
||||||
</template>
|
</template>
|
||||||
</j-input>
|
</j-input>
|
||||||
<j-modal
|
<j-modal
|
||||||
|
@ -101,6 +101,11 @@ const clickMap = (e: any) => {
|
||||||
position.value = [e.lnglat.lng, e.lnglat.lat];
|
position.value = [e.lnglat.lng, e.lnglat.lat];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showMap = () => {
|
||||||
|
mapPoint.value = props.point
|
||||||
|
modalVis.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选择搜索结果
|
* 选择搜索结果
|
||||||
* @param e
|
* @param e
|
||||||
|
@ -110,6 +115,8 @@ const selectPoi = (e: any) => {
|
||||||
mapPoint.value = selectPoint.join(',');
|
mapPoint.value = selectPoint.join(',');
|
||||||
map.setCenter(selectPoint);
|
map.setCenter(selectPoint);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -0,0 +1,468 @@
|
||||||
|
<template>
|
||||||
|
<div :class="{
|
||||||
|
'metadata-edit-table-wrapper': true,
|
||||||
|
'table-full-screen': isFullscreen
|
||||||
|
}" ref="tableWrapper">
|
||||||
|
<div class="metadata-edit-table-extra">
|
||||||
|
<slot name="extra" :isFullscreen="isFullscreen" :fullScreenToggle="toggle"/>
|
||||||
|
</div>
|
||||||
|
<div class="metadata-edit-table">
|
||||||
|
<div class="metadata-edit-table-header" style="height: 50px" :style="{paddingRight: scrollWidth + 'px'}">
|
||||||
|
<Header
|
||||||
|
:columns="myColumns"
|
||||||
|
:style="{width: tableStyle.width}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="metadata-edit-table-body" :style="{width: tableStyle.width, height: `${height}px`}">
|
||||||
|
<Body
|
||||||
|
ref="tableBody"
|
||||||
|
:dataSource="bodyDataSource"
|
||||||
|
:columns="myColumns"
|
||||||
|
:cellHeight="cellHeight"
|
||||||
|
:height="height"
|
||||||
|
:disableMenu="disableMenu"
|
||||||
|
:rowKey="rowKey"
|
||||||
|
:groupKey="groupActive.value"
|
||||||
|
:openGroup="openGroup"
|
||||||
|
:rowSelection="rowSelection"
|
||||||
|
@scrollDown="onScrollDown"
|
||||||
|
>
|
||||||
|
<template v-for="(_, name) in slots" #[name]="slotData">
|
||||||
|
<slot :name="name" v-bind="slotData || {}"/>
|
||||||
|
</template>
|
||||||
|
</Body>
|
||||||
|
<slot name="bodyExtra"></slot>
|
||||||
|
</div>
|
||||||
|
<Group
|
||||||
|
v-if="dataSource.length && openGroup"
|
||||||
|
v-model:activeKey="groupActive.value"
|
||||||
|
:options="groupOptions"
|
||||||
|
@add="addGroup"
|
||||||
|
@delete="groupDelete"
|
||||||
|
@edit="groupEdit"
|
||||||
|
@change="updateGroupActive"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataBaseTable">
|
||||||
|
import {
|
||||||
|
FULL_SCREEN,
|
||||||
|
RIGHT_MENU,
|
||||||
|
TABLE_DATA_SOURCE,
|
||||||
|
TABLE_ERROR,
|
||||||
|
TABLE_GROUP_ACTIVE,
|
||||||
|
TABLE_GROUP_ERROR,
|
||||||
|
TABLE_GROUP_OPTIONS,
|
||||||
|
TABLE_OPEN_GROUP,
|
||||||
|
TABLE_TOOL,
|
||||||
|
TABLE_WRAPPER
|
||||||
|
} from './consts'
|
||||||
|
import {handleColumnsWidth} from './utils'
|
||||||
|
import {useGroup, useResizeObserver, useValidate} from './hooks'
|
||||||
|
import {tableProps} from 'ant-design-vue/lib/table'
|
||||||
|
import {useFormContext} from './context'
|
||||||
|
import Header from './header.vue'
|
||||||
|
import Body from './body.vue'
|
||||||
|
import {useFullscreen} from '@vueuse/core';
|
||||||
|
import {provide, useAttrs, useSlots} from 'vue'
|
||||||
|
import Group from './group.vue'
|
||||||
|
import {bodyProps} from "./props";
|
||||||
|
import {findIndex, get, sortBy} from 'lodash-es'
|
||||||
|
|
||||||
|
const emit = defineEmits(['scrollDown', 'rightMenuClick', 'editChange', 'groupDelete', 'groupEdit'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
...tableProps(),
|
||||||
|
...bodyProps(),
|
||||||
|
serial: {
|
||||||
|
type: [Object, Boolean],
|
||||||
|
default: () => ({
|
||||||
|
width: 66,
|
||||||
|
title: '序号'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
validateRowKey: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
const attrs = useAttrs()
|
||||||
|
|
||||||
|
const myColumns = ref([])
|
||||||
|
const tableWrapper = ref()
|
||||||
|
const tableBody = ref()
|
||||||
|
const tableStyle = reactive({
|
||||||
|
width: '100%',
|
||||||
|
height: props.height
|
||||||
|
})
|
||||||
|
|
||||||
|
const fields = {}
|
||||||
|
const defaultGroupId = 'group_1'
|
||||||
|
|
||||||
|
const fieldsErrMap = ref({})
|
||||||
|
const fieldsGroupError = ref({})
|
||||||
|
|
||||||
|
const sortData = reactive({
|
||||||
|
key: undefined,
|
||||||
|
order: undefined,
|
||||||
|
orderKeys: [],
|
||||||
|
dataIndex: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
groupActive,
|
||||||
|
groupOptions,
|
||||||
|
addGroup,
|
||||||
|
removeGroup,
|
||||||
|
updateGroupActive,
|
||||||
|
updateGroupOptions
|
||||||
|
} = useGroup(props.openGroup)
|
||||||
|
|
||||||
|
const _dataSource = computed(() => {
|
||||||
|
const _options = new Map()
|
||||||
|
|
||||||
|
const sortDataSource = sortData.key ?
|
||||||
|
sortBy(props.dataSource, (val) => {
|
||||||
|
if (!val.id) return 99999999
|
||||||
|
|
||||||
|
const index = findIndex(sortData.orderKeys, val2 => get(val, sortData.key) === val2)
|
||||||
|
return sortData.order === 'desc' ? index : ~index + 1
|
||||||
|
}) : props.dataSource
|
||||||
|
|
||||||
|
sortDataSource.forEach((item, index) => {
|
||||||
|
item.__dataIndex = index
|
||||||
|
if (props.openGroup) {
|
||||||
|
const _groupId = item.expands?.groupId
|
||||||
|
if (!_groupId) {
|
||||||
|
item.expands.groupId = groupActive.value || defaultGroupId
|
||||||
|
item.expands.groupName = groupActive.label || '分组_1'
|
||||||
|
}
|
||||||
|
|
||||||
|
const _optionsItem = _options.get(item.expands.groupId)
|
||||||
|
|
||||||
|
if (!_optionsItem) {
|
||||||
|
_options.set(item.expands.groupId, {
|
||||||
|
value: item.expands?.groupId,
|
||||||
|
label: item.expands?.groupName,
|
||||||
|
effective: item.id ? 1 : 0, // 有效数据长度
|
||||||
|
len: 1 // 分组数据总长度
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (item.id) {
|
||||||
|
_optionsItem.effective += 1
|
||||||
|
}
|
||||||
|
_optionsItem.len += 1
|
||||||
|
_options.set(item.expands.groupId, _optionsItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
item.__serial = _optionsItem?.len || 1
|
||||||
|
} else {
|
||||||
|
item.__serial = index + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (props.openGroup) {
|
||||||
|
updateGroupOptions([..._options.values()])
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortDataSource
|
||||||
|
})
|
||||||
|
|
||||||
|
const bodyDataSource = computed(() => {
|
||||||
|
if (props.openGroup) {
|
||||||
|
return _dataSource.value.filter(item => {
|
||||||
|
return item.expands.groupId === groupActive.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return _dataSource.value
|
||||||
|
})
|
||||||
|
|
||||||
|
useResizeObserver(tableWrapper, onResize)
|
||||||
|
|
||||||
|
const {isFullscreen, toggle} = useFullscreen(tableWrapper);
|
||||||
|
|
||||||
|
const {rules, validateItem, validate, errorMap} = useValidate(
|
||||||
|
_dataSource,
|
||||||
|
props.columns,
|
||||||
|
props.rowKey,
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
fieldsErrMap.value = {}
|
||||||
|
fieldsGroupError.value = {}
|
||||||
|
const errMap = {}
|
||||||
|
|
||||||
|
// 显示全部err红标
|
||||||
|
err.forEach((item, errIndex) => {
|
||||||
|
item.forEach((e, eIndex) => {
|
||||||
|
const field = findField(e.__dataIndex, e.field)
|
||||||
|
|
||||||
|
const _eventKey = field ? field.eventKey : `${e.__dataIndex}-${e.field}`
|
||||||
|
if (field) {
|
||||||
|
field.showErrorTip(e.message)
|
||||||
|
}
|
||||||
|
errMap[_eventKey] = e.message
|
||||||
|
|
||||||
|
if (errIndex === 0 && eIndex === 0) {
|
||||||
|
|
||||||
|
if (props.openGroup) {
|
||||||
|
const expands = _dataSource.value[e.__dataIndex].expands
|
||||||
|
updateGroupActive(expands.groupId, expands.groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
tableBody.value.scrollTo(e.__serial - 1)
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
fieldsErrMap.value = errMap
|
||||||
|
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
fieldsErrMap.value = {}
|
||||||
|
},
|
||||||
|
onEdit: () => {
|
||||||
|
emit('editChange', true)
|
||||||
|
},
|
||||||
|
validateRowKey: props.validateRowKey
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
provide(TABLE_WRAPPER, tableWrapper)
|
||||||
|
provide(FULL_SCREEN, isFullscreen)
|
||||||
|
provide(RIGHT_MENU, {click: rightMenu, getPopupContainer: () => tableWrapper.value})
|
||||||
|
provide(TABLE_ERROR, fieldsErrMap)
|
||||||
|
provide(TABLE_GROUP_ERROR, fieldsGroupError)
|
||||||
|
provide(TABLE_DATA_SOURCE, _dataSource)
|
||||||
|
provide(TABLE_OPEN_GROUP, props.openGroup)
|
||||||
|
provide(TABLE_TOOL, {
|
||||||
|
scrollTo: (record) => {
|
||||||
|
if (props.openGroup) {
|
||||||
|
const expands = record.expands
|
||||||
|
updateGroupActive(expands.groupId, expands.groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
tableBody.value.scrollTo(record.__serial)
|
||||||
|
}, 10)
|
||||||
|
},
|
||||||
|
selected: (keys) => {
|
||||||
|
tableBody.value.updateSelectedKeys(keys)
|
||||||
|
},
|
||||||
|
order: (type, key, orderKeys, dataIndex) => {
|
||||||
|
sortData.key = key
|
||||||
|
sortData.order = type
|
||||||
|
sortData.orderKeys = orderKeys
|
||||||
|
sortData.dataIndex = dataIndex
|
||||||
|
},
|
||||||
|
cleanOrder: () => {
|
||||||
|
sortData.key = undefined
|
||||||
|
sortData.order = undefined
|
||||||
|
sortData.orderKeys = []
|
||||||
|
sortData.dataIndex = undefined
|
||||||
|
},
|
||||||
|
sortData
|
||||||
|
})
|
||||||
|
provide(TABLE_GROUP_OPTIONS, groupOptions)
|
||||||
|
provide(TABLE_GROUP_ACTIVE, groupActive)
|
||||||
|
|
||||||
|
const addField = (key, field) => {
|
||||||
|
fields[key] = field
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeField = (key) => {
|
||||||
|
delete fields[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function findField(index, name) {
|
||||||
|
const fieldId = Object.keys(fields).find(key => {
|
||||||
|
const {names} = fields[key]
|
||||||
|
return names[0] === index && names[1] === name
|
||||||
|
})
|
||||||
|
return fields[fieldId]
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFieldError(key) {
|
||||||
|
delete fieldsErrMap.value[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFieldError(key, message) {
|
||||||
|
fieldsErrMap.value[key] = message
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollWidth = computed(() => {
|
||||||
|
return (props.dataSource.length * props.cellHeight) > props.height ? 17 : 0
|
||||||
|
})
|
||||||
|
|
||||||
|
function onResize({width = 0, height}) {
|
||||||
|
|
||||||
|
const _width = width - scrollWidth.value
|
||||||
|
|
||||||
|
tableStyle.width = width || '100%'
|
||||||
|
|
||||||
|
// tableStyle.height = height - 146
|
||||||
|
|
||||||
|
let newColumns = [...props.columns]
|
||||||
|
|
||||||
|
if (props.serial) {
|
||||||
|
const serial = {
|
||||||
|
dataIndex: '__serial',
|
||||||
|
title: props.serial.title || '序号',
|
||||||
|
customRender: (customData) => {
|
||||||
|
if (props.serial?.customRender) {
|
||||||
|
return props.serial?.customRender(customData)
|
||||||
|
}
|
||||||
|
return customData.index + 1
|
||||||
|
},
|
||||||
|
width: props.serial?.width
|
||||||
|
}
|
||||||
|
newColumns = [serial, ...props.columns]
|
||||||
|
}
|
||||||
|
|
||||||
|
myColumns.value = handleColumnsWidth(newColumns, _width)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScrollDown = (len) => {
|
||||||
|
emit('scrollDown', len)
|
||||||
|
}
|
||||||
|
|
||||||
|
function rightMenu(menuType, record, copyValue) {
|
||||||
|
emit('rightMenuClick', menuType, record, copyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToById = (key) => {
|
||||||
|
const _index = _dataSource.value.findIndex(item => item[props.rowKey] === key)
|
||||||
|
tableBody.value.scrollTo(_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToByIndex = (index) => {
|
||||||
|
tableBody.value.scrollTo(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTableWrapperRef = () => {
|
||||||
|
return tableWrapper.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupDelete = (id, index) => {
|
||||||
|
removeGroup(index)
|
||||||
|
Object.keys(fieldsErrMap.value).forEach(errorKey => {
|
||||||
|
const [index] = errorKey.split('-')
|
||||||
|
const dataSourceItem = _dataSource.value[index]
|
||||||
|
const groupId = dataSourceItem.expands?.groupId
|
||||||
|
if (groupId === id) {
|
||||||
|
removeFieldError(errorKey)
|
||||||
|
removeField(errorKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
emit('groupDelete', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupEdit = (record) => {
|
||||||
|
emit('groupEdit', record)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGroupActive = () => {
|
||||||
|
return groupActive.value
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => fieldsErrMap.value, (errorMap) => {
|
||||||
|
fieldsGroupError.value = {}
|
||||||
|
|
||||||
|
if (props.openGroup) {
|
||||||
|
const _errorObj = errorMap
|
||||||
|
const groupErrorMap = {}
|
||||||
|
|
||||||
|
Object.keys(_errorObj).forEach(errorKey => {
|
||||||
|
const [index] = errorKey.split('-')
|
||||||
|
const dataSourceItem = _dataSource.value[index]
|
||||||
|
const groupId = dataSourceItem.expands?.groupId
|
||||||
|
|
||||||
|
const groupError = groupErrorMap[groupId]
|
||||||
|
|
||||||
|
const groupErrorItem = {
|
||||||
|
[errorKey]: {
|
||||||
|
message: _errorObj[errorKey],
|
||||||
|
index,
|
||||||
|
serial: dataSourceItem.__serial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupError) {
|
||||||
|
groupError.push(groupErrorItem)
|
||||||
|
} else {
|
||||||
|
groupErrorMap[groupId] = [groupErrorItem]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(groupErrorMap)
|
||||||
|
|
||||||
|
fieldsGroupError.value = groupErrorMap
|
||||||
|
}
|
||||||
|
}, {deep: true})
|
||||||
|
|
||||||
|
watch(() => scrollWidth.value, () => {
|
||||||
|
onResize({width: tableStyle.width})
|
||||||
|
})
|
||||||
|
|
||||||
|
useFormContext({
|
||||||
|
dataSource: computed(() => {
|
||||||
|
return props.dataSource
|
||||||
|
}),
|
||||||
|
errorMap,
|
||||||
|
rules,
|
||||||
|
addField,
|
||||||
|
removeField,
|
||||||
|
removeFieldError,
|
||||||
|
addFieldError,
|
||||||
|
validateItem
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validate,
|
||||||
|
tableWrapper,
|
||||||
|
scrollToById,
|
||||||
|
scrollToByIndex,
|
||||||
|
getTableWrapperRef,
|
||||||
|
getGroupActive
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.metadata-edit-table-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.table-full-screen {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-edit-table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #fafafa;
|
||||||
|
transition: background-color .3s ease;
|
||||||
|
|
||||||
|
.metadata-edit-table-header {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-edit-table-body {
|
||||||
|
background-color: #fff;
|
||||||
|
overflow-y: hidden;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,201 @@
|
||||||
|
<template>
|
||||||
|
<a-tooltip
|
||||||
|
color="#ffffff"
|
||||||
|
:get-popup-container="popContainer"
|
||||||
|
:arrowPointAtCenter="true"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<span style="color: #1d2129">{{errorMap.message}}</span>
|
||||||
|
</template>
|
||||||
|
<div v-if="errorMap.visible" class="table-form-error-target" ></div>
|
||||||
|
</a-tooltip>
|
||||||
|
<div :id="eventKey" style="position: relative" :class="{'edit-table-form-has-error': errorMap.message }">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="TableFormItem">
|
||||||
|
import {useInjectError, useInjectForm, useTableWrapper} from "./context";
|
||||||
|
import {get, isArray } from 'lodash-es'
|
||||||
|
import {onBeforeUnmount, computed} from "vue";
|
||||||
|
import { useProvideFormItemContext } from 'ant-design-vue/es/form/FormItemContext'
|
||||||
|
import {TABLE_FORM_ITEM_ERROR} from "@/components/Metadata/Table/consts";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const context = useInjectForm()
|
||||||
|
const globalErrorMessage = useInjectError()
|
||||||
|
|
||||||
|
let hideTimer
|
||||||
|
|
||||||
|
const eventKey = computed(() => {
|
||||||
|
const names = isArray(props.name) ? props.name : [props.name]
|
||||||
|
return names.join('-')
|
||||||
|
})
|
||||||
|
|
||||||
|
const errorMap = reactive({
|
||||||
|
message: '',
|
||||||
|
visible: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const filedId = computed(() => {
|
||||||
|
const names = isArray(props.name) ? props.name : [props.name]
|
||||||
|
return names.join('_')
|
||||||
|
})
|
||||||
|
|
||||||
|
const filedName = computed(() => {
|
||||||
|
const names = isArray(props.name) ? props.name : [props.name]
|
||||||
|
const _rules = context.rules.value
|
||||||
|
let tempKey = undefined
|
||||||
|
|
||||||
|
for (const key of names) {
|
||||||
|
if (key in _rules) {
|
||||||
|
tempKey = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempKey
|
||||||
|
})
|
||||||
|
|
||||||
|
const filedValue = computed(() => {
|
||||||
|
return get(context.dataSource.value, props.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
provide(TABLE_FORM_ITEM_ERROR, errorMap)
|
||||||
|
|
||||||
|
const popContainer = (e) => {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeTimer = () => {
|
||||||
|
if (hideTimer) {
|
||||||
|
window.clearTimeout(hideTimer)
|
||||||
|
hideTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const showErrorTip = (msg) => {
|
||||||
|
removeTimer()
|
||||||
|
errorMap.message = msg
|
||||||
|
errorMap.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideErrorTip = () => {
|
||||||
|
errorMap.visible = false
|
||||||
|
removeTimer()
|
||||||
|
hideTimer = setTimeout(() => {
|
||||||
|
errorMap.message = ''
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
const validateRules = () => {
|
||||||
|
let index = 0
|
||||||
|
if (isArray(props.name)) {
|
||||||
|
index = props.name[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = context.validateItem({ [filedName.value]: get(context.dataSource.value, props.name) }, index)
|
||||||
|
promise.catch(res => {
|
||||||
|
const error = res?.filter(item => item.field === filedName.value) || []
|
||||||
|
if (error.length === 0) {
|
||||||
|
hideErrorTip()
|
||||||
|
context.removeFieldError(eventKey.value)
|
||||||
|
} else {
|
||||||
|
removeTimer()
|
||||||
|
errorMap.message = error[0]?.message || errorMap.message
|
||||||
|
errorMap.visible = !!error.length
|
||||||
|
context.addFieldError(eventKey.value, errorMap.message)
|
||||||
|
}
|
||||||
|
return errorMap.message
|
||||||
|
})
|
||||||
|
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFieldBlur = () => {
|
||||||
|
// validateRules()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onFieldChange = () => {
|
||||||
|
validateRules()
|
||||||
|
emit('change')
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => globalErrorMessage.value, (val) => {
|
||||||
|
if (val[eventKey.value]) {
|
||||||
|
showErrorTip(val[eventKey.value])
|
||||||
|
} else {
|
||||||
|
hideErrorTip()
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true})
|
||||||
|
|
||||||
|
useProvideFormItemContext({
|
||||||
|
id: filedId,
|
||||||
|
onFieldChange,
|
||||||
|
onFieldBlur,
|
||||||
|
}, computed(() => get(context.dataSource.value, props.name)))
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
hideErrorTip()
|
||||||
|
// context.removeField(eventKey.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => [filedName.value, props.name], () => {
|
||||||
|
context.addField(eventKey.value, {
|
||||||
|
filedName: filedName.value,
|
||||||
|
eventKey: eventKey.value,
|
||||||
|
names: props.name,
|
||||||
|
validateRules,
|
||||||
|
showErrorTip
|
||||||
|
})
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.table-form-error-target {
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
top: -9px;
|
||||||
|
border: 16px solid transparent;
|
||||||
|
border-top-color: @error-color;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.edit-table-form-has-error {
|
||||||
|
|
||||||
|
.select-no-value {
|
||||||
|
.ant-select-selector {
|
||||||
|
border-color: @error-color !important;
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 2px var(--ant-error-color-outline) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> input {
|
||||||
|
border-color: @error-color !important;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 2px var(--ant-error-color-outline) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-form-required-aicon {
|
||||||
|
color: @error-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,282 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="dataSource.length"
|
||||||
|
class="metadata-edit-table-body-viewport" :style="{ ...style, height: height + 'px'}" ref="viewScrollRef" @scroll="onScroll">
|
||||||
|
<div :style="{position: 'relative'}">
|
||||||
|
<div class="metadata-edit-scrollbar" :style="containerStyle"> </div>
|
||||||
|
<div class="metadata-edit-table-center" ref="tableCenterRef" >
|
||||||
|
<div
|
||||||
|
v-if="virtualData.length"
|
||||||
|
v-for="(item, index) in virtualData"
|
||||||
|
:class="{
|
||||||
|
'metadata-edit-table-row': true,
|
||||||
|
'metadata-edit-table-row-selected': selectedRowKeys?.includes(item[rowKey] || virtualRang.start + index + 1)
|
||||||
|
}"
|
||||||
|
:key="`record_${item.__key}`"
|
||||||
|
:style="{height: `${cellHeight}px`,}"
|
||||||
|
:data-row-key="item[rowKey] || virtualRang.start + index + 1"
|
||||||
|
@click.right.native="(e) => showContextMenu(e,item, virtualRang.start + index)"
|
||||||
|
@click.stop="() => rowClick(item)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="column in columns"
|
||||||
|
class="metadata-edit-table-cell"
|
||||||
|
:style="{
|
||||||
|
width: `${column.width}px`,
|
||||||
|
left: `${column.left}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div v-if="column.dataIndex === '__serial'" class="body-cell-box">
|
||||||
|
<slot name="serial" :record="item" :index="item.__dataIndex" :column="column" >
|
||||||
|
{{ virtualRang.start + index + 1 }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div v-else class="body-cell-box">
|
||||||
|
<slot :name="column.dataIndex" :record="item" :index="item.__dataIndex" :column="column" >
|
||||||
|
{{ item[column.dataIndex] }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<slot name="empty">
|
||||||
|
<div class="metadata-edit-table-body-empty">
|
||||||
|
<j-empty />
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataBaseTableBody">
|
||||||
|
import ContextMenu from './components/ContextMenu'
|
||||||
|
import {useRightMenuContext} from "@/components/Metadata/Table/context";
|
||||||
|
import {randomString} from "@/utils/utils";
|
||||||
|
import {bodyProps} from "./props";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
...bodyProps(),
|
||||||
|
groupKey: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:dataSource', 'scrollDown'])
|
||||||
|
|
||||||
|
const viewScrollRef = ref()
|
||||||
|
const tableCenterRef = ref()
|
||||||
|
// const virtualData = ref([])
|
||||||
|
const virtualRang = reactive({
|
||||||
|
start: 0,
|
||||||
|
end: 15
|
||||||
|
})
|
||||||
|
const containerStyle = ref(0)
|
||||||
|
const context = useRightMenuContext()
|
||||||
|
|
||||||
|
let scrollLock = ref(false)
|
||||||
|
let menuInstance
|
||||||
|
|
||||||
|
const maxLen = computed(() => {
|
||||||
|
return Math.trunc(props.height / props.cellHeight)
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedRowKeys = ref([])
|
||||||
|
|
||||||
|
const virtualData = computed(()=> {
|
||||||
|
|
||||||
|
const array = props.dataSource.slice(virtualRang.start, virtualRang.end)
|
||||||
|
if (tableCenterRef.value) {
|
||||||
|
tableCenterRef.value.style.webkitTransform = `translate3d(0, ${virtualRang.start * props.cellHeight}px, 0)`
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
})
|
||||||
|
// const updateVirtualData = (start, end) => {
|
||||||
|
// debugger
|
||||||
|
// virtualData.value = props.dataSource.slice(start, end)
|
||||||
|
// if (tableCenterRef.value) {
|
||||||
|
// tableCenterRef.value.style.webkitTransform = `translate3d(0, ${start * props.cellHeight}px, 0)`
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const onScroll = () => {
|
||||||
|
const height = viewScrollRef.value.scrollTop
|
||||||
|
const clientHeight = viewScrollRef.value.clientHeight
|
||||||
|
const scrollHeight = viewScrollRef.value.scrollHeight
|
||||||
|
|
||||||
|
const _index = Math.round(height / props.cellHeight) - 1
|
||||||
|
|
||||||
|
|
||||||
|
const start = _index < 0 ? 0 : _index
|
||||||
|
const end = start + maxLen.value + 4
|
||||||
|
|
||||||
|
if (height + clientHeight >= props.dataSource.length * props.cellHeight && !scrollLock.value) { // 滚动到底
|
||||||
|
emit('scrollDown')
|
||||||
|
scrollLock.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualRang.start = start
|
||||||
|
virtualRang.end = end
|
||||||
|
// updateVirtualData(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollTo = (index) => {
|
||||||
|
if (viewScrollRef.value) {
|
||||||
|
let top = index * props.cellHeight
|
||||||
|
viewScrollRef.value.scrollTop = top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const showContextMenu = (e, record, _index) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (props.disableMenu) {
|
||||||
|
record = {
|
||||||
|
...record,
|
||||||
|
__index: _index
|
||||||
|
}
|
||||||
|
menuInstance = ContextMenu(e, record, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const updateView = () => {
|
||||||
|
// updateVirtualData(virtualRang.start, virtualRang.start + maxLen.value)
|
||||||
|
// }
|
||||||
|
|
||||||
|
const rowClick = (record) => {
|
||||||
|
if (props.rowSelection?.selectedRowKeys) {
|
||||||
|
const rowSet = new Set(selectedRowKeys.value)
|
||||||
|
const key = record[props.rowKey]
|
||||||
|
const selected = !rowSet.has(key)
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
rowSet.delete(key)
|
||||||
|
} else {
|
||||||
|
rowSet.add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
props.rowSelection.onSelect?.(record, selected )
|
||||||
|
|
||||||
|
selectedRowKeys.value = [...rowSet.values()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSelectedKeys = (keys) => {
|
||||||
|
selectedRowKeys.value = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
onScroll()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
menuInstance?.destroy()
|
||||||
|
menuInstance?.cleanCopy()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => JSON.stringify(props.rowSelection?.selectedRowKeys), (val) => {
|
||||||
|
selectedRowKeys.value = JSON.parse(val || '[]')
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
watch(() => props.dataSource, (val, oldVal) => {
|
||||||
|
|
||||||
|
props.dataSource.forEach((item, index) => {
|
||||||
|
if (!item.__key) {
|
||||||
|
item.__key = randomString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// updateView()
|
||||||
|
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.dataSource.length, () => {
|
||||||
|
scrollLock.value = false
|
||||||
|
containerStyle.value = {
|
||||||
|
height: props.dataSource.length * props.cellHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.dataSource.length <= maxLen.value || props.dataSource.length === 0) {
|
||||||
|
emit('scrollDown', maxLen.value - props.dataSource.length + 3)
|
||||||
|
}
|
||||||
|
}, { immediate: true})
|
||||||
|
|
||||||
|
// watch(() => props.height, () => {
|
||||||
|
// updateView()
|
||||||
|
// })
|
||||||
|
|
||||||
|
watch(() => props.groupKey, () => {
|
||||||
|
if (props.openGroup) {
|
||||||
|
scrollTo(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
scrollTo,
|
||||||
|
updateSelectedKeys
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.metadata-edit-table-body-viewport {
|
||||||
|
max-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden auto;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.metadata-edit-scrollbar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-edit-table-body-container {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-edit-table-center {
|
||||||
|
position: relative;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.metadata-edit-table-row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
//position: absolute;
|
||||||
|
transition: top .2s, height .2s, background-color .1s;
|
||||||
|
border-bottom: 1px solid #ebebeb;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(248, 248, 248);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.metadata-edit-table-row-selected {
|
||||||
|
background-color: var(--ant-primary-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-cell-box {
|
||||||
|
padding: 0 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.metadata-edit-table-body-empty {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,216 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 450px">
|
||||||
|
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<a-form-item label="元素类型" required name="type" :rules="rules" :validate-first="true">
|
||||||
|
<TypeSelect v-model:value="formData.type"/>
|
||||||
|
</a-form-item>
|
||||||
|
<ScaleItem v-if="showDouble" v-model:value="formData.scale" />
|
||||||
|
<StringItem v-else-if="showString" v-model:value="formData.expands.maxLength" />
|
||||||
|
<BooleanItem v-else-if="showBoolean" v-model:value="formData.boolean" name="boolean"/>
|
||||||
|
<DateItem v-else-if="showDate" v-model:value="formData.format"/>
|
||||||
|
<EnumItem ref="enumTableRef" v-else-if="showEnum" v-model:value="formData.enum.elements"/>
|
||||||
|
<a-form-item v-else-if="showArray" label="子元素类型" required :name="['elementType','type']" :rules="[{ required: true, message: '请选择子元素类型'}]">
|
||||||
|
<TypeSelect v-model:value="formData.elementType.type" :filter="['array', 'object']" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined" :class="{'table-form-required-aicon': !value.type}"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataArray">
|
||||||
|
import { PopoverModal, TypeSelect } from '../index'
|
||||||
|
import ScaleItem from '../Double/ScaleItem.vue'
|
||||||
|
import StringItem from '../String/Item.vue'
|
||||||
|
import BooleanItem from '../Boolean/Item.vue'
|
||||||
|
import DateItem from '../Date/Item.vue'
|
||||||
|
import EnumItem from '../Enum/Item.vue'
|
||||||
|
import {cloneDeep, pick} from 'lodash-es'
|
||||||
|
import {Form} from "ant-design-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'cancel', 'confirm']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
unitOptions: {
|
||||||
|
type: [Array, Function],
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const enumTableRef = ref()
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
type: props.value?.type,
|
||||||
|
scale: props.value?.scale,
|
||||||
|
expands: {
|
||||||
|
maxLength: props.value?.maxLength || props.value?.expands?.maxLength,
|
||||||
|
},
|
||||||
|
boolean: {
|
||||||
|
trueText: props.value?.trueText || '是',
|
||||||
|
trueValue: props.value?.trueValue || 'true',
|
||||||
|
falseText: props.value?.falseText || '否',
|
||||||
|
falseValue: props.value?.falseValue || 'false',
|
||||||
|
},
|
||||||
|
format: props.value?.format,
|
||||||
|
enum: {
|
||||||
|
multiple: props.value?.multiple,
|
||||||
|
elements: cloneDeep(props.value?.elements) || [],
|
||||||
|
},
|
||||||
|
elementType: props.value?.type === 'array' ? props.value.elementType : {
|
||||||
|
type: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const showDouble = computed(() => {
|
||||||
|
return ['float', 'double'].includes(formData.type)
|
||||||
|
})
|
||||||
|
|
||||||
|
const showString = computed(() => {
|
||||||
|
return ['string', 'password'].includes(formData.type)
|
||||||
|
})
|
||||||
|
|
||||||
|
const showBoolean = computed(() => {
|
||||||
|
return formData.type === 'boolean'
|
||||||
|
})
|
||||||
|
|
||||||
|
const showDate = computed(() => {
|
||||||
|
return formData.type === 'date'
|
||||||
|
})
|
||||||
|
|
||||||
|
const showEnum = computed(() => {
|
||||||
|
return formData.type === 'enum'
|
||||||
|
})
|
||||||
|
|
||||||
|
const showArray = computed(() => {
|
||||||
|
return formData.type === 'array'
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请选择元素类型');
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
trigger: 'change',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const typeChange = (e) => {
|
||||||
|
if (['float', 'double'].includes(e)) {
|
||||||
|
formData.scale = 2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initValue = () => {
|
||||||
|
formData.type = props.value?.type;
|
||||||
|
formData.scale = props.value?.scale;
|
||||||
|
formData.expands.maxLength = props.value?.maxLength || props.value?.expands?.maxLength;
|
||||||
|
formData.boolean = {
|
||||||
|
trueText: props.value?.trueText || '是',
|
||||||
|
trueValue: props.value?.trueValue || 'true',
|
||||||
|
falseText: props.value?.falseText || '否',
|
||||||
|
falseValue: props.value?.falseValue || 'false',
|
||||||
|
};
|
||||||
|
formData.format = props.value?.format;
|
||||||
|
formData.enum = {
|
||||||
|
multiple: props.value?.multiple,
|
||||||
|
elements: cloneDeep(props.value?.elements || []),
|
||||||
|
};
|
||||||
|
|
||||||
|
formData.elementType = props.value?.type === 'array' ? props.value.elementType : {
|
||||||
|
type: undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleValue = (type, data) => {
|
||||||
|
let newObject = {};
|
||||||
|
switch (type) {
|
||||||
|
case 'float':
|
||||||
|
case 'double':
|
||||||
|
newObject = pick(data, 'scale');
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
newObject = { ...data.boolean };
|
||||||
|
break;
|
||||||
|
case 'enum':
|
||||||
|
newObject.elements = data.enum.elements;
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
case 'password':
|
||||||
|
newObject = pick(data, 'expands');
|
||||||
|
break;
|
||||||
|
case 'date':
|
||||||
|
newObject = pick(data, 'format');
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
newObject = pick(data, 'elementType')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: type,
|
||||||
|
...newObject,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
let enumTable = true
|
||||||
|
if (enumTableRef.value) {
|
||||||
|
enumTable = !!(await enumTableRef.value.validate())
|
||||||
|
}
|
||||||
|
if (data && enumTable) {
|
||||||
|
|
||||||
|
visible.value = false
|
||||||
|
const _value = handleValue(formData.type, formData)
|
||||||
|
emit('update:value', _value);
|
||||||
|
emit('confirm', _value);
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
initValue();
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => JSON.stringify(props.value), () => {
|
||||||
|
initValue()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,67 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item
|
||||||
|
label="布尔值"
|
||||||
|
required
|
||||||
|
:name="name"
|
||||||
|
:rules="rules"
|
||||||
|
:validate-first="true"
|
||||||
|
>
|
||||||
|
<Content
|
||||||
|
v-model:value="myValue"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataBooleanItem">
|
||||||
|
import Content from './ItemContext.vue'
|
||||||
|
|
||||||
|
const emits = defineEmits([
|
||||||
|
'update:value',
|
||||||
|
'change'
|
||||||
|
])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'value',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const myValue = ref(props.value)
|
||||||
|
|
||||||
|
const rules = [{
|
||||||
|
validator(_, v) {
|
||||||
|
const isMax = Object.values(v).some(
|
||||||
|
(item) => item.length > 64,
|
||||||
|
);
|
||||||
|
const isNull = Object.values(v).some((item) => !item);
|
||||||
|
if (isMax) {
|
||||||
|
return Promise.reject('最多可输入64个字符');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNull) {
|
||||||
|
return Promise.reject('请输入布尔值');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
emits(`update:value`, myValue.value)
|
||||||
|
emits(`change`, myValue.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => JSON.stringify(props.value), () => {
|
||||||
|
myValue.value = props.value
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<div class="boolean-items">
|
||||||
|
<div class="boolean-item boolean-true">
|
||||||
|
<div class="boolean-true-text">
|
||||||
|
<a-input v-model:value="formData.trueText" @change="valueChange"/>
|
||||||
|
</div>
|
||||||
|
<span>-</span>
|
||||||
|
<div class="boolean-true-value">
|
||||||
|
<a-input v-model:value="formData.trueValue" @change="valueChange"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="boolean-item boolean-false">
|
||||||
|
<div class="boolean-false-text">
|
||||||
|
<a-input v-model:value="formData.falseText" @change="valueChange"/>
|
||||||
|
</div>
|
||||||
|
<span>-</span>
|
||||||
|
<div class="boolean-false-value">
|
||||||
|
<a-input v-model:value="formData.falseValue" @change="valueChange"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="BooleanItemContext">
|
||||||
|
|
||||||
|
const emits = defineEmits([
|
||||||
|
'update:value',
|
||||||
|
'change'
|
||||||
|
])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
trueText: props.value?.trueText || '是',
|
||||||
|
trueValue: props.value?.trueValue || 'true',
|
||||||
|
falseText: props.value?.falseText || '否',
|
||||||
|
falseValue: props.value?.falseValue || 'false',
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueChange = () => {
|
||||||
|
emits(`update:value`, formData)
|
||||||
|
emits(`change`, formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => JSON.stringify(props.value), () => {
|
||||||
|
formData.trueText = props.value?.trueText;
|
||||||
|
formData.trueValue = props.value?.trueValue;
|
||||||
|
formData.falseText = props.value?.falseText;
|
||||||
|
formData.falseValue = props.value?.falseValue;
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.boolean-items {
|
||||||
|
.boolean-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.boolean-true {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 250px">
|
||||||
|
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<Item v-model:value="formData.value" />
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataBoolean">
|
||||||
|
import { PopoverModal } from '../index'
|
||||||
|
import Item from './Item.vue'
|
||||||
|
import {Form} from "ant-design-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
'update:trueText',
|
||||||
|
'update:trueValue',
|
||||||
|
'update:falseText',
|
||||||
|
'update:falseValue',
|
||||||
|
'confirm',
|
||||||
|
'cancel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
trueText: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
trueValue: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
falseText: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
falseValue: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
value: {
|
||||||
|
trueText: props.trueText || '是',
|
||||||
|
trueValue: props.trueValue || 'true',
|
||||||
|
falseText: props.falseText || '否',
|
||||||
|
falseValue: props.falseValue || 'false',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
if (data) {
|
||||||
|
visible.value = false
|
||||||
|
emit('update:trueText', formData.value.trueText);
|
||||||
|
emit('update:trueValue', formData.value.trueValue);
|
||||||
|
emit('update:falseText', formData.value.falseText);
|
||||||
|
emit('update:falseValue', formData.value.falseValue);
|
||||||
|
emit('confirm', formData.value);
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formData.value.trueText = props.trueText || '是'
|
||||||
|
formData.value.trueValue = props.trueValue || 'true'
|
||||||
|
formData.value.falseText = props.falseText || '否'
|
||||||
|
formData.value.falseValue = props.falseValue || 'false'
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => [
|
||||||
|
props.trueText,
|
||||||
|
props.trueValue,
|
||||||
|
props.falseText,
|
||||||
|
props.falseValue,
|
||||||
|
], () => {
|
||||||
|
formData.value.trueText = props.trueText || '是'
|
||||||
|
formData.value.trueValue = props.trueValue || 'true'
|
||||||
|
formData.value.falseText = props.falseText || '否'
|
||||||
|
formData.value.falseValue = props.falseValue || 'false'
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,80 @@
|
||||||
|
<template>
|
||||||
|
<j-select
|
||||||
|
v-model:value="myValue"
|
||||||
|
style="width: 100%;"
|
||||||
|
:options="options"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
@change="change"
|
||||||
|
>
|
||||||
|
|
||||||
|
</j-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="BooleanSelect">
|
||||||
|
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
|
import {isBoolean} from "lodash-es";
|
||||||
|
import { selectProps } from 'ant-design-vue/lib/select'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
...selectProps(),
|
||||||
|
value: {
|
||||||
|
type: [Boolean, Number, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
trueLabel: {
|
||||||
|
type: String,
|
||||||
|
default: '必填',
|
||||||
|
},
|
||||||
|
falseLabel: {
|
||||||
|
type: String,
|
||||||
|
default: '不必填',
|
||||||
|
},
|
||||||
|
trueValue: {
|
||||||
|
type: [Boolean, Number, String],
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
falseValue: {
|
||||||
|
type: [Boolean, Number, String],
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'change'])
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
|
||||||
|
const myValue = ref()
|
||||||
|
|
||||||
|
const options = computed(() => {
|
||||||
|
|
||||||
|
const _trueValue = isBoolean(props.trueValue) ? String(props.trueValue) : props.trueValue
|
||||||
|
const _falseValue = isBoolean(props.falseValue) ? String(props.falseValue) : props.falseValue
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: props.trueLabel,
|
||||||
|
value: _trueValue,
|
||||||
|
baseValue: props.trueValue
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: props.falseLabel,
|
||||||
|
value: _falseValue,
|
||||||
|
baseValue: props.falseValue
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const change = (e) => {
|
||||||
|
const item = options.value.find(item => item.value === myValue.value)
|
||||||
|
emit('update:value', item.baseValue)
|
||||||
|
emit('change', item.baseValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => [props.value, options.value], () => {
|
||||||
|
const item = options.value.find(item => item.baseValue === props.value)
|
||||||
|
myValue.value = item ? item.value : options.value[0].value
|
||||||
|
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,90 @@
|
||||||
|
import MenuContext from './menu.vue'
|
||||||
|
import { h, render } from 'vue'
|
||||||
|
import {handlePureRecord} from "@/components/Metadata/Table/utils";
|
||||||
|
import {omit} from "lodash-es";
|
||||||
|
|
||||||
|
let curInstance: Record<string, any> | null = null
|
||||||
|
let seed = 1
|
||||||
|
let copyValue: any
|
||||||
|
const contextMenu = (e: Event, data: any, context: any) => {
|
||||||
|
if (curInstance) {
|
||||||
|
curInstance.destroy()
|
||||||
|
}
|
||||||
|
curInstance = null
|
||||||
|
|
||||||
|
let id = seed++
|
||||||
|
// 创建一个临时的div,用于挂载我们的菜单
|
||||||
|
const container = document.createElement('div') as HTMLElement
|
||||||
|
// 获取body标签,用于挂载整个菜单
|
||||||
|
const appendTo = context.getPopupContainer() || document.body
|
||||||
|
// 传给menu组件的props
|
||||||
|
const props = {
|
||||||
|
data: data,
|
||||||
|
onClose: () => {
|
||||||
|
if(curInstance){
|
||||||
|
curInstance.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick: (type: string) => {
|
||||||
|
const copyRecord = handlePureRecord(copyValue)
|
||||||
|
if (copyRecord.expands) {
|
||||||
|
copyRecord.expands = omit(copyRecord.expands, ['isProduct'])
|
||||||
|
}
|
||||||
|
context.click(type, data, handlePureRecord(copyRecord))
|
||||||
|
},
|
||||||
|
onCopy: (data: any) => {
|
||||||
|
copyValue = data
|
||||||
|
context.click('copy', data)
|
||||||
|
},
|
||||||
|
paste: !!copyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染虚拟节点
|
||||||
|
const vnode = h(
|
||||||
|
MenuContext,
|
||||||
|
props
|
||||||
|
)
|
||||||
|
// vnode为需要渲染的虚拟节点,container为渲染的容器
|
||||||
|
render(vnode, container)
|
||||||
|
// 首先需要先把菜单真正渲染到页面,才能拿到它的宽度和高度
|
||||||
|
appendTo.appendChild(container.firstElementChild as Node)
|
||||||
|
// 当前真正的菜单节点,上面输出的vnode中可以看到,el就是我们的菜单节点
|
||||||
|
const curMenu = vnode.el!
|
||||||
|
// 获取curMenu的高度和宽度,用于临界的计算
|
||||||
|
const { offsetWidth, offsetHeight } = curMenu!
|
||||||
|
// 获取body的可视区域的宽度
|
||||||
|
const { clientWidth } = appendTo
|
||||||
|
// 取出右键点击时的坐标,clientX是距离左侧的位置,clientY是距离顶部的位置
|
||||||
|
const { clientX, clientY } = e
|
||||||
|
|
||||||
|
// 当前可视区域的宽度 - 当前鼠标距离浏览器左边的距离
|
||||||
|
// 如果 大于菜单的宽度,说明正常设置菜单距离左边界的距离,即设置style.left
|
||||||
|
// 否则菜单需要在鼠标左侧展示,即需要设置style.right组件距离可视区域右侧的距离
|
||||||
|
const leftOrRight = clientWidth - clientX > offsetWidth ? "left" : "right"
|
||||||
|
|
||||||
|
// 当前浏览器的高度(不包含滚动条) - 当前鼠标距离浏览器上边的距离
|
||||||
|
// 如果 大于菜单的高度,说明可以正常设置菜单距离上边界的距离,即设置style.top
|
||||||
|
// 否则需要设置菜单距离底部边界的位置,即style.bottom
|
||||||
|
const topOrBottom = window.innerHeight - clientY > offsetHeight ? "top" : "bottom"
|
||||||
|
const offsetLeft = Math.abs(clientWidth - clientX)
|
||||||
|
// 设置left或者right的style
|
||||||
|
curMenu.style[leftOrRight] = leftOrRight === "left" ? `${clientX + 20}px` : `${offsetLeft}px`
|
||||||
|
// 设置top或者bottom的style
|
||||||
|
curMenu.style[topOrBottom] = topOrBottom === 'bottom' ? '2px' : `${clientY}px`
|
||||||
|
|
||||||
|
const instance = {
|
||||||
|
id,
|
||||||
|
destroy: () => {
|
||||||
|
curInstance = null
|
||||||
|
render(null, container)
|
||||||
|
},
|
||||||
|
cleanCopy: () => {
|
||||||
|
copyValue = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curInstance = instance
|
||||||
|
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
export default contextMenu
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="metadata-context-menu"
|
||||||
|
ref="contextMenu"
|
||||||
|
tabindex="-1"
|
||||||
|
@blur="close"
|
||||||
|
>
|
||||||
|
<a-menu @click="clickFunc">
|
||||||
|
<a-menu-item key="add">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="PlusSquareOutlined" />
|
||||||
|
</template>
|
||||||
|
新增行
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="copy">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="icon-copy" />
|
||||||
|
</template>
|
||||||
|
复制行
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="paste" :disabled="showPaste">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="icon-paste" />
|
||||||
|
</template>
|
||||||
|
粘贴行
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="detail" :disabled="showDetail">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="icon-chakan" />
|
||||||
|
</template>
|
||||||
|
查看详情
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="delete" class="danger" :disabled="showDelete">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</template>
|
||||||
|
删除
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataContextMenu">
|
||||||
|
import { onMounted, ref, nextTick } from "vue";
|
||||||
|
import { AIcon } from 'jetlinks-ui-components'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {type: Object, default: () => ({})},
|
||||||
|
onClose: { type: Function, default: () => {} },
|
||||||
|
onClick: { type: Function, default: () => {} },
|
||||||
|
onCopy: { type: Function, default: () => {} },
|
||||||
|
paste: { type: Object, default: () => ({}) }
|
||||||
|
});
|
||||||
|
|
||||||
|
const contextMenu = ref(null);
|
||||||
|
|
||||||
|
const showDetail = computed(() => {
|
||||||
|
return !props.data.id
|
||||||
|
})
|
||||||
|
|
||||||
|
const showPaste = computed(() => {
|
||||||
|
return !props.paste
|
||||||
|
})
|
||||||
|
|
||||||
|
const showDelete = computed(() => {
|
||||||
|
return props.data.expands?.isProduct
|
||||||
|
})
|
||||||
|
|
||||||
|
const clickFunc = ({ key }) => {
|
||||||
|
if (key === 'copy') {
|
||||||
|
props.onCopy(props.data)
|
||||||
|
}
|
||||||
|
props.onClick(key)
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = (e) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
props.onClose()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// 确保组件已经渲染
|
||||||
|
await nextTick();
|
||||||
|
// 触发组件focus
|
||||||
|
contextMenu.value.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.metadata-context-menu{
|
||||||
|
position: fixed;
|
||||||
|
box-shadow: 0 0 12px rgba(0, 0, 0 ,.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 192px;
|
||||||
|
padding: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
:deep(.ant-menu) {
|
||||||
|
border-right: none;
|
||||||
|
|
||||||
|
.ant-menu-item {
|
||||||
|
margin: 0;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
color: @error-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-menu-item-active) {
|
||||||
|
background-color: var(--ant-primary-1);;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item
|
||||||
|
label="时间格式"
|
||||||
|
required
|
||||||
|
:name="name"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择时间格式',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<AutoComplete
|
||||||
|
v-model:value="date"
|
||||||
|
:options="options"
|
||||||
|
mode="tags"
|
||||||
|
placeholder="请选择时间格式"
|
||||||
|
:dropdownStyle="{ zIndex: 1072}"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataDateItem">
|
||||||
|
import { AutoComplete } from 'jetlinks-ui-components'
|
||||||
|
import {useTableWrapper} from "components/Metadata/Table/context";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'format',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ label: 'yyyy-MM-dd HH:mm:ss', value: 'yyyy-MM-dd HH:mm:ss' },
|
||||||
|
{ label: 'yyyy-MM-dd', value: 'yyyy-MM-dd' },
|
||||||
|
{ label: 'hh:mm:ss', value: 'hh:mm:ss' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const date = ref(props.value);
|
||||||
|
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const change = () => {
|
||||||
|
emit('update:value', date.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
date.value = props.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 200px">
|
||||||
|
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<Item v-model:value="formData.format" />
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined" :class="{'table-form-required-aicon': !value}"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataDate">
|
||||||
|
import { PopoverModal } from '../index'
|
||||||
|
import Item from './Item.vue'
|
||||||
|
import { Form } from 'ant-design-vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'confirm', 'cancel']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
const formRef = ref()
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
format: props.value,
|
||||||
|
})
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
visible.value = false
|
||||||
|
emit('update:value', formData.format);
|
||||||
|
emit('confirm', formData.format);
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formData.format = props.value;
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, (newValue) => {
|
||||||
|
formData.format = newValue
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item
|
||||||
|
label="精度"
|
||||||
|
:name="name"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="scale"
|
||||||
|
style="width: 100%"
|
||||||
|
:precision="0"
|
||||||
|
:min="0"
|
||||||
|
:max="99"
|
||||||
|
placeholder="请输入0-99以内的整数"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataScaleItem">
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'scale',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const scale = ref(props.value);
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
emit('update:value', scale.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
scale.value = props.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 200px">
|
||||||
|
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<a-form-item label="单位" name="unit" :rules="[{ max: 64, message: '最多可输入64个字符' }]">
|
||||||
|
<UnitSelect v-model:value="formData.unit"/>
|
||||||
|
</a-form-item>
|
||||||
|
<ScaleItem v-model:value="formData.scale" />
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataDouble">
|
||||||
|
import { UnitSelect, PopoverModal } from '../index'
|
||||||
|
import ScaleItem from './ScaleItem.vue'
|
||||||
|
import {Form} from "ant-design-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'cancel', 'confirm']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
unit: props.value?.unit,
|
||||||
|
scale: props.value?.scale || 0, // 精度
|
||||||
|
});
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
if (data) {
|
||||||
|
visible.value = false
|
||||||
|
emit('update:value', {
|
||||||
|
...props.value,
|
||||||
|
...formData
|
||||||
|
});
|
||||||
|
emit('confirm', {
|
||||||
|
...props.value,
|
||||||
|
...formData
|
||||||
|
});
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formData.unit = props.value?.unit;
|
||||||
|
formData.scale = props.value?.scale || 0;
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, (newValue) => {
|
||||||
|
formData.unit = props.value?.unit;
|
||||||
|
formData.scale = props.value?.scale || 0;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item :name="name" :rules="rules" :validate-first="true">
|
||||||
|
<template #label>
|
||||||
|
<span style="color: #ff4d4f; padding-right: 4px; padding-top: 2px">*</span>
|
||||||
|
枚举项
|
||||||
|
</template>
|
||||||
|
<Content ref="tableRef" v-model:value="dataSource" @change="change" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataEnumItem">
|
||||||
|
import Content from './ItemContent.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value'])
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'elements',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const dataSource = ref(props.value || [])
|
||||||
|
const tableRef = ref()
|
||||||
|
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
validator: async (_, value) =>{
|
||||||
|
console.log(value, dataSource.value)
|
||||||
|
if (!dataSource.value?.length) {
|
||||||
|
return Promise.reject('请添加枚举项');
|
||||||
|
}
|
||||||
|
// console.log(tableRef, tableRef.value)
|
||||||
|
// const data = await tableRef.value?.validate?.()
|
||||||
|
// if (!data) {
|
||||||
|
// return Promise.reject('');
|
||||||
|
// }
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
emit('update:value', dataSource.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = async () => {
|
||||||
|
const res = await tableRef.value?.validate()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validate
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => JSON.stringify(props.value),
|
||||||
|
(val) => {
|
||||||
|
dataSource.value = val ? JSON.parse(val) : [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,150 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<EditTable
|
||||||
|
ref="tableRef"
|
||||||
|
rowKey="value"
|
||||||
|
:columns="columns"
|
||||||
|
:dataSource="dataSource"
|
||||||
|
:pagination="false"
|
||||||
|
:height="200"
|
||||||
|
:disableMenu="false"
|
||||||
|
:validateRowKey="true"
|
||||||
|
>
|
||||||
|
<template #value="{ record, index }">
|
||||||
|
<EditTableFormItem
|
||||||
|
:name="[index, 'value']"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="record.value" @change="valueChange"/>
|
||||||
|
</EditTableFormItem>
|
||||||
|
</template>
|
||||||
|
<template #text="{ record, index }">
|
||||||
|
<EditTableFormItem
|
||||||
|
:name="[index, 'text']"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="record.text" @change="valueChange"/>
|
||||||
|
</EditTableFormItem>
|
||||||
|
</template>
|
||||||
|
<template #action="{ index }">
|
||||||
|
<a-button danger type="link" @click="() => deleteItem(index)">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</EditTable>
|
||||||
|
<a-button class="enum-table-add" @click="addItem">
|
||||||
|
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||||
|
新增枚举项
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="EnumItemContent">
|
||||||
|
import EditTable from '../../Table.vue'
|
||||||
|
import EditTableFormItem from '../../TableFormItem.vue'
|
||||||
|
import { Form } from "ant-design-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'change'])
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const dataSource = ref(props.value || [])
|
||||||
|
const tableRef = ref()
|
||||||
|
|
||||||
|
const columns = [{
|
||||||
|
title: 'Value',
|
||||||
|
dataIndex: 'value',
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
|
{
|
||||||
|
asyncValidator: (rule, value, ...setting) => {
|
||||||
|
const option = setting[2]
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入Value值')
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSome = dataSource.value.some((item) => {
|
||||||
|
return item.__dataIndex !== option.index && item.value === value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isSome) {
|
||||||
|
return Promise.reject('该Value值已存在')
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Text',
|
||||||
|
dataIndex: 'text',
|
||||||
|
width: 150,
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入Text' },
|
||||||
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const deleteItem = (index) => {
|
||||||
|
dataSource.value.splice(index, 1)
|
||||||
|
emit('update:value', dataSource.value)
|
||||||
|
emit('change', dataSource.value)
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
dataSource.value.push({
|
||||||
|
value: undefined,
|
||||||
|
text: undefined
|
||||||
|
})
|
||||||
|
emit('update:value', dataSource.value)
|
||||||
|
emit('change', dataSource.value)
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueChange = () => {
|
||||||
|
emit('update:value', dataSource.value)
|
||||||
|
emit('change', dataSource.value)
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = async () => {
|
||||||
|
const res = await tableRef.value?.validate()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validate
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => JSON.stringify(props.value),
|
||||||
|
() => {
|
||||||
|
dataSource.value = props.value || [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.enum-table-add {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 450px" v-if="visible">
|
||||||
|
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<Item ref="tableRef" :value="formData.elements"/>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined" :class="{'table-form-required-aicon': !value.length}"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataEnum">
|
||||||
|
import { PopoverModal } from '../index'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import Item from './Item.vue'
|
||||||
|
import {Form} from "ant-design-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'confirm', 'cancel']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ([]),
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const tableRef = ref();
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
elements: cloneDeep(props.value) || [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formData.elements = cloneDeep(props.value) || [];
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
const tableData = await tableRef.value.validate()
|
||||||
|
console.log(data, tableData)
|
||||||
|
if (data && tableData) {
|
||||||
|
visible.value = false
|
||||||
|
formData.elements = tableData
|
||||||
|
emit('update:value', formData.elements)
|
||||||
|
emit('confirm', formData.elements);
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => JSON.stringify(props.value),
|
||||||
|
() => {
|
||||||
|
formData.elements = cloneDeep(props.value) || [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item
|
||||||
|
label="文件类型"
|
||||||
|
required
|
||||||
|
:name="name"
|
||||||
|
:rules="[{ required: true, message: '请选择文件类型' }]"
|
||||||
|
>
|
||||||
|
<j-check-button
|
||||||
|
v-model:value="myValue"
|
||||||
|
:options="[
|
||||||
|
{ label: 'URL', value: 'url' },
|
||||||
|
{ label: 'Base64', value: 'base64' },
|
||||||
|
{ label: 'binary', value: 'binary' },
|
||||||
|
]"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataFileType">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: 'bodyType',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
|
||||||
|
const myValue = ref(props.value);
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
emit('update:value', myValue.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
myValue.value = props.value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,81 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 250px">
|
||||||
|
<a-form ref="formRef" :model="formData" layout="vertical">
|
||||||
|
<Type v-model:value="formData.bodyType" name="bodyType" />
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined" :class="{'table-form-required-aicon': !value}"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataFile">
|
||||||
|
import Type from './Type.vue'
|
||||||
|
import { PopoverModal } from '../index'
|
||||||
|
import {Form} from "ant-design-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'confirm', 'cancel']);
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const formData = reactive({
|
||||||
|
bodyType: props.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
if (data) {
|
||||||
|
visible.value = false
|
||||||
|
emit('update:value', formData.bodyType)
|
||||||
|
emit('onOk', formData.bodyType)
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formData.bodyType = props.value;
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
() => {
|
||||||
|
formData.bodyType = props.value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<a-auto-complete
|
||||||
|
v-model:value="myValue"
|
||||||
|
:options="options"
|
||||||
|
placeholder="请选择分组"
|
||||||
|
style="width: 100%;"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
@search="onSearch"
|
||||||
|
@select="onSelect"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataGroup">
|
||||||
|
|
||||||
|
import {METADATA_GROUP_OPTIONS} from "../../consts";
|
||||||
|
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'change'])
|
||||||
|
const myValue = ref(props.value)
|
||||||
|
const searchValue = ref()
|
||||||
|
|
||||||
|
const groupSetting = inject(METADATA_GROUP_OPTIONS, {})
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
|
||||||
|
const options = computed(() => {
|
||||||
|
if (searchValue.value) {
|
||||||
|
let _options = (groupSetting.options.value || []).filter(item => {
|
||||||
|
return item.value.includes(searchValue.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!_options.length) {
|
||||||
|
_options.unshift({
|
||||||
|
label: searchValue.value,
|
||||||
|
value: searchValue.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return _options
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupSetting.options.value || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSearch = (value) => {
|
||||||
|
searchValue.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelect = (value) => {
|
||||||
|
groupSetting.addOptions({ label: value, value })
|
||||||
|
emit('update:value', value)
|
||||||
|
emit('change', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, () => {
|
||||||
|
myValue.value = props.value
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 200px">
|
||||||
|
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<a-form-item label="单位" name="unit" :rules="[{ max: 64, message: '最多可输入64个字符' }]">
|
||||||
|
<UnitSelect v-model:value="formData.unit" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataInteger">
|
||||||
|
import { UnitSelect, PopoverModal } from '../index'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'cancel', 'confirm']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
unit: props.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await formRef.value.validate()
|
||||||
|
if (data) {
|
||||||
|
visible.value = false
|
||||||
|
emit('update:value', formData.unit);
|
||||||
|
emit('confirm', formData.unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formData.unit = props.value;
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, (newValue) => {
|
||||||
|
formData.unit = newValue
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,232 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 750px">
|
||||||
|
<EditTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="myColumns"
|
||||||
|
:dataSource="dataSource"
|
||||||
|
:pagination="false"
|
||||||
|
:height="200"
|
||||||
|
>
|
||||||
|
<!-- <template v-for="(_, key) in slots" :key="key" #[key]="slotData">-->
|
||||||
|
<!-- <slot :name="key" v-bind="slotData"/>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<template #id="{ record, index }">
|
||||||
|
<EditTableFormItem :name="[index, 'id']">
|
||||||
|
<a-input v-model:value="record.id" placeholder="请输入标识"/>
|
||||||
|
</EditTableFormItem>
|
||||||
|
</template>
|
||||||
|
<template #name="{ record, index }">
|
||||||
|
<EditTableFormItem :name="[index, 'name']">
|
||||||
|
<a-input v-model:value="record.name" placeholder="请输入名称"/>
|
||||||
|
</EditTableFormItem>
|
||||||
|
</template>
|
||||||
|
<template #expands="{ record }">
|
||||||
|
<BooleanSelect v-model:value="record.expands.required"/>
|
||||||
|
</template>
|
||||||
|
<template #valueType="{ record, index }">
|
||||||
|
<EditTableFormItem :name="[index, 'valueType']">
|
||||||
|
<div style="display: flex; gap: 12px; align-items: center">
|
||||||
|
<TypeSelect v-model:value="record.valueType.type" style="flex: 1 1 0;min-width: 0" />
|
||||||
|
<DoubleParams v-if="['float', 'double'].includes(record.valueType.type)" v-model:value="record.valueType" placement="topRight"/>
|
||||||
|
<StringParams v-else-if="record.valueType.type === 'string'" v-model:value="record.valueType" placement="topRight"/>
|
||||||
|
<DateParams v-else-if="record.valueType.type === 'date'" v-model:value="record.valueType.format" placement="topRight"/>
|
||||||
|
<FileParams v-else-if="record.valueType.type === 'file'" v-model:value="record.valueType.bodyType" placement="topRight"/>
|
||||||
|
<EnumParams v-else-if="record.valueType.type === 'enum'" v-model:value="record.valueType.elements" placement="topRight"/>
|
||||||
|
<BooleanParams
|
||||||
|
v-else-if="record.valueType.type === 'boolean'"
|
||||||
|
v-model:falseText="record.valueType.falseText"
|
||||||
|
v-model:falseValue="record.valueType.falseValue"
|
||||||
|
v-model:trueText="record.valueType.trueText"
|
||||||
|
v-model:trueValue="record.valueType.trueValue"
|
||||||
|
placement="topRight"
|
||||||
|
/>
|
||||||
|
<ArrayParams v-else-if="record.valueType.type === 'array'" v-model:value="record.valueType.elementType" placement="topRight"/>
|
||||||
|
</div>
|
||||||
|
</EditTableFormItem>
|
||||||
|
</template>
|
||||||
|
<template #action="{ index }">
|
||||||
|
<a-button danger type="link" style="padding: 0 5px" @click="() => deleteItem(index)">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</EditTable>
|
||||||
|
<a-button style="width: 100%;margin-top: 4px" @click="addItem">
|
||||||
|
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||||
|
新增
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined" :class="{'table-form-required-aicon': !value.length}"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataObject">
|
||||||
|
import { PopoverModal } from '../index'
|
||||||
|
import BooleanSelect from "../BooleanSelect/index.vue";
|
||||||
|
import { EditTable, TypeSelect, EditTableFormItem, StringParams, DateParams, FileParams, EnumParams, BooleanParams, ObjectParams, ArrayParams, DoubleParams } from '@/components/Metadata/Table'
|
||||||
|
import {Form} from "ant-design-vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'properties'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'confirm', 'cancel']);
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
|
const tableRef = ref()
|
||||||
|
const dataSource = ref([])
|
||||||
|
const visible = ref(false)
|
||||||
|
|
||||||
|
const defaultColumns = [
|
||||||
|
{
|
||||||
|
title: '参数标识',
|
||||||
|
dataIndex: 'id',
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
asyncValidator(_, value, ...setting) {
|
||||||
|
if (value) {
|
||||||
|
const option = setting[2]
|
||||||
|
|
||||||
|
const isSome = dataSource.value.some((item) => {
|
||||||
|
return item.__dataIndex !== option.index && item.id === value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isSome) {
|
||||||
|
return Promise.reject('该标识已存在')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
return Promise.reject('请输入标识')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
|
{
|
||||||
|
pattern: /^[a-zA-Z0-9_]+$/,
|
||||||
|
message: '标识只能由数字、字母、下划线组成',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '参数名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入名称'
|
||||||
|
},
|
||||||
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props.type === 'functions' ? {
|
||||||
|
title: '填写约束',
|
||||||
|
dataIndex: 'expands',
|
||||||
|
width: 120,
|
||||||
|
} : null,
|
||||||
|
{
|
||||||
|
title: '数据类型',
|
||||||
|
dataIndex: 'valueType',
|
||||||
|
width: 240,
|
||||||
|
form: {
|
||||||
|
required: true,
|
||||||
|
rules: [{
|
||||||
|
validator(_, value) {
|
||||||
|
if (!value?.type) {
|
||||||
|
return Promise.reject('请选择数据类型')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const myColumns = computed(() => {
|
||||||
|
|
||||||
|
return [
|
||||||
|
...defaultColumns.filter(item => !!item),
|
||||||
|
{
|
||||||
|
dataIndex: 'action',
|
||||||
|
title: '操作',
|
||||||
|
width: 60,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const data = await tableRef.value.validate()
|
||||||
|
if (data) {
|
||||||
|
visible.value = false
|
||||||
|
emit('update:value', data)
|
||||||
|
emit('confirm', data)
|
||||||
|
formItemContext.onFieldChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteItem = (index) => {
|
||||||
|
dataSource.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
dataSource.value.push({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
expands: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
valueType: {
|
||||||
|
expands: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => [JSON.stringify(props.value), visible.value], (val) => {
|
||||||
|
if (visible.value) {
|
||||||
|
dataSource.value = JSON.parse(val[0] || '[]')
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,111 @@
|
||||||
|
<template>
|
||||||
|
<a-popover
|
||||||
|
trigger="click"
|
||||||
|
:visible="visible"
|
||||||
|
:overlay-class-name="{
|
||||||
|
[warpClassNames]: true,
|
||||||
|
'metadata-table-popover-warp': true
|
||||||
|
}"
|
||||||
|
:overlayStyle="{
|
||||||
|
'zIndex': 1070
|
||||||
|
}"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
@visibleChange="null"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div v-if="visible">
|
||||||
|
<div :style="bodyStyle">
|
||||||
|
<slot name="content"/>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<a-space>
|
||||||
|
<a-button v-if="showCancel" size="small" @click="cancel">取消</a-button>
|
||||||
|
<a-button v-if="showOk" type="primary" size="small" @click="submit" :loading="confirmLoading">确认</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span @click="() => visibleChange(true)">
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataPopover">
|
||||||
|
import { useMask } from '../utils'
|
||||||
|
import {useTableWrapper, useTableFullScreen} from "@/components/Metadata/Table/context";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top'
|
||||||
|
},
|
||||||
|
confirmLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showCancel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showOk: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
bodyStyle: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['ok', 'cancel', 'update:visible'])
|
||||||
|
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const fullScreen = useTableFullScreen()
|
||||||
|
|
||||||
|
const { warpClassNames, visibleChange, visible } = useMask(props.visible, {
|
||||||
|
visibleChange(v) {
|
||||||
|
emit('update:visible', v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
emit('cancel')
|
||||||
|
visibleChange(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
emit('ok')
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.visible, (newValue) => {
|
||||||
|
visibleChange(newValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => fullScreen.value, (val) => {
|
||||||
|
if (!val) {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.metadata-table-popover-warp {
|
||||||
|
.ant-popover-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,153 @@
|
||||||
|
<template>
|
||||||
|
<a-popover
|
||||||
|
trigger="click"
|
||||||
|
placement="bottom"
|
||||||
|
:overlayStyle="{
|
||||||
|
'zIndex': 1050
|
||||||
|
}"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
@visibleChange="visibleChange"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="table-sort-content">
|
||||||
|
<div class="table-sort-title">
|
||||||
|
排序
|
||||||
|
</div>
|
||||||
|
<div class="table-sort-body">
|
||||||
|
<a-table
|
||||||
|
v-if="visible"
|
||||||
|
size="small"
|
||||||
|
:columns="columns"
|
||||||
|
:dataSource="dataSource"
|
||||||
|
:row-selection="{ selectedRowKeys: mySelectedRowKeys, onChange: onSelectChange }"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="dataSource.length > 5 ? { y: 200 } : undefined"
|
||||||
|
>
|
||||||
|
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
<div class="table-sort-footer">
|
||||||
|
<j-space :size="16">
|
||||||
|
<a-button type="primary" @click="cleanParams" :disabled="!mySelectedRowKeys.length">清空条件</a-button>
|
||||||
|
<a-button type="primary" ghost @click="onAsc">升序</a-button>
|
||||||
|
<a-button type="primary" ghost @click="onDesc">降序</a-button>
|
||||||
|
</j-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<AIcon type="icon-paixu" :class="{ 'table-sort-icon': true, 'active': props.active }" />
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataTableSort">
|
||||||
|
import {useTableTool, useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dataSource: {
|
||||||
|
type: [Function, Array],
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
sortKey: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
dataIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
selectedRowKeys: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const tableTool = useTableTool()
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const dataSource = ref([])
|
||||||
|
const mySelectedRowKeys = ref([])
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
dataIndex: 'name',
|
||||||
|
title: '名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'total',
|
||||||
|
title: '计数',
|
||||||
|
width: 70
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const visibleChange = (e) => {
|
||||||
|
if (e) {
|
||||||
|
mySelectedRowKeys.value = props.selectedRowKeys
|
||||||
|
dataSource.value = (props.dataSource?.() || []).map((item, index) => Object.assign(item, { __index: index + 1}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e) {
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = e
|
||||||
|
}, 300)
|
||||||
|
} else {
|
||||||
|
visible.value = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelectChange = (keys) => {
|
||||||
|
mySelectedRowKeys.value = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanParams = () => {
|
||||||
|
mySelectedRowKeys.value = []
|
||||||
|
tableTool.cleanOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSortRowKeys = () => {
|
||||||
|
return dataSource.value
|
||||||
|
.filter(item => mySelectedRowKeys.value.includes(item.key))
|
||||||
|
.sort((a, b) => b.__index - a.__index)
|
||||||
|
.map(item => item.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAsc = () => {
|
||||||
|
tableTool.order('asc', props.sortKey, handleSortRowKeys(), props.dataIndex)
|
||||||
|
emit('click')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDesc = () => {
|
||||||
|
tableTool.order('desc', props.sortKey, handleSortRowKeys(), props.dataIndex)
|
||||||
|
emit('click')
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.table-sort-content {
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
.table-sort-title {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-sort-body {
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-sort-icon {
|
||||||
|
color: rgba(0,0,0, 0.25);
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as SearchModal } from './search.vue'
|
||||||
|
export { default as Sort } from './Sort.vue'
|
|
@ -0,0 +1,205 @@
|
||||||
|
<template>
|
||||||
|
<DragModal
|
||||||
|
:width="800"
|
||||||
|
:height="modalHeight"
|
||||||
|
:title="false"
|
||||||
|
:dragRang="[600, 200]"
|
||||||
|
:bodyStyle="{
|
||||||
|
overflow: 'hidden'
|
||||||
|
}"
|
||||||
|
@heightChange="heightChange"
|
||||||
|
>
|
||||||
|
<div class="table-search">
|
||||||
|
<div class="table-search-header">
|
||||||
|
<div>
|
||||||
|
<a-space>
|
||||||
|
<span>查找</span>
|
||||||
|
<a-input v-model:value="searchValue" :maxlength="64" placeholder="请输入查找内容" />
|
||||||
|
<a-button type="primary" ghost @click="() => search('all')">查找全部</a-button>
|
||||||
|
<a-button type="primary" ghost @click="() => search('prev')">上一个</a-button>
|
||||||
|
<a-button type="primary" ghost @click="() => search('next')">下一个</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a-button type="primary" @click.stop="onClose">关闭</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="visible" style="margin: 12px 0">
|
||||||
|
<Table
|
||||||
|
ref="tableRef"
|
||||||
|
:data-source="filterArray"
|
||||||
|
:columns="columns"
|
||||||
|
:height="tableHeight"
|
||||||
|
:disableMenu="false"
|
||||||
|
:cellHeight="36"
|
||||||
|
:rowSelection="{
|
||||||
|
onSelect: onSelect,
|
||||||
|
selectedRowKeys: selectedRowKeys
|
||||||
|
}"
|
||||||
|
:serial="{
|
||||||
|
width: openGroup ? 150 : 66,
|
||||||
|
title: '行数'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #serial="{ record }">
|
||||||
|
<span v-if="openGroup">
|
||||||
|
<Ellipsis>
|
||||||
|
{{ record.expands.groupName }} 第 {{ record.__oldSerial }} 行
|
||||||
|
</Ellipsis>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ record.__serial }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #id="{ record }">
|
||||||
|
<Ellipsis>
|
||||||
|
{{ record.id }}
|
||||||
|
</Ellipsis>
|
||||||
|
</template>
|
||||||
|
<template #name="{ record }">
|
||||||
|
<Ellipsis>
|
||||||
|
{{ record.name }}
|
||||||
|
</Ellipsis>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
<div v-if="visible">
|
||||||
|
查找到 <span class="table-search-result-total">{{filterArray.length}}</span> 条相关属性
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DragModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataTableSearch">
|
||||||
|
import { DragModal } from '@/components/Modal'
|
||||||
|
import Table from '../../Table.vue'
|
||||||
|
import {useTableDataSource, useTableOpenGroup, useTableTool, useGroupOptions} from "@/components/Metadata/Table/context";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
searchKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
|
||||||
|
const dataSource = useTableDataSource()
|
||||||
|
const openGroup = useTableOpenGroup()
|
||||||
|
const tableTool = useTableTool()
|
||||||
|
const groupOptions = useGroupOptions()
|
||||||
|
|
||||||
|
const searchValue = ref()
|
||||||
|
const filterArray = ref([])
|
||||||
|
const visible = ref(false)
|
||||||
|
const searchIndex = ref(-1)
|
||||||
|
const modalHeight = ref(100)
|
||||||
|
const tableHeight = ref(230)
|
||||||
|
const selectedRowKeys = ref([])
|
||||||
|
const tableRef = ref()
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '标识',
|
||||||
|
dataIndex: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const selectedTableRow = (record) => {
|
||||||
|
tableTool.scrollTo({
|
||||||
|
...record,
|
||||||
|
__serial: record.__serial - 1
|
||||||
|
})
|
||||||
|
tableTool.selected([record.id])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFilterArray = () => {
|
||||||
|
const cloneDataSource = JSON.parse(JSON.stringify(dataSource.value || '[]')).map(item => Object.assign(item, { __oldSerial: item.__serial}))
|
||||||
|
const _filterArray = cloneDataSource.filter(item => {
|
||||||
|
if (item[props.searchKey]) {
|
||||||
|
return item[props.searchKey].includes(searchValue.value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (openGroup) {
|
||||||
|
const handleGroup = []
|
||||||
|
|
||||||
|
groupOptions.value.forEach(group => {
|
||||||
|
handleGroup.push(..._filterArray.filter(item => item.expands.groupId === group.value))
|
||||||
|
})
|
||||||
|
|
||||||
|
return handleGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
return _filterArray
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = (key) => {
|
||||||
|
filterArray.value = handleFilterArray()
|
||||||
|
|
||||||
|
if (key === 'all') {
|
||||||
|
visible.value = true
|
||||||
|
modalHeight.value = 400
|
||||||
|
searchIndex.value = 0
|
||||||
|
} else if (key === 'next') {
|
||||||
|
searchIndex.value += 1
|
||||||
|
} else {
|
||||||
|
searchIndex.value -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchIndex.value < 0) {
|
||||||
|
searchIndex.value = filterArray.value.length - 1
|
||||||
|
} else if (searchIndex.value > filterArray.value.length - 1){
|
||||||
|
searchIndex.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchItem = filterArray.value[searchIndex.value]
|
||||||
|
|
||||||
|
if (key !== 'all' && visible.value) {
|
||||||
|
tableRef.value?.scrollToByIndex(searchIndex.value - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterArray.value.length > 1) {
|
||||||
|
selectedRowKeys.value = [searchItem.id]
|
||||||
|
selectedTableRow(searchItem)
|
||||||
|
} else {
|
||||||
|
selectedRowKeys.value = []
|
||||||
|
tableTool.selected([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const heightChange = (h) => {
|
||||||
|
if (h > 340) {
|
||||||
|
tableHeight.value = h - 160
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
console.log('close')
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSelect = (record) => {
|
||||||
|
searchIndex.value = filterArray.value.findIndex(item => item.id === record.id)
|
||||||
|
selectedRowKeys.value = [record.id]
|
||||||
|
selectedTableRow(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.table-search-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-search-result-total {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item label="最大长度" :name="name">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="myValue"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入0-9999以内的整数"
|
||||||
|
:precision="0"
|
||||||
|
:min="0"
|
||||||
|
:max="9999"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataStringItem">
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'maxLength'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const myValue = ref(props.value)
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
emit('update:value', myValue.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(newValue) => {
|
||||||
|
myValue.value = newValue
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<PopoverModal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:placement="placement"
|
||||||
|
@ok="onOk"
|
||||||
|
@cancel="onCancel"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div style="width: 200px">
|
||||||
|
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<Item v-model:value="formData.maxLength"/>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
<a-button type="link" :disabled="disabled" style="padding: 0">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="EditOutlined"/>
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</slot>
|
||||||
|
</PopoverModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataString">
|
||||||
|
import { PopoverModal } from '../index'
|
||||||
|
import Item from './Item.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'confirm', 'cancel']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
placement: {
|
||||||
|
type: String,
|
||||||
|
default: 'top',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default:false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
maxLength: props.value.maxLength || props.value.expands?.maxLength,
|
||||||
|
})
|
||||||
|
|
||||||
|
const onOk = () => {
|
||||||
|
visible.value = false
|
||||||
|
const obj = {
|
||||||
|
...props.value,
|
||||||
|
expands: {
|
||||||
|
maxLength: formData.maxLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:value', obj);
|
||||||
|
emit('confirm', obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formData.maxLength = props.value.maxLength || props.value.expands?.maxLength,
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, (newValue) => {
|
||||||
|
formData.maxLength = newValue.maxLength || newValue.expands?.maxLength
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,67 @@
|
||||||
|
const type = [
|
||||||
|
{
|
||||||
|
value: 'int',
|
||||||
|
label: 'int(整数型)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'long',
|
||||||
|
label: 'long(长整数型)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'float',
|
||||||
|
label: 'float(单精度浮点型)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'double',
|
||||||
|
label: 'double(双精度浮点数)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'string',
|
||||||
|
label: 'text(字符串)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'boolean',
|
||||||
|
label: 'boolean(布尔型)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'date',
|
||||||
|
label: 'date(时间型)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'enum',
|
||||||
|
label: 'enum(枚举)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'array',
|
||||||
|
label: 'array(数组)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'object',
|
||||||
|
label: 'object(结构体)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'file',
|
||||||
|
label: 'file(文件)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'password',
|
||||||
|
label: 'password(密码)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'geoPoint',
|
||||||
|
label: 'geoPoint(地理位置)',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const findTypeItem = (value: string) => {
|
||||||
|
return type.find(item => item.value === value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTypeMap = () => {
|
||||||
|
return type.reduce((prev, next) => {
|
||||||
|
prev[next.value] = next.label
|
||||||
|
return prev
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default type
|
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<div :class="{'select-no-value': !value}">
|
||||||
|
<a-select
|
||||||
|
v-bind="props"
|
||||||
|
allow-clear
|
||||||
|
:value="myValue"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请选择数据类型"
|
||||||
|
:dropdownStyle="{
|
||||||
|
zIndex: 1071
|
||||||
|
}"
|
||||||
|
:options="options"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataType">
|
||||||
|
import { selectProps } from 'ant-design-vue/lib/select';
|
||||||
|
import defaultOptions from './data';
|
||||||
|
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
...selectProps(),
|
||||||
|
filter: {
|
||||||
|
type: Array ,
|
||||||
|
default: () => [],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']);
|
||||||
|
|
||||||
|
const myValue = ref(props.value)
|
||||||
|
|
||||||
|
const options = computed(() => {
|
||||||
|
return defaultOptions.filter(item => !props.filter.includes(item.value) )
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const change = (key) => {
|
||||||
|
myValue.value = key
|
||||||
|
emit('update:value', key)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(newValue) => {
|
||||||
|
myValue.value = newValue;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择单位"
|
||||||
|
style="width: 100%"
|
||||||
|
mode="tags"
|
||||||
|
v-model:value="myValue"
|
||||||
|
:dropdownStyle="{
|
||||||
|
zIndex: 1071
|
||||||
|
}"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
:options="options"
|
||||||
|
optionFilterProp="label"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataUnitSelect">
|
||||||
|
import { Form, Select } from 'ant-design-vue'
|
||||||
|
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
|
import {useGetUnit} from "@/views/device/components/Metadata/Base/columns";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'middle'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: 'maxLength'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'change'])
|
||||||
|
|
||||||
|
const myValue = ref(props.value)
|
||||||
|
|
||||||
|
const options = useGetUnit()
|
||||||
|
|
||||||
|
const formItemContext = Form.useInjectFormItemContext();
|
||||||
|
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const change = (v) => {
|
||||||
|
const newValue = v.length > 1 ? v.pop() : v?.[0];
|
||||||
|
myValue.value = [newValue];
|
||||||
|
emit('update:value', newValue);
|
||||||
|
emit('change', newValue);
|
||||||
|
formItemContext.onFieldChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOption = (v, option) => {
|
||||||
|
return option.label.includes(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(newV) => {
|
||||||
|
myValue.value = newV ? [newV] : undefined;
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,15 @@
|
||||||
|
export { default as ArrayParams } from './Array/index.vue'
|
||||||
|
export { default as ObjectParams } from './Object/index.vue'
|
||||||
|
export { default as DateParams } from './Date/index.vue'
|
||||||
|
export { default as BooleanParams } from './Boolean/index.vue'
|
||||||
|
export { default as EnumParams } from './Enum/index.vue'
|
||||||
|
export { default as FileParams } from './File/index.vue'
|
||||||
|
export { default as IntegerParams } from './Integer/index.vue'
|
||||||
|
export { default as DoubleParams } from './Double/index.vue'
|
||||||
|
export { default as StringParams } from './String/index.vue'
|
||||||
|
|
||||||
|
export { default as PopoverModal } from './Popover/index.vue'
|
||||||
|
export { default as UnitSelect } from './UnitSelect/index.vue'
|
||||||
|
export { default as TypeSelect } from './type/index.vue'
|
||||||
|
export { default as GroupSelect } from './group/index.vue'
|
||||||
|
export { default as BooleanSelect } from './BooleanSelect/index.vue'
|
|
@ -0,0 +1,156 @@
|
||||||
|
import {randomString} from "@/utils/utils";
|
||||||
|
import {Ref} from "vue";
|
||||||
|
|
||||||
|
let maskIds: string[] = []
|
||||||
|
export const maskNodeClassName = 'popover-mask'
|
||||||
|
|
||||||
|
const bodyHasScrollbar = () => {
|
||||||
|
return document.body.scrollHeight > document.body.clientHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStyle = (dom: HTMLElement | undefined, style: Record<string, any>) => {
|
||||||
|
if (!dom) return
|
||||||
|
|
||||||
|
Object.keys(style).forEach(key => {
|
||||||
|
dom.style[key] = style[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const bodyHidden = () => {
|
||||||
|
const hasScrollbar = bodyHasScrollbar()
|
||||||
|
|
||||||
|
if (hasScrollbar) {
|
||||||
|
updateStyle(document.body as HTMLElement, {
|
||||||
|
overflow: 'hidden',
|
||||||
|
width: 'calc(100% - 17px)',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaskNode = (id: string, warpClassNames: string) => {
|
||||||
|
let maskNode = document.querySelector(`#${id}`) as HTMLElement
|
||||||
|
|
||||||
|
if (maskNode) {
|
||||||
|
return maskNode
|
||||||
|
}
|
||||||
|
|
||||||
|
maskNode = document.createElement('div')
|
||||||
|
maskNode.id = id
|
||||||
|
|
||||||
|
updateStyle(maskNode, {
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
'z-index': 1070,
|
||||||
|
'background-color': '#0003'
|
||||||
|
})
|
||||||
|
|
||||||
|
const warpNode = document.querySelector(`.${warpClassNames}`) as HTMLDivElement
|
||||||
|
|
||||||
|
if (!warpNode) return undefined
|
||||||
|
|
||||||
|
warpNode.insertAdjacentElement('beforebegin', maskNode)
|
||||||
|
|
||||||
|
return maskNode
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMask = (propVisible: boolean, options: { visibleChange: (visible: boolean) => void }): {
|
||||||
|
warpClassNames: string,
|
||||||
|
visible: Ref<boolean>,
|
||||||
|
hideMask: Function,
|
||||||
|
showMask: Function,
|
||||||
|
visibleChange: (visible: boolean) => void
|
||||||
|
} => {
|
||||||
|
const visible = ref(propVisible)
|
||||||
|
const maskDomId = `${maskNodeClassName}-${randomString(6)}`
|
||||||
|
|
||||||
|
const warpClassNames = `${maskNodeClassName}-warp-${randomString(4)}`
|
||||||
|
const createMask = () => {
|
||||||
|
if (!maskIds.includes(maskDomId)) {
|
||||||
|
maskIds.push(maskDomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getMaskNode(maskDomId, warpClassNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLastMask = (): HTMLElement | undefined => {
|
||||||
|
const index = maskIds.findIndex(key => key === maskDomId) // 当前遮罩层下标
|
||||||
|
|
||||||
|
let dom = undefined
|
||||||
|
let lastIndex = 0
|
||||||
|
|
||||||
|
if (maskIds.length > 0) {
|
||||||
|
|
||||||
|
lastIndex = index < 0 ? 0 : index - 1
|
||||||
|
|
||||||
|
const lastMaskId = maskIds[lastIndex]
|
||||||
|
|
||||||
|
dom = document.querySelector(`#${lastMaskId}`) as HTMLElement
|
||||||
|
}
|
||||||
|
|
||||||
|
return dom
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideLastMask = () => {
|
||||||
|
const lastMaskNode = getLastMask()
|
||||||
|
updateStyle(lastMaskNode, {
|
||||||
|
display: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showLastMask = () => {
|
||||||
|
const lastMaskNode = getLastMask()
|
||||||
|
updateStyle(lastMaskNode, {
|
||||||
|
display: 'block'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showMask = () => {
|
||||||
|
bodyHidden()
|
||||||
|
setTimeout(() => {
|
||||||
|
hideLastMask()
|
||||||
|
const maskNode = createMask()
|
||||||
|
updateStyle(maskNode, {
|
||||||
|
display: 'block'
|
||||||
|
})
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideMask = () => {
|
||||||
|
const maskNode = createMask()
|
||||||
|
showLastMask()
|
||||||
|
|
||||||
|
const index = maskIds.findIndex(key => key === maskDomId) // 当前遮罩层下标
|
||||||
|
|
||||||
|
maskIds.splice(index, 1)
|
||||||
|
if (index === 0) {
|
||||||
|
document.body.style = ''
|
||||||
|
maskIds = []
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStyle(maskNode, {
|
||||||
|
display: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleChange = (v: boolean) => {
|
||||||
|
visible.value = v
|
||||||
|
options?.visibleChange(v)
|
||||||
|
if (v) {
|
||||||
|
showMask()
|
||||||
|
} else {
|
||||||
|
hideMask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
warpClassNames,
|
||||||
|
visible,
|
||||||
|
hideMask,
|
||||||
|
showMask,
|
||||||
|
visibleChange
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
export const METADATA_GROUP_OPTIONS = Symbol('metadata-group-options')
|
||||||
|
|
||||||
|
export const TABLE_WRAPPER = Symbol('table-wrapper')
|
||||||
|
export const FULL_SCREEN = Symbol('full')
|
||||||
|
|
||||||
|
export const RIGHT_MENU = Symbol('right-menu')
|
||||||
|
|
||||||
|
export const TABLE_ERROR = Symbol('table-error')
|
||||||
|
|
||||||
|
export const TABLE_GROUP_ERROR = Symbol('table-group-error')
|
||||||
|
|
||||||
|
export const TABLE_GROUP_OPTIONS = Symbol('table-group-options')
|
||||||
|
|
||||||
|
export const TABLE_DATA_SOURCE = Symbol('table-data-source')
|
||||||
|
|
||||||
|
export const TABLE_OPEN_GROUP = Symbol('table-open-group')
|
||||||
|
|
||||||
|
export const TABLE_TOOL = Symbol('table-tool')
|
||||||
|
|
||||||
|
export const TABLE_FORM_ITEM_ERROR = Symbol('table-form-item-error')
|
||||||
|
|
||||||
|
export const TABLE_GROUP_ACTIVE = Symbol('table-group-active')
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { provide } from 'vue'
|
||||||
|
import {
|
||||||
|
RIGHT_MENU,
|
||||||
|
TABLE_DATA_SOURCE,
|
||||||
|
TABLE_ERROR,
|
||||||
|
TABLE_GROUP_ERROR,
|
||||||
|
TABLE_WRAPPER,
|
||||||
|
TABLE_OPEN_GROUP,
|
||||||
|
TABLE_TOOL,
|
||||||
|
TABLE_GROUP_OPTIONS,
|
||||||
|
TABLE_FORM_ITEM_ERROR,
|
||||||
|
TABLE_GROUP_ACTIVE, FULL_SCREEN
|
||||||
|
} from "./consts";
|
||||||
|
|
||||||
|
type FiledExpose = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormContextKey = 'form-context'
|
||||||
|
export const useFormContext = (options: Record<string, any>) => {
|
||||||
|
provide(FormContextKey, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInjectForm = () => {
|
||||||
|
return inject(FormContextKey, {
|
||||||
|
addField: (key: string, field: FiledExpose) => {},
|
||||||
|
dataSource: computed(() => []),
|
||||||
|
rules: computed(() => undefined),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useInjectError = () => inject(TABLE_ERROR)
|
||||||
|
|
||||||
|
export const useTableWrapper = () => inject(TABLE_WRAPPER)
|
||||||
|
|
||||||
|
export const useRightMenuContext = () => inject(RIGHT_MENU)
|
||||||
|
|
||||||
|
export const useTableGroupError = () => inject(TABLE_GROUP_ERROR)
|
||||||
|
|
||||||
|
export const useTableDataSource = () => inject(TABLE_DATA_SOURCE, [])
|
||||||
|
|
||||||
|
export const useTableOpenGroup = () => inject(TABLE_OPEN_GROUP, false)
|
||||||
|
|
||||||
|
export const useTableTool = () => inject(TABLE_TOOL, false)
|
||||||
|
|
||||||
|
export const useGroupOptions = () => inject(TABLE_GROUP_OPTIONS, [])
|
||||||
|
|
||||||
|
export const useFormItemError = () => inject(TABLE_FORM_ITEM_ERROR)
|
||||||
|
export const useGroupActive = () => inject(TABLE_GROUP_ACTIVE)
|
||||||
|
|
||||||
|
export const useTableFullScreen = () => inject(FULL_SCREEN)
|
|
@ -0,0 +1,245 @@
|
||||||
|
<template>
|
||||||
|
<Table
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:dataSource="dataSource"
|
||||||
|
:pagination="false"
|
||||||
|
@scroll-down="onScrollDown"
|
||||||
|
>
|
||||||
|
<template #string="{record}">
|
||||||
|
<div>
|
||||||
|
<a-input />
|
||||||
|
<StringParams v-model:value="record.string"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #double="{record}">
|
||||||
|
<DoubleParams v-model:value="record.double"/>
|
||||||
|
</template>
|
||||||
|
<template #integer="{record}">
|
||||||
|
<IntegerParams v-model:value="record.integer"/>
|
||||||
|
</template>
|
||||||
|
<template #date="{record}">
|
||||||
|
<DateParams v-model:value="record.date"/>
|
||||||
|
</template>
|
||||||
|
<template #boolean="{record}">
|
||||||
|
<BooleanParams v-model:value="record.boolean"/>
|
||||||
|
</template>
|
||||||
|
<template #file="{record}">
|
||||||
|
<FileParams v-model:value="record.file"/>
|
||||||
|
</template>
|
||||||
|
<template #enum="{record}">
|
||||||
|
<EnumParams v-model:value="record.enum"/>
|
||||||
|
</template>
|
||||||
|
<template #object="{record}">
|
||||||
|
<ObjectParams v-model:value="record.object"/>
|
||||||
|
</template>
|
||||||
|
<template #array="{record}">
|
||||||
|
<ArrayParams v-model:value="record.array"/>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<a-button @click="addItem">新增</a-button>
|
||||||
|
<a-button @click="validate">校验</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="demo">
|
||||||
|
import Table from './Table.vue'
|
||||||
|
import TableFormItem from './TableFormItem.vue'
|
||||||
|
import {
|
||||||
|
ArrayParams,
|
||||||
|
ObjectParams,
|
||||||
|
DateParams,
|
||||||
|
BooleanParams,
|
||||||
|
EnumParams,
|
||||||
|
FileParams,
|
||||||
|
IntegerParams,
|
||||||
|
DoubleParams,
|
||||||
|
StringParams
|
||||||
|
} from './components'
|
||||||
|
|
||||||
|
const tableRef = ref()
|
||||||
|
const dataSource = ref(new Array(10).fill(0).map((_, index) => {
|
||||||
|
return {
|
||||||
|
string: undefined,
|
||||||
|
double: undefined,
|
||||||
|
integer: undefined,
|
||||||
|
object: undefined,
|
||||||
|
file: undefined,
|
||||||
|
date: undefined,
|
||||||
|
boolean: undefined,
|
||||||
|
enum: undefined,
|
||||||
|
array: undefined,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
dataIndex: 'string',
|
||||||
|
title: 'string',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入列名')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'double',
|
||||||
|
title: 'double',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入age')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'integer',
|
||||||
|
title: 'integer',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入类型')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'date',
|
||||||
|
title: 'date',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入类型')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'boolean',
|
||||||
|
title: 'boolean',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入类型')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'file',
|
||||||
|
title: 'file',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入类型')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'enum',
|
||||||
|
title: 'enum',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入类型')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'object',
|
||||||
|
title: 'object',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入属性')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'array',
|
||||||
|
title: 'array',
|
||||||
|
form: {
|
||||||
|
rules: {
|
||||||
|
asyncValidator: (rule, value, cb) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入其它')
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const addItem = async () => {
|
||||||
|
if (dataSource.length) {
|
||||||
|
await validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.value.push({
|
||||||
|
string: undefined,
|
||||||
|
double: undefined,
|
||||||
|
integer: undefined,
|
||||||
|
object: undefined,
|
||||||
|
file: undefined,
|
||||||
|
date: undefined,
|
||||||
|
boolean: undefined,
|
||||||
|
enum: undefined,
|
||||||
|
array: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScrollDown = () => {
|
||||||
|
dataSource.value.push(...(new Array(10).fill(1).map(() =>({
|
||||||
|
string: undefined,
|
||||||
|
double: undefined,
|
||||||
|
integer: undefined,
|
||||||
|
object: undefined,
|
||||||
|
file: undefined,
|
||||||
|
date: undefined,
|
||||||
|
boolean: undefined,
|
||||||
|
enum: undefined,
|
||||||
|
array: undefined,
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = async () => {
|
||||||
|
const data = await tableRef.value.validate().catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
console.log(data, dataSource.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,252 @@
|
||||||
|
<template>
|
||||||
|
<div class="table-group-warp">
|
||||||
|
<a-tabs type="editable-card" v-model:activeKey="myActiveKey" @edit="onAdd" @change="change">
|
||||||
|
<a-tab-pane v-for="item in options" :key="item.value" :closable="false">
|
||||||
|
<template #tab>
|
||||||
|
<a-dropdown
|
||||||
|
v-if="myActiveKey === item.value"
|
||||||
|
:trigger="['click']"
|
||||||
|
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||||
|
>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="(e) => { menuClick(e, item)}">
|
||||||
|
<a-menu-item key="edit">
|
||||||
|
编辑
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="delete">
|
||||||
|
删除
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
<div class="table-group-error-warp">
|
||||||
|
{{ item.label }} ({{ item.effective}})
|
||||||
|
<a-tooltip
|
||||||
|
v-if="errorMap[item.value]"
|
||||||
|
color="#ffffff"
|
||||||
|
:arrowPointAtCenter="true"
|
||||||
|
:get-popup-container="popContainer"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<span style="color: #1d2129">校验不合规</span>
|
||||||
|
</template>
|
||||||
|
<div class="table-group-error-target"></div>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</a-dropdown>
|
||||||
|
<div v-else class="table-group-error-warp">
|
||||||
|
{{ item.label }} ({{ item.effective}})
|
||||||
|
<a-tooltip
|
||||||
|
v-if="errorMap[item.value]"
|
||||||
|
color="#ffffff"
|
||||||
|
:arrowPointAtCenter="true"
|
||||||
|
:getContainer="popContainer"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<span style="color: #1d2129">校验不合规</span>
|
||||||
|
</template>
|
||||||
|
<div class="table-group-error-target"></div>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
title="编辑分组"
|
||||||
|
:maskClosable="false"
|
||||||
|
:getContainer="modalContainer"
|
||||||
|
@cancel="onCancel"
|
||||||
|
@ok="onOk"
|
||||||
|
>
|
||||||
|
<a-form ref="formRef" :model="formData" @finish="onOk">
|
||||||
|
<a-form-item name="label" :rules="[{ required: true, message: '请输入分组名称'}, { max: 16, message: '最多可输入16个字符'}]">
|
||||||
|
<a-input v-model:value="formData.label" placeholder="请输入分组名称"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-show="false">
|
||||||
|
<a-button html-type="submit"></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataBaseTableGroup">
|
||||||
|
import {Modal} from "ant-design-vue";
|
||||||
|
import {randomNumber} from "@/utils/utils";
|
||||||
|
import {isFullScreen} from "@/utils/comm";
|
||||||
|
import {useTableGroupError, useTableWrapper} from './context'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
activeKey: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['delete', 'edit', 'add', 'change', 'update:activeKey'])
|
||||||
|
|
||||||
|
const myActiveKey = ref(props.activeKey)
|
||||||
|
const visible = ref(false)
|
||||||
|
const type = ref('新增分组')
|
||||||
|
const errorMap = useTableGroupError()
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
const addIndex = ref(0)
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const formData = reactive({
|
||||||
|
label: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const onAdd = (targetKey, action) => {
|
||||||
|
if (action === 'add') {
|
||||||
|
type.value = 'add'
|
||||||
|
// 获取上一个包含 “分组_” 的信息
|
||||||
|
const groupName = props.options.filter(item => item.label.includes('分组_'))
|
||||||
|
let index = addIndex.value + 1
|
||||||
|
let findStatus = false
|
||||||
|
while (!findStatus) {
|
||||||
|
const status = groupName.some(item => {
|
||||||
|
const [ _, _index] = item.label.split('_')
|
||||||
|
if (index === Number(_index)) {
|
||||||
|
index = Number(_index) + 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
findStatus = !status
|
||||||
|
}
|
||||||
|
|
||||||
|
addIndex.value = index
|
||||||
|
formData.label = '分组_' + index
|
||||||
|
onOk()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEdit = (record) => {
|
||||||
|
visible.value = true
|
||||||
|
type.value = 'edit'
|
||||||
|
formData.label = record.label
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
formRef.value.resetFields()
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
const onOk = () => {
|
||||||
|
const data = { ...formData }
|
||||||
|
|
||||||
|
if (type.value === 'add') {
|
||||||
|
data.value = 'group_'+randomNumber()
|
||||||
|
myActiveKey.value = data.value
|
||||||
|
emit(type.value, data)
|
||||||
|
emit('change', data.value, data.label)
|
||||||
|
emit('update:activeKey', data.value)
|
||||||
|
visible.value = false
|
||||||
|
} else {
|
||||||
|
formRef.value.validate().then(() => {
|
||||||
|
data.value = myActiveKey.value
|
||||||
|
emit(type.value, data)
|
||||||
|
emit('change', data.value, data.label)
|
||||||
|
visible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
const item = props.options.find(item => item.value === myActiveKey.value)
|
||||||
|
emit('change', myActiveKey.value, item.label)
|
||||||
|
emit('update:activeKey', myActiveKey.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuClick = (e, record) => {
|
||||||
|
if (e.key === 'edit') {
|
||||||
|
onEdit(record)
|
||||||
|
} else {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '将会同步删除分组内的数据,确认删除?',
|
||||||
|
onOk: () => {
|
||||||
|
// activeKey 根据数据长度进行左右移动
|
||||||
|
const index = props.options.findIndex(item => item.value === record.value)
|
||||||
|
let label = ''
|
||||||
|
|
||||||
|
if (index !== 0 && index === props.options.length - 1) {
|
||||||
|
myActiveKey.value = props.options[index - 1].value
|
||||||
|
label = props.options[index - 1].label
|
||||||
|
} else if (index === 0 && props.options.length === 1) {
|
||||||
|
myActiveKey.value = props.options[0].value
|
||||||
|
label = props.options[0].label
|
||||||
|
} else {
|
||||||
|
myActiveKey.value = props.options[index + 1].value
|
||||||
|
label = props.options[index + 1].label
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('delete', record.value, index)
|
||||||
|
emit('change', myActiveKey.value, label)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const popContainer = (e) => {
|
||||||
|
return tableWrapperRef.value || e
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalContainer = (e) =>{
|
||||||
|
if (isFullScreen()) {
|
||||||
|
return tableWrapperRef.value || document.body
|
||||||
|
}
|
||||||
|
return document.body
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
myActiveKey.value = props.options[0]?.value
|
||||||
|
emit('change', myActiveKey.value, props.options[0]?.label)
|
||||||
|
emit('update:activeKey', myActiveKey.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.activeKey, (val) => {
|
||||||
|
myActiveKey.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.table-group-warp {
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
.ant-tabs-tab {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ant-tabs-tab-active {
|
||||||
|
background-color: #BAE0FF !important;
|
||||||
|
border-color: #91CAFF !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-nav-add {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-group-error-warp {
|
||||||
|
color: #1a1a1a !important;
|
||||||
|
|
||||||
|
.table-group-error-target {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
border: 16px solid transparent;
|
||||||
|
border-top-color: @error-color;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,132 @@
|
||||||
|
<template>
|
||||||
|
<div class="metadata-edit-table-header-container" :style="style">
|
||||||
|
<div class="metadata-edit-table-header-cell" v-for="(item, index) in columns" :id="item.dataIndex" :style="{width: `${item.width}px`, left: `${item.left}px`}">
|
||||||
|
<div :class="{ 'metadata-edit-table-header-cell-box': true, 'header-cell-box-tool': !!(item.sort || item.filter) }">
|
||||||
|
<div class="table-header-cell-title">
|
||||||
|
<span>{{ item.title }}</span>
|
||||||
|
<span v-if="item.form?.required" class="header-cell-required">*</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="!!(item.sort || item.filter)" class="table-header-cell-trigger">
|
||||||
|
<AIcon
|
||||||
|
v-if="item.filter"
|
||||||
|
type="SearchOutlined"
|
||||||
|
style="color: rgba(0,0,0, 0.25)"
|
||||||
|
@click="() => {showFilter(item.filter.key || item.dataIndex)}"
|
||||||
|
/>
|
||||||
|
<Sort
|
||||||
|
v-if="item.sort"
|
||||||
|
v-bind="item.sort"
|
||||||
|
:key="item.dataIndex"
|
||||||
|
:active="tableTool.sortData.dataIndex === item.dataIndex"
|
||||||
|
:selectedRowKeys="tableTool.sortData.dataIndex === item.dataIndex ? tableTool.sortData.orderKeys : []"
|
||||||
|
:dataIndex="item.dataIndex"
|
||||||
|
@click="sortClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SearchModal
|
||||||
|
v-if="searchData.visible"
|
||||||
|
:searchKey="searchData.key"
|
||||||
|
@close="searchData.visible = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="MetadataBaseTableHeader">
|
||||||
|
import { SearchModal, Sort } from './components/Search'
|
||||||
|
import {useTableTool} from "@/components/Metadata/Table/context";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
serial: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableTool = useTableTool()
|
||||||
|
|
||||||
|
const searchData = reactive({
|
||||||
|
visible: false,
|
||||||
|
key: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const showFilter = (key) => {
|
||||||
|
searchData.visible = true
|
||||||
|
searchData.key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortClick = () => {
|
||||||
|
searchData.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.metadata-edit-table-header-container {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
.metadata-edit-table-header-cell {
|
||||||
|
height: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
float: left;
|
||||||
|
overflow: visible;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
.metadata-edit-table-header-cell-box {
|
||||||
|
padding: 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.header-cell-box-tool {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header-cell-title {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cell-required {
|
||||||
|
color: @error-color;
|
||||||
|
padding-left: 8px;
|
||||||
|
transform: translateY(3px);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 1px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1.6em;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: rgba(0,0,0,.06);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
transition: background-color .3s;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
&::before {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './useResizeObserver'
|
||||||
|
export * from './useValidate'
|
||||||
|
export * from './useGroup'
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
|
||||||
|
type GroupActiveType = {
|
||||||
|
value?: number
|
||||||
|
label?: string
|
||||||
|
len?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const useGroup = (openGroup: boolean = false) => {
|
||||||
|
|
||||||
|
const groupOptions = ref<Array<GroupActiveType>>([])
|
||||||
|
const groupActive = reactive<GroupActiveType>({
|
||||||
|
value: undefined,
|
||||||
|
label: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前选择的分组id
|
||||||
|
* @param key
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
const updateGroupActive = (key: number, name: string) => {
|
||||||
|
if (openGroup) {
|
||||||
|
groupActive.value = key
|
||||||
|
groupActive.label = name
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const addGroup = (val: GroupActiveType) => {
|
||||||
|
if (openGroup) {
|
||||||
|
groupOptions.value.push(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeGroup = (index: number) => {
|
||||||
|
if (openGroup) {
|
||||||
|
groupOptions.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateGroupOptions = (options: Array<GroupActiveType> = []) => {
|
||||||
|
if (openGroup) {
|
||||||
|
groupOptions.value = options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupOptions,
|
||||||
|
groupActive,
|
||||||
|
updateGroupActive,
|
||||||
|
addGroup,
|
||||||
|
removeGroup,
|
||||||
|
updateGroupOptions
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import {onBeforeUnmount, Ref} from "vue";
|
||||||
|
import ResizeObserver from "resize-observer-polyfill";
|
||||||
|
import {debounce} from "lodash-es";
|
||||||
|
|
||||||
|
export const useResizeObserver = (tableWrapper: Ref<HTMLElement>, cb: Function) => {
|
||||||
|
|
||||||
|
let observer: ResizeObserver
|
||||||
|
|
||||||
|
const onResize = (e: any[]) => {
|
||||||
|
let rect = {}
|
||||||
|
for (const entry of e) {
|
||||||
|
rect = entry.contentRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(rect, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
observer = new ResizeObserver(debounce(onResize, 100))
|
||||||
|
observer.observe(tableWrapper.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
observer?.unobserve(tableWrapper.value)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
import {Ref, watch} from "vue";
|
||||||
|
import Schema from "async-validator";
|
||||||
|
import {handlePureRecord, collectValidateRules} from "../utils";
|
||||||
|
import type {ColumnsType } from "../utils";
|
||||||
|
|
||||||
|
type DataSourceType = Array<Record<string, any> & { __validate_id?: string, __validate_index?: number}>
|
||||||
|
|
||||||
|
export type ErrorItem = {
|
||||||
|
message: string
|
||||||
|
__serial: number
|
||||||
|
__dataIndex: number
|
||||||
|
|
||||||
|
field: string
|
||||||
|
|
||||||
|
filedValue: any
|
||||||
|
|
||||||
|
groupId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OptionsType = {
|
||||||
|
onError?: (err: Array<Array<ErrorItem>>) => void
|
||||||
|
onSuccess?: () => void
|
||||||
|
onEdit?: (item: any) => void
|
||||||
|
|
||||||
|
validateRowKey?: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useValidate = (dataSource: Ref<DataSourceType>, columns: ColumnsType, rowKey: string, options: OptionsType = {}): {
|
||||||
|
validate: () => Promise<any>
|
||||||
|
validateItem: (data: Record<string, any> ) => Promise<any>
|
||||||
|
errorMap: Ref<Record<string, any>>
|
||||||
|
rules: Ref<Record<string, any>>
|
||||||
|
} => {
|
||||||
|
const errorMap = ref({})
|
||||||
|
|
||||||
|
let schemaInstance: any
|
||||||
|
let rules = ref({})
|
||||||
|
|
||||||
|
const _options = Object.assign({ validateRowKey: false }, options)
|
||||||
|
|
||||||
|
|
||||||
|
const validateItem = (data: Record<string, any>, index: number = 0): Promise<any> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
schemaInstance.validate(data, { firstFields: true, index}, (err: any[]) => {
|
||||||
|
if (err?.length) {
|
||||||
|
reject(err.map(item => ({ ...item, __serial: data.__serial, __dataIndex: index })))
|
||||||
|
} else {
|
||||||
|
resolve(data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const filterDataSource = dataSource.value
|
||||||
|
|
||||||
|
const len = filterDataSource.length
|
||||||
|
const error: any[] = []
|
||||||
|
const success: any[] = []
|
||||||
|
let validateLen = 0
|
||||||
|
const end = () => {
|
||||||
|
validateLen += 1
|
||||||
|
if (validateLen === len) {
|
||||||
|
const isSuccess = !Object.keys(error).length
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
resolve(success)
|
||||||
|
_options.onSuccess?.()
|
||||||
|
} else {
|
||||||
|
_options.onError?.(error)
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateRowKey = _options.validateRowKey
|
||||||
|
|
||||||
|
if (filterDataSource.length) {
|
||||||
|
filterDataSource.forEach((record, index) => {
|
||||||
|
if (validateRowKey || record[rowKey]) {
|
||||||
|
validateItem(record, index).then(res => {
|
||||||
|
success.push(handlePureRecord(res))
|
||||||
|
end()
|
||||||
|
}).catch(err => {
|
||||||
|
error.push(err)
|
||||||
|
end()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resolve(filterDataSource)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createValidate = () => {
|
||||||
|
rules.value = collectValidateRules(columns)
|
||||||
|
schemaInstance = new Schema(rules.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
createValidate()
|
||||||
|
|
||||||
|
return {
|
||||||
|
validate,
|
||||||
|
validateItem,
|
||||||
|
errorMap,
|
||||||
|
rules,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export { default as EditTable } from './Table.vue'
|
||||||
|
export { default as EditTableFormItem } from './TableFormItem.vue'
|
||||||
|
export { default as EditTableGroup } from './group.vue'
|
||||||
|
|
||||||
|
export * from './components'
|
|
@ -0,0 +1,38 @@
|
||||||
|
export const bodyProps = () => ({
|
||||||
|
dataSource: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ([])
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ([])
|
||||||
|
},
|
||||||
|
cellHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 50
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 300
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
disableMenu: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
rowKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
openGroup: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rowSelection: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,96 @@
|
||||||
|
import type { ColumnType } from 'ant-design-vue/lib/table'
|
||||||
|
import { omit} from "lodash-es";
|
||||||
|
|
||||||
|
|
||||||
|
type ColumnsFormType = {
|
||||||
|
rules: Array<Record<string, any>>,
|
||||||
|
watch: Array<string>
|
||||||
|
}
|
||||||
|
export type ColumnsType = Array<ColumnType & { form?: ColumnsFormType }>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则收集器,收集columns中的rules和watch
|
||||||
|
* @param columns
|
||||||
|
*/
|
||||||
|
export const collectValidateRules = (columns: ColumnsType): Record<string, any> => {
|
||||||
|
const rules = {}
|
||||||
|
columns.forEach(item => {
|
||||||
|
if (item.form) {
|
||||||
|
if (item.form.rules) {
|
||||||
|
rules[item.dataIndex as string] = item.form.rules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handlePureRecord = (record: Record<string, any>) => {
|
||||||
|
if (!record) return {}
|
||||||
|
|
||||||
|
// if (record.expands) {
|
||||||
|
// record.expands = omit(record.expands, ['isProduct'])
|
||||||
|
// }
|
||||||
|
return omit(record, ['__serial', '__index', '__top', '__selected', '__key', '__dataIndex'])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const handleColumnsWidth = (columns: any[], warpWidth: number): any[] => {
|
||||||
|
|
||||||
|
let newColumns = [...columns]
|
||||||
|
let noWidthLen = 0 // 没有width属性的长度
|
||||||
|
let hasWidthCount = 0 // 有width属性的合计
|
||||||
|
let average = 0 // 每个column宽度
|
||||||
|
let parseAverage = 0 // 取整宽度
|
||||||
|
let decimalCount = 0 // 收集每个取整后的小数
|
||||||
|
let lastNoWidthIndex : number | undefined = undefined // 最后一个没有width属性的位置
|
||||||
|
|
||||||
|
newColumns.forEach(item => {
|
||||||
|
if (item.width) {
|
||||||
|
hasWidthCount += item.width
|
||||||
|
} else {
|
||||||
|
noWidthLen += 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (noWidthLen) {
|
||||||
|
average = (warpWidth - hasWidthCount) / noWidthLen // 剩余平分分配宽度
|
||||||
|
parseAverage = Math.trunc(average)
|
||||||
|
decimalCount = (average - parseAverage) * noWidthLen
|
||||||
|
}
|
||||||
|
|
||||||
|
newColumns.forEach((item, index) => {
|
||||||
|
if (!item.width) {
|
||||||
|
lastNoWidthIndex = index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return newColumns.map((item, index) => {
|
||||||
|
let _width = item.width
|
||||||
|
let left = 0
|
||||||
|
|
||||||
|
if (!item.width) {
|
||||||
|
_width = parseAverage
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === lastNoWidthIndex) {
|
||||||
|
_width = Math.trunc(decimalCount) + parseAverage
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index !== 0) {
|
||||||
|
left = newColumns[index - 1].width + newColumns[index - 1].left
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
item.width = _width
|
||||||
|
item.left = left
|
||||||
|
// prev.push({
|
||||||
|
// ...next,
|
||||||
|
// width: _width,
|
||||||
|
// left
|
||||||
|
// })
|
||||||
|
return item
|
||||||
|
}, [])
|
||||||
|
}
|
|
@ -1,16 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<j-form-item name="type" label="读写类型" :rules="[
|
<j-form-item
|
||||||
|
name="type"
|
||||||
|
label="读写类型"
|
||||||
|
:rules="[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请选择读写类型'
|
message: '请选择读写类型'
|
||||||
}
|
}
|
||||||
]">
|
]"
|
||||||
<j-select
|
style="margin-bottom: 0"
|
||||||
|
>
|
||||||
|
<!-- <j-select-->
|
||||||
|
<!-- v-model:value="myValue"-->
|
||||||
|
<!-- mode="multiple"-->
|
||||||
|
<!-- :options="options"-->
|
||||||
|
<!-- :disabled="disabled"-->
|
||||||
|
<!-- :dropdownStyle="{-->
|
||||||
|
<!-- zIndex: 1071-->
|
||||||
|
<!-- }"-->
|
||||||
|
<!-- :getPopupContainer="(node) => tableWrapperRef || node"-->
|
||||||
|
<!-- placeholder="请选择读写类型"-->
|
||||||
|
<!-- @change="onChange"-->
|
||||||
|
<!-- />-->
|
||||||
|
<CheckButton
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
mode="multiple"
|
:multiple="true"
|
||||||
:options="options"
|
:options="options"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
placeholder="请选择读写类型"
|
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
|
@ -19,6 +35,7 @@
|
||||||
<script setup lang="ts" name="ReadType">
|
<script setup lang="ts" name="ReadType">
|
||||||
|
|
||||||
import type {PropType} from "vue";
|
import type {PropType} from "vue";
|
||||||
|
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||||
|
|
||||||
type Emit = {
|
type Emit = {
|
||||||
(e: 'update:value', data: Array<string>): void
|
(e: 'update:value', data: Array<string>): void
|
||||||
|
@ -43,6 +60,7 @@ const props = defineProps({
|
||||||
const emit = defineEmits<Emit>()
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
const myValue = ref<Array<string>>([])
|
const myValue = ref<Array<string>>([])
|
||||||
|
const tableWrapperRef = useTableWrapper()
|
||||||
|
|
||||||
const onChange = (keys: Array<string>) =>{
|
const onChange = (keys: Array<string>) =>{
|
||||||
myValue.value = keys
|
myValue.value = keys
|
||||||
|
|
|
@ -0,0 +1,326 @@
|
||||||
|
<template>
|
||||||
|
<div ref='dialog' :style='styleName' class='dialog'>
|
||||||
|
<Transition name='dialog'>
|
||||||
|
<div class='dialog-sprite' ref='header'>
|
||||||
|
<div class='header' v-if="title !== false">
|
||||||
|
<span>{{ title }}</span>
|
||||||
|
<a-button size='small' type='text' @click.stop='onClose'>
|
||||||
|
<AIcon type='CloseOutlined' />
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class='dialog-body' :style="bodyStyle">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<!-- <div class='dialog-footer' v-if='slots?.footer'>-->
|
||||||
|
<!-- <slot name='footer'></slot>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
<!-- <template v-for='item in rangeList'>-->
|
||||||
|
<!-- <div :class="{'range': true, [item.classname]: true}" @mousedown.stop='rangeMove($event,item.classname)'></div>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
|
||||||
|
<div :class="{'range': true, 'bottom-right': true}" @mousedown.stop='rangeMove($event,"bottom-right")'></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts'>
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'DragModal'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: [String, Boolean],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
dragRang: {
|
||||||
|
type: [Array , Number],
|
||||||
|
default: [400, 200]
|
||||||
|
},
|
||||||
|
bodyStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emits = defineEmits(['close', 'heightChange'])
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
|
const ele = document.body
|
||||||
|
|
||||||
|
const dialog = ref()
|
||||||
|
const header = ref()
|
||||||
|
const baseWidth = ref(props.width || 400)
|
||||||
|
const baseHeight = ref(props.height || 100)
|
||||||
|
const baseLeft = ref(100)
|
||||||
|
const baseTop = ref(100)
|
||||||
|
|
||||||
|
const rangeList = [
|
||||||
|
// {
|
||||||
|
// classname: 'top-left'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// classname: 'top-right'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// classname: 'bottom-left'
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
classname: 'bottom-right'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const styleName = computed(() => {
|
||||||
|
return {
|
||||||
|
top: getFixed(baseTop.value) + 'px',
|
||||||
|
left: getFixed(baseLeft.value) + 'px',
|
||||||
|
width: getFixed(baseWidth.value) + 'px',
|
||||||
|
height: getFixed(baseHeight.value) + 'px'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getFixed = (val: number) => {
|
||||||
|
return Number(val.toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDrag = () => {
|
||||||
|
let active = false
|
||||||
|
let initialX: number
|
||||||
|
let initialY: number
|
||||||
|
let initialWindowX: number
|
||||||
|
let initialWindowY: number
|
||||||
|
|
||||||
|
header.value.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
|
active = true
|
||||||
|
|
||||||
|
initialX = e.clientX
|
||||||
|
initialY = e.clientY
|
||||||
|
|
||||||
|
initialWindowX = dialog.value.offsetLeft
|
||||||
|
initialWindowY = dialog.value.offsetTop
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
active = false
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
if (active) {
|
||||||
|
const dx = e.clientX - initialX
|
||||||
|
const dy = e.clientY - initialY
|
||||||
|
|
||||||
|
baseLeft.value = initialWindowX + dx
|
||||||
|
baseTop.value = initialWindowY + dy
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
document.onmouseup = () => {
|
||||||
|
document.onmousemove = null
|
||||||
|
document.onmouseup = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangeMove = (e: MouseEvent, position: string) => {
|
||||||
|
//移动的方向
|
||||||
|
let x: boolean = false
|
||||||
|
let y: boolean = false
|
||||||
|
//移动的位置
|
||||||
|
let xp: boolean = false
|
||||||
|
let yp: boolean = false
|
||||||
|
//移动的正负
|
||||||
|
let xc: boolean = false
|
||||||
|
let yc: boolean = false
|
||||||
|
let disX = e.clientX
|
||||||
|
let disY = e.clientY
|
||||||
|
document.onmousemove = e => {
|
||||||
|
if (position === 'bottom-right') {
|
||||||
|
x = true
|
||||||
|
y = true
|
||||||
|
} else if (position === 'bottom-left') {
|
||||||
|
x = true
|
||||||
|
y = true
|
||||||
|
xp = true
|
||||||
|
xc = true
|
||||||
|
} else if (position === 'top-right') {
|
||||||
|
x = true
|
||||||
|
y = true
|
||||||
|
yp = true
|
||||||
|
yc = true
|
||||||
|
} else if (position === 'top-left') {
|
||||||
|
x = true
|
||||||
|
y = true
|
||||||
|
xp = true
|
||||||
|
xc = true
|
||||||
|
yp = true
|
||||||
|
yc = true
|
||||||
|
}
|
||||||
|
let left = e.clientX - disX
|
||||||
|
let top = e.clientY - disY
|
||||||
|
disX = e.clientX
|
||||||
|
disY = e.clientY
|
||||||
|
if (x) {
|
||||||
|
let calc = left
|
||||||
|
|
||||||
|
if (xc) {
|
||||||
|
calc = -calc
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xp) {
|
||||||
|
baseLeft.value = baseLeft.value - calc
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = baseWidth.value + calc
|
||||||
|
|
||||||
|
baseWidth.value = width <= props.dragRang[0] ? props.dragRang[0] : width
|
||||||
|
}
|
||||||
|
if (y) {
|
||||||
|
let calc = top
|
||||||
|
if (yc) {
|
||||||
|
calc = -calc
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yp) {
|
||||||
|
baseTop.value = baseTop.value - calc
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = baseHeight.value + calc
|
||||||
|
|
||||||
|
baseHeight.value = height <= props.dragRang[1] ? props.dragRang[1] : height
|
||||||
|
|
||||||
|
emits('heightChange', baseHeight.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleClear()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
console.log('close---1')
|
||||||
|
emits("close")
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (dialog.value && header.value) {
|
||||||
|
onDrag()
|
||||||
|
}
|
||||||
|
if (ele) {
|
||||||
|
const data = ele?.getBoundingClientRect()
|
||||||
|
baseLeft.value = (data?.right - baseWidth.value) / 2 || 0
|
||||||
|
baseTop.value = data?.top + 200 || 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.height, () => {
|
||||||
|
if (props.height > baseHeight.value) {
|
||||||
|
baseHeight.value = props.height
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='less' scoped>
|
||||||
|
@boxColor: rgb(@primary-color);
|
||||||
|
|
||||||
|
// 弹窗动画
|
||||||
|
.dialog-enter-active,
|
||||||
|
.dialog-leave-active {
|
||||||
|
transition: opacity .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-enter,
|
||||||
|
.dialog-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.dialog-sprite {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 23456765435;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #91CAFF;
|
||||||
|
box-shadow: 0 3px 8px 0 rgba(#1677FF, 0.24);
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
padding: 5px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.range {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 100%;
|
||||||
|
z-index: 23456765436;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-right, .top-left {
|
||||||
|
&:hover {
|
||||||
|
cursor: nwse-resize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-left, .top-right {
|
||||||
|
&:hover {
|
||||||
|
cursor: nesw-resize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right {
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-left {
|
||||||
|
top: -6px;
|
||||||
|
left: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-right {
|
||||||
|
bottom: -6px;
|
||||||
|
right: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-left {
|
||||||
|
bottom: -6px;
|
||||||
|
left: -6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as DragModal } from './DragModal.vue'
|
|
@ -1,28 +1,84 @@
|
||||||
<template>
|
<template>
|
||||||
<template v-if="isPermission">
|
<template v-if="isPermission">
|
||||||
<template v-if="popConfirm">
|
<template v-if="popConfirm">
|
||||||
<j-popconfirm :disabled="!isPermission || props.disabled" :overlayStyle='{width: "220px", zIndex: 1075 }' v-bind="popConfirm">
|
<!-- <j-popconfirm
|
||||||
|
:disabled="!isPermission || props.disabled"
|
||||||
|
:overlayStyle="{ width: '220px', zIndex: 1075 }"
|
||||||
|
v-bind="popConfirm"
|
||||||
|
>
|
||||||
<j-tooltip v-if="tooltip" v-bind="tooltip">
|
<j-tooltip v-if="tooltip" v-bind="tooltip">
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
<j-button
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
:disabled="_isPermission"
|
||||||
|
:style="props.style"
|
||||||
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
</template>
|
</template>
|
||||||
</j-button>
|
</j-button>
|
||||||
</j-tooltip>
|
</j-tooltip>
|
||||||
<j-button v-else v-bind="props" :disabled="_isPermission" >
|
<j-button v-else v-bind="props" :disabled="_isPermission">
|
||||||
|
<slot></slot>
|
||||||
|
<template #icon>
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
</template>
|
||||||
|
</j-button>
|
||||||
|
</j-popconfirm> -->
|
||||||
|
<!-- <a-modal
|
||||||
|
v-if="modalVisible"
|
||||||
|
visible
|
||||||
|
:closable="false"
|
||||||
|
@cancel="modalVisible = false"
|
||||||
|
@ok="modalConfirm"
|
||||||
|
:confirmLoading="confirmLoading"
|
||||||
|
:width="300"
|
||||||
|
centered
|
||||||
|
:maskClosable="false"
|
||||||
|
z-index="9999"
|
||||||
|
>
|
||||||
|
<div class="modalContent">
|
||||||
|
{{ popConfirm.title }}
|
||||||
|
</div>
|
||||||
|
</a-modal> -->
|
||||||
|
<j-tooltip v-if="tooltip" v-bind="tooltip">
|
||||||
|
<slot v-if="noButton"></slot>
|
||||||
|
<j-button
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
:disabled="_isPermission"
|
||||||
|
:style="props.style"
|
||||||
|
@click="showConfirm"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
<template #icon>
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
</template>
|
||||||
|
</j-button>
|
||||||
|
</j-tooltip>
|
||||||
|
<j-button
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
:disabled="_isPermission"
|
||||||
|
@click="showConfirm"
|
||||||
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
</template>
|
</template>
|
||||||
</j-button>
|
</j-button>
|
||||||
</j-popconfirm>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="tooltip">
|
<template v-else-if="tooltip">
|
||||||
<j-tooltip v-bind="tooltip">
|
<j-tooltip v-bind="tooltip">
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
<j-button
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
:disabled="_isPermission"
|
||||||
|
:style="props.style"
|
||||||
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
|
@ -32,7 +88,12 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
<j-button
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
:disabled="_isPermission"
|
||||||
|
:style="props.style"
|
||||||
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
|
@ -42,7 +103,12 @@
|
||||||
</template>
|
</template>
|
||||||
<j-tooltip v-else title="暂无权限,请联系管理员" :placement="placement">
|
<j-tooltip v-else title="暂无权限,请联系管理员" :placement="placement">
|
||||||
<slot v-if="noButton"></slot>
|
<slot v-if="noButton"></slot>
|
||||||
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
<j-button
|
||||||
|
v-else
|
||||||
|
v-bind="props"
|
||||||
|
:disabled="_isPermission"
|
||||||
|
:style="props.style"
|
||||||
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
|
@ -51,31 +117,17 @@
|
||||||
</j-tooltip>
|
</j-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="PermissionButton">
|
<script setup lang="ts" name="PermissionButton">
|
||||||
import { CSSProperties, PropType } from 'vue'
|
import { CSSProperties, PropType } from 'vue';
|
||||||
import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es'
|
import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es';
|
||||||
import { buttonProps } from 'ant-design-vue/es/button/button'
|
import { buttonProps } from 'ant-design-vue/es/button/button';
|
||||||
import { usePermissionStore } from '@/store/permission';
|
import { usePermissionStore } from '@/store/permission';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
|
import { Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
// interface PermissionButtonEmits {
|
|
||||||
// (e: 'click', data: MouseEvent): void;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const emits = defineEmits<PermissionButtonEmits>()
|
|
||||||
|
|
||||||
// interface PermissionButtonProps extends ButtonProps {
|
|
||||||
// tooltip?: TooltipProps;
|
|
||||||
// popConfirm?: PopconfirmProps;
|
|
||||||
// hasPermission?: string | Array<string>;
|
|
||||||
// noButton?: boolean;
|
|
||||||
// }
|
|
||||||
// const props = withDefaults(defineProps<PermissionButtonProps>(), {
|
|
||||||
// noButton: false,
|
|
||||||
// })
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
noButton: {
|
noButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: () => false
|
default: () => false,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
type: Object as PropType<TooltipProps>,
|
type: Object as PropType<TooltipProps>,
|
||||||
|
@ -84,40 +136,69 @@ const props = defineProps({
|
||||||
type: Object as PropType<PopconfirmProps>,
|
type: Object as PropType<PopconfirmProps>,
|
||||||
},
|
},
|
||||||
hasPermission: {
|
hasPermission: {
|
||||||
type: [String , Array, Boolean],
|
type: [String, Array, Boolean],
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
type: Object as PropType<CSSProperties>
|
type: Object as PropType<CSSProperties>,
|
||||||
},
|
},
|
||||||
placement:{
|
placement: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'top'
|
default: 'top',
|
||||||
},
|
},
|
||||||
...omit(buttonProps(), 'icon')
|
...omit(buttonProps(), 'icon'),
|
||||||
})
|
});
|
||||||
|
// const modalVisible = ref(false);
|
||||||
// const { tooltip, popConfirm, hasPermission, noButton, ..._buttonProps } = props;
|
// const confirmLoading = ref(false);
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
const permissionStore = usePermissionStore()
|
|
||||||
|
|
||||||
const isPermission = computed(() => {
|
const isPermission = computed(() => {
|
||||||
if (!props.hasPermission || props.hasPermission === true) {
|
if (!props.hasPermission || props.hasPermission === true) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
return permissionStore.hasPermission(props.hasPermission)
|
return permissionStore.hasPermission(props.hasPermission);
|
||||||
})
|
});
|
||||||
const _isPermission = computed(() =>
|
const _isPermission = computed(() =>
|
||||||
'hasPermission' in props && isPermission.value
|
'hasPermission' in props && isPermission.value
|
||||||
? 'disabled' in props
|
? 'disabled' in props
|
||||||
? props.disabled as boolean
|
? (props.disabled as boolean)
|
||||||
: false
|
: false
|
||||||
: true
|
: true,
|
||||||
)
|
);
|
||||||
|
|
||||||
// const conform = (e: MouseEvent) => {
|
// const modalConfirm = async (e: MouseEvent) => {
|
||||||
// props.popConfirm?.onConfirm?.(e)
|
// if (typeof props.popConfirm?.onConfirm === 'function') {
|
||||||
|
// confirmLoading.value = true;
|
||||||
|
// const res: any = await props.popConfirm?.onConfirm(e)?.finally(()=>{
|
||||||
|
// confirmLoading.value = false;
|
||||||
|
// modalVisible.value = false;
|
||||||
|
// return
|
||||||
|
// });
|
||||||
|
// if(!res?.finally){
|
||||||
|
// confirmLoading.value = false;
|
||||||
|
// modalVisible.value = false;
|
||||||
// }
|
// }
|
||||||
|
// } else {
|
||||||
|
// modalVisible.value = false;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
const showConfirm = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: props.popConfirm?.title,
|
||||||
|
content: props.popConfirm?.content,
|
||||||
|
onOk() {
|
||||||
|
return props.popConfirm?.onConfirm();
|
||||||
|
},
|
||||||
|
onCancel() {},
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
// .modalContent {
|
||||||
|
// text-align: center;
|
||||||
|
// }
|
||||||
|
// .control {
|
||||||
|
// margin-top: 20px;
|
||||||
|
// display: flex;
|
||||||
|
// justify-content: space-between;
|
||||||
|
// }
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -75,24 +75,20 @@
|
||||||
>
|
>
|
||||||
<j-space>
|
<j-space>
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
<j-popconfirm
|
<PermissionButton
|
||||||
title="确认删除?"
|
type="text"
|
||||||
ok-text="确认"
|
:popConfirm="{
|
||||||
cancel-text="取消"
|
title: '确认删除?',
|
||||||
@confirm="(e: any) => {
|
onConfirm: (e: any) => {
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
deleteHistory(item.key);
|
deleteHistory(item.key);
|
||||||
}
|
}
|
||||||
"
|
}"
|
||||||
>
|
>
|
||||||
<AIcon
|
<AIcon
|
||||||
type="DeleteOutlined"
|
type="DeleteOutlined"
|
||||||
@click="
|
|
||||||
(e:any) =>
|
|
||||||
e?.stopPropagation()
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</j-popconfirm>
|
</PermissionButton>
|
||||||
</j-space>
|
</j-space>
|
||||||
</j-menu-item>
|
</j-menu-item>
|
||||||
</j-menu>
|
</j-menu>
|
||||||
|
@ -316,12 +312,15 @@ const getHistory = async () => {
|
||||||
* 删除历史分屏
|
* 删除历史分屏
|
||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
const deleteHistory = async (id: string) => {
|
const deleteHistory = (id: string) => {
|
||||||
const res = await deleteSearchHistory(DEFAULT_SAVE_CODE, id);
|
const response = deleteSearchHistory(DEFAULT_SAVE_CODE, id);
|
||||||
if (res.success) {
|
response.then((res)=>{
|
||||||
|
if(res.success){
|
||||||
getHistory();
|
getHistory();
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return response
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -458,16 +457,16 @@ defineExpose({
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './index.less';
|
@import './index.less';
|
||||||
:deep(.live-player-stretch-btn){
|
:deep(.live-player-stretch-btn) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
:deep(.vjs-icon-spinner){
|
:deep(.vjs-icon-spinner) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.refreshBtn{
|
.refreshBtn {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.refreshBtn:hover{
|
.refreshBtn:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.live-player-warp {
|
.live-player-warp {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.live-player-content {
|
.live-player-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
&.screen-1 {
|
&.screen-1 {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
@ -46,7 +48,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 99;
|
||||||
border: 2px solid red;
|
border: 2px solid red;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-screen {
|
.full-screen {
|
||||||
|
|
|
@ -1,28 +1,37 @@
|
||||||
<!-- 视频播放 -->
|
<!-- 视频播放 -->
|
||||||
<template>
|
<template>
|
||||||
<LivePlayer
|
<!-- <LivePlayer-->
|
||||||
ref="player"
|
<!-- ref="player"-->
|
||||||
fluent
|
<!-- fluent-->
|
||||||
:protocol="props.protocol || 'mp4'"
|
<!-- :protocol="props.protocol || 'mp4'"-->
|
||||||
:class="props.className"
|
<!-- :class="props.className"-->
|
||||||
:loading="props.loading"
|
<!-- :loading="props.loading"-->
|
||||||
:live="'live' in props ? props.live !== false : true"
|
<!-- :live="'live' in props ? props.live !== false : true"-->
|
||||||
:autoplay="'autoplay' in props ? props.autoplay !== false : true"
|
<!-- :autoplay="'autoplay' in props ? props.autoplay !== false : true"-->
|
||||||
:muted="'muted' in props ? props.muted !== false : true"
|
<!-- :muted="'muted' in props ? props.muted !== false : true"-->
|
||||||
:hide-big-play-button="true"
|
<!-- :hide-big-play-button="true"-->
|
||||||
:poster="props.poster || ''"
|
<!-- :poster="props.poster || ''"-->
|
||||||
:timeout="props.timeout || 30"
|
<!-- :timeout="props.timeout || 30"-->
|
||||||
:video-url="url || ''"
|
<!-- :video-url="url || ''"-->
|
||||||
@play="props.onPlay"
|
<!-- @play="props.onPlay"-->
|
||||||
@pause="props.onPause"
|
<!-- @pause="props.onPause"-->
|
||||||
@ended="props.onEnded"
|
<!-- @ended="props.onEnded"-->
|
||||||
@error="props.onError"
|
<!-- @error="props.onError"-->
|
||||||
@timeupdate="props.onTimeUpdate"
|
<!-- @timeupdate="props.onTimeUpdate"-->
|
||||||
/>
|
<!-- />-->
|
||||||
|
<div class="media-player-container" >
|
||||||
|
<div ref="playerElement">
|
||||||
|
<span v-if="!props.url">
|
||||||
|
No Video
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" name="LivePlayer">
|
||||||
import LivePlayer from '@liveqing/liveplayer-v3';
|
import Player, { Events, Sniffer } from 'xgplayer'
|
||||||
|
import { settingEnum } from './utils'
|
||||||
|
|
||||||
type PlayerProps = {
|
type PlayerProps = {
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@ -35,7 +44,7 @@ type PlayerProps = {
|
||||||
updateTime?: number;
|
updateTime?: number;
|
||||||
key?: string | number;
|
key?: string | number;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
protocol?: 'mp4' | 'flv' | 'hls' | 'rtc';
|
protocol?: 'mp4' | 'flv' | 'm3u8' | 'rtc';
|
||||||
onDestroy?: (e?: any) => void;
|
onDestroy?: (e?: any) => void;
|
||||||
onMessage?: (msg: any) => void;
|
onMessage?: (msg: any) => void;
|
||||||
onError?: (err: any) => void;
|
onError?: (err: any) => void;
|
||||||
|
@ -52,34 +61,115 @@ type PlayerProps = {
|
||||||
|
|
||||||
const props = defineProps<PlayerProps>();
|
const props = defineProps<PlayerProps>();
|
||||||
|
|
||||||
const player = ref<HTMLVideoElement>();
|
const playerElement = ref<HTMLVideoElement>();
|
||||||
const url = ref(props.url);
|
let player: any = null
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
url.value = props.url;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 播放
|
* 播放
|
||||||
*/
|
*/
|
||||||
const play = () => {
|
const play = () => {
|
||||||
player.value?.play();
|
player?.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停
|
* 暂停
|
||||||
*/
|
*/
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
player.value?.pause();
|
player?.pause();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停状态
|
* 暂停状态
|
||||||
*/
|
*/
|
||||||
const paused = () => {
|
const paused = () => {
|
||||||
return player.value?.paused;
|
return player?.paused;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
if (player) {
|
||||||
|
player.destroy()
|
||||||
|
player = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
|
||||||
|
destroy()
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
console.log(props.protocol)
|
||||||
|
|
||||||
|
player = new Player({
|
||||||
|
el: playerElement.value,
|
||||||
|
// autoplay: props.autoplay ?? true,
|
||||||
|
url: props.url,
|
||||||
|
isLive: props.live,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
hasStart: false,
|
||||||
|
playbackRate: false,
|
||||||
|
ignores: ['progress', 'volume', 'time', 'replay'],
|
||||||
|
closeVideoClick: true,
|
||||||
|
closeVideoDblclick: true,
|
||||||
|
closeVideoTouch: true,
|
||||||
|
closePlayerBlur: true,
|
||||||
|
closeControlsBlur: true,
|
||||||
|
closeFocusVideoFocus: true,
|
||||||
|
closePlayVideoFocus: true,
|
||||||
|
...settingEnum[props.protocol || 'mp4']
|
||||||
|
})
|
||||||
|
|
||||||
|
player.on(Events.PLAY, (ev) => {
|
||||||
|
console.log('-播放开始-', ev);
|
||||||
|
props.onPlay?.()
|
||||||
|
})
|
||||||
|
player.on(Events.PAUSE, (ev) => {
|
||||||
|
console.log('-播放暂停-', ev);
|
||||||
|
props.onPause?.()
|
||||||
|
})
|
||||||
|
player.on(Events.ENDED, (ev) => {
|
||||||
|
console.log('-播放结束-', ev);
|
||||||
|
props.onEnded?.()
|
||||||
|
})
|
||||||
|
player.on(Events.TIME_UPDATE, (ev) => {
|
||||||
|
props.onTimeUpdate?.(ev)
|
||||||
|
})
|
||||||
|
player.on(Events.CANPLAY, (ev) => {
|
||||||
|
console.log('-媒体数据加载好了-', ev);
|
||||||
|
if (props.autoplay !== false) {
|
||||||
|
play()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player.on(Events.SEEKED, (ev) => {
|
||||||
|
console.log('-跳着播放-', ev);
|
||||||
|
if (props.live) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
player.on(Events.ERROR, (ev) => {
|
||||||
|
console.log('-播放错误-', ev);
|
||||||
|
if (props.live) {
|
||||||
|
setTimeout(() => {
|
||||||
|
init()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
props.onError?.(ev)
|
||||||
|
})
|
||||||
|
}, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.url, () => {
|
||||||
|
if (props.url) {
|
||||||
|
nextTick(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
destroy()
|
||||||
|
})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
play,
|
play,
|
||||||
pause,
|
pause,
|
||||||
|
@ -93,4 +183,13 @@ defineExpose({
|
||||||
:deep(.vjs-icon-spinner){
|
:deep(.vjs-icon-spinner){
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.media-player-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #000;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import FlvPlugin from 'xgplayer-flv'
|
||||||
|
import HlsPlugin from "xgplayer-hls"
|
||||||
|
export const settingEnum = {
|
||||||
|
mp4: {
|
||||||
|
isLive: false,
|
||||||
|
},
|
||||||
|
flv: {
|
||||||
|
plugins: [FlvPlugin],
|
||||||
|
},
|
||||||
|
m3u8: {
|
||||||
|
plugins: [HlsPlugin],
|
||||||
|
}
|
||||||
|
}
|
|
@ -121,8 +121,8 @@ const handleRadio = (item: any) => {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
color: #1d39c4;
|
color: @primary-color;
|
||||||
border-color: #1d39c4;
|
border-color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,8 +195,8 @@ const handleRadio = (item: any) => {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
color: #1d39c4;
|
color: @primary-color;
|
||||||
border-color: #1d39c4;
|
border-color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
<div class="value-item-warp">
|
<div class="value-item-warp">
|
||||||
<j-select
|
<j-select
|
||||||
v-if="typeMap.get(itemType) === 'select'"
|
v-if="typeMap.get(itemType) === 'select'"
|
||||||
:mode="mode"
|
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
:options="options"
|
|
||||||
allowClear
|
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
allowClear
|
||||||
|
:mode="mode"
|
||||||
|
v-bind="extra"
|
||||||
|
:options="options"
|
||||||
:getPopupContainer="getPopupContainer"
|
:getPopupContainer="getPopupContainer"
|
||||||
@change='selectChange'
|
@change='selectChange'
|
||||||
/>
|
/>
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
allowClear
|
allowClear
|
||||||
valueFormat="HH:mm:ss"
|
valueFormat="HH:mm:ss"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
v-bind="extra"
|
||||||
:getPopupContainer="getPopupContainer"
|
:getPopupContainer="getPopupContainer"
|
||||||
@change='timeChange'
|
@change='timeChange'
|
||||||
/>
|
/>
|
||||||
|
@ -26,7 +28,8 @@
|
||||||
allowClear
|
allowClear
|
||||||
showTime
|
showTime
|
||||||
valueFormat="YYYY-MM-DD HH:mm:ss"
|
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||||
style="width: 100%"
|
style="width: 100%;"
|
||||||
|
v-bind="extra"
|
||||||
:getPopupContainer="getPopupContainer"
|
:getPopupContainer="getPopupContainer"
|
||||||
@change='dateChange'
|
@change='dateChange'
|
||||||
/>
|
/>
|
||||||
|
@ -35,12 +38,14 @@
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
allowClear
|
allowClear
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
v-bind="extra"
|
||||||
@change='inputChange'
|
@change='inputChange'
|
||||||
/>
|
/>
|
||||||
<j-input
|
<j-input
|
||||||
allowClear
|
allowClear
|
||||||
v-else-if="typeMap.get(itemType) === 'object'"
|
v-else-if="typeMap.get(itemType) === 'object'"
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
|
v-bind="extra"
|
||||||
@change='inputChange'
|
@change='inputChange'
|
||||||
>
|
>
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
|
@ -50,11 +55,13 @@
|
||||||
<GeoComponent
|
<GeoComponent
|
||||||
v-else-if="typeMap.get(itemType) === 'geoPoint'"
|
v-else-if="typeMap.get(itemType) === 'geoPoint'"
|
||||||
v-model:point="myValue"
|
v-model:point="myValue"
|
||||||
|
v-bind="extra"
|
||||||
@change='inputChange'
|
@change='inputChange'
|
||||||
/>
|
/>
|
||||||
<j-input
|
<j-input
|
||||||
v-else-if="typeMap.get(itemType) === 'file'"
|
v-else-if="typeMap.get(itemType) === 'file'"
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
|
v-bind="extra"
|
||||||
placeholder="请输入链接"
|
placeholder="请输入链接"
|
||||||
allowClear
|
allowClear
|
||||||
@change='inputChange'
|
@change='inputChange'
|
||||||
|
@ -75,6 +82,7 @@
|
||||||
v-else-if="typeMap.get(itemType) === 'password'"
|
v-else-if="typeMap.get(itemType) === 'password'"
|
||||||
allowClear
|
allowClear
|
||||||
type="password"
|
type="password"
|
||||||
|
v-bind="extra"
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change='inputChange'
|
@change='inputChange'
|
||||||
|
@ -85,6 +93,7 @@
|
||||||
allowClear
|
allowClear
|
||||||
type="text"
|
type="text"
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
|
v-bind="extra"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change='inputChange'
|
@change='inputChange'
|
||||||
/>
|
/>
|
||||||
|
@ -113,11 +122,10 @@ import { PropType } from 'vue';
|
||||||
import { UploadChangeParam, UploadFile } from 'ant-design-vue';
|
import { UploadChangeParam, UploadFile } from 'ant-design-vue';
|
||||||
import { DefaultOptionType } from 'ant-design-vue/lib/select';
|
import { DefaultOptionType } from 'ant-design-vue/lib/select';
|
||||||
import GeoComponent from '@/components/GeoComponent/index.vue';
|
import GeoComponent from '@/components/GeoComponent/index.vue';
|
||||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
import { TOKEN_KEY } from '@/utils/variable';
|
||||||
import { LocalStore } from '@/utils/comm';
|
import { LocalStore } from '@/utils/comm';
|
||||||
import { ItemData, ITypes } from './types';
|
import { ItemData, ITypes } from './types';
|
||||||
import { FILE_UPLOAD } from '@/api/comm';
|
import { FILE_UPLOAD } from '@/api/comm';
|
||||||
import { Upload } from 'jetlinks-ui-components'
|
|
||||||
|
|
||||||
type Emits = {
|
type Emits = {
|
||||||
(e: 'update:modelValue', data: string | number | boolean): void;
|
(e: 'update:modelValue', data: string | number | boolean): void;
|
||||||
|
@ -157,6 +165,10 @@ const props = defineProps({
|
||||||
getPopupContainer: {
|
getPopupContainer: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: undefined
|
default: undefined
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// type Props = {
|
// type Props = {
|
||||||
|
@ -182,6 +194,7 @@ const componentsType = ref<ITypes>({
|
||||||
object: 'object',
|
object: 'object',
|
||||||
geoPoint: 'geoPoint',
|
geoPoint: 'geoPoint',
|
||||||
file: 'file',
|
file: 'file',
|
||||||
|
time: 'time',
|
||||||
});
|
});
|
||||||
const typeMap = new Map(Object.entries(componentsType.value));
|
const typeMap = new Map(Object.entries(componentsType.value));
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,6 @@ export type ITypes = {
|
||||||
object: string
|
object: string
|
||||||
geoPoint: string
|
geoPoint: string
|
||||||
file: string
|
file: string
|
||||||
|
|
||||||
|
time: string
|
||||||
}
|
}
|
|
@ -12,12 +12,16 @@ import { BasicLayoutPage, BlankLayoutPage, FullPage } from './Layout'
|
||||||
import RadioCard from './RadioCard/index.vue'
|
import RadioCard from './RadioCard/index.vue'
|
||||||
import { PageContainer, AIcon, Ellipsis } from 'jetlinks-ui-components'
|
import { PageContainer, AIcon, Ellipsis } from 'jetlinks-ui-components'
|
||||||
import MarkDown from './Markdown'
|
import MarkDown from './Markdown'
|
||||||
|
import CardSelect from './CardSelect'
|
||||||
// import Ellipsis from './Ellipsis/index.vue'
|
// import Ellipsis from './Ellipsis/index.vue'
|
||||||
import JEmpty from './Empty/index.vue'
|
import JEmpty from './Empty/index.vue'
|
||||||
import AMapComponent from './AMapComponent/index.vue'
|
import AMapComponent from './AMapComponent/AMap.vue'
|
||||||
import PathSimplifier from './AMapComponent/PathSimplifier.vue'
|
import PathSimplifier from './AMapComponent/PathSimplifier.vue'
|
||||||
import ValueItem from './ValueItem/index.vue'
|
import ValueItem from './ValueItem/index.vue'
|
||||||
import RowPagination from './RowPagination/index.vue'
|
import RowPagination from './RowPagination/index.vue'
|
||||||
|
import LevelIcon from './AlarmLeveIcon/index.vue'
|
||||||
|
import CheckButton from "./CheckButton";
|
||||||
|
import ConfirmModal from './ConfirmModal/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App) {
|
install(app: App) {
|
||||||
|
@ -43,5 +47,9 @@ export default {
|
||||||
.component('FullPage', FullPage)
|
.component('FullPage', FullPage)
|
||||||
.component('RadioCard', RadioCard)
|
.component('RadioCard', RadioCard)
|
||||||
.component('MarkDown', MarkDown)
|
.component('MarkDown', MarkDown)
|
||||||
|
.component('CardSelect', CardSelect)
|
||||||
|
.component('CheckButton', CheckButton)
|
||||||
|
.component('ConfirmModal',ConfirmModal)
|
||||||
|
.component('LevelIcon',LevelIcon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue