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', // 格式(不影响代码变动)
|
||||
'revert', // 撤销commit 回滚上一版本
|
||||
'perf', // 性能优化
|
||||
'remove', //删除
|
||||
'release', //
|
||||
]
|
||||
],
|
||||
'scope-case': [0],
|
||||
|
@ -24,11 +26,11 @@ module.exports = {
|
|||
rules: {
|
||||
"commit-rule": ({ raw }) => {
|
||||
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(",")}`
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,5 @@ components.d.ts
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.history
|
||||
.history
|
||||
du-i18n.config.json
|
||||
|
|
4
build.sh
4
build.sh
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
docker build -t 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.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.3.0-SNAPSHOT
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
theme: {
|
||||
'primary-color': '#1d39c4',
|
||||
'primary-color': '#1677FF',
|
||||
},
|
||||
logo: '/favicon.ico', // 浏览器标签页logo
|
||||
title: 'Jetlinks', // 浏览器标签页title
|
||||
|
@ -10,4 +10,4 @@ export default {
|
|||
mode: 'inline',
|
||||
theme: 'light', // 'dark' 'light'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "2.2.0",
|
||||
"scripts": {
|
||||
"dev": "vite --mode develop",
|
||||
"dev:force": "vite --force --mode develop",
|
||||
|
@ -14,19 +14,27 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||
"@vuemap/vue-amap": "^1.1.20",
|
||||
"@vuemap/vue-amap": "^2.1.2",
|
||||
"@vueuse/core": "^9.10.0",
|
||||
"ant-design-vue": "^3.2.15",
|
||||
"async-validator": "^4.2.5",
|
||||
"axios": "^1.2.1",
|
||||
"colorpicker-v3": "^2.10.2",
|
||||
"cronstrue": "^2.50.0",
|
||||
"driver.js": "^0.9.8",
|
||||
"echarts": "^5.4.1",
|
||||
"event-source-polyfill": "^1.0.31",
|
||||
"global": "^4.4.0",
|
||||
"jetlinks-store": "^0.0.3",
|
||||
"jetlinks-ui-components": "^1.0.38",
|
||||
"jetlinks-ui-components": "^1.0.47",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
|
@ -46,9 +54,10 @@
|
|||
"markdown-it-toc-done-right": "^4.2.0",
|
||||
"marked": "^4.2.12",
|
||||
"moment": "^2.29.4",
|
||||
"monaco-editor": "^0.36.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"nrm": "^1.2.5",
|
||||
"pinia": "^2.0.28",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"unplugin-auto-import": "^0.12.1",
|
||||
|
@ -57,10 +66,15 @@
|
|||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "3.3.4",
|
||||
"vue-cropper": "^1.0.9",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-json-viewer": "^3.0.4",
|
||||
"vue-router": "^4.1.6",
|
||||
"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": {
|
||||
"@commitlint/cli": "^17.4.1",
|
||||
|
|
|
@ -295,7 +295,6 @@ function getSideEffects(compName: string, options: JetlinksVueResolverOptions, _
|
|||
return
|
||||
const lib = options.cjs ? 'lib' : 'es'
|
||||
const packageName = options?.packageName || 'jetlinks-ui-components'
|
||||
|
||||
if (importStyle === 'less' || importLess) {
|
||||
const styleDir = getStyleDir(compName, _isAntd)
|
||||
return `${packageName}/${lib}/${styleDir}/style`
|
||||
|
@ -319,8 +318,25 @@ const primitiveNames = ['AIcon','Affix', 'Anchor', 'AnchorLink', 'message', 'Not
|
|||
'DataTableObject',
|
||||
'CheckButton',
|
||||
]
|
||||
|
||||
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>
|
||||
|
||||
function genJetlinksNames(primitiveNames: string[]): void {
|
||||
|
@ -356,11 +372,20 @@ export function JetlinksVueResolver(options: JetlinksVueResolverOptions = {}): a
|
|||
}
|
||||
const _isJetlinks = isJetlinks(name)
|
||||
const _isAntd = isAntdv(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)
|
||||
options.packageName = _isJetlinks ? 'jetlinks-ui-components' : 'ant-design-vue'
|
||||
const path = `${options.packageName}/${options.cjs ? 'lib' : 'es'}`
|
||||
const stylePath = getSideEffects(importName, options, _isAntd)
|
||||
|
||||
return {
|
||||
name: importName,
|
||||
from: path,
|
||||
|
@ -369,4 +394,4 @@ export function JetlinksVueResolver(options: JetlinksVueResolverOptions = {}): a
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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>
|
||||
<ConfigProvider :locale='zhCN'>
|
||||
<ConfigProvider
|
||||
:locale='zhCN'
|
||||
:IconConfig="{
|
||||
scriptUrl: '//at.alicdn.com/t/c/font_4035907_i1jazcune3.js'
|
||||
}"
|
||||
>
|
||||
<router-view />
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
|
@ -9,7 +14,6 @@ import { ConfigProvider } from 'jetlinks-ui-components'
|
|||
import zhCN from 'jetlinks-ui-components/es/locale/zh_CN';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useSystem } from './store/system';
|
||||
import DefaultSetting from '../config/config'
|
||||
import {LocalStore} from "@/utils/comm";
|
||||
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 FileUpload = `${BASE_API_PATH}/file/upload`;
|
||||
|
||||
/**
|
||||
* 保存查询记录
|
||||
* @param data
|
||||
|
@ -31,11 +33,11 @@ export const systemVersion = () => server.get<{edition?: string}>('/system/versi
|
|||
|
||||
/**
|
||||
* 聚合查询
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryDashboard = (data: Record<string, any>) => server.post(`/dashboard/_multi`, data)
|
||||
|
||||
export const fileUpload = (data: any) => server.post('/file/static', data)
|
||||
|
||||
export const lowCodeUrl = () => server.get('/system/config/low-code')
|
||||
export const lowCodeUrl = () => server.get('/system/config/low-code')
|
||||
|
|
|
@ -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)
|
||||
|
||||
// /**
|
||||
// * 查询所有属性id
|
||||
// */
|
||||
// export const getBacnetAllPropertyId = () => server.get('/collect/bacnet/property/ids')
|
||||
|
||||
/**查询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 queryPaginateNot = (data: object) => server.post('/firmware/_query/no-paging',data)
|
||||
|
||||
export const querySystemApi = (data?: object) =>
|
||||
server.post(`/system/config/scopes`, data);
|
||||
|
||||
export const task = (data: Record<string, unknown>) =>
|
||||
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) =>
|
||||
server.get(`/firmware/upgrade/task/${id}`);
|
||||
|
||||
|
@ -29,6 +33,10 @@ export const deleteTask = (id: string) =>
|
|||
export const history = (data: Record<string, unknown>) =>
|
||||
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>) =>
|
||||
server.post(`/firmware/upgrade/history/_count`, data);
|
||||
|
||||
|
@ -41,6 +49,8 @@ export const stopTask = (id: string) =>
|
|||
export const startOneTask = (data: string[]) =>
|
||||
server.post(`/firmware/upgrade/task/_start`, data);
|
||||
|
||||
export const stopOneTask = (data: string[]) =>
|
||||
server.post('/firmware/upgrade/task/_stop',data)
|
||||
// export const queryProduct = (data?: any) =>
|
||||
// server.post(`/device-product/_query/no-paging`, data);
|
||||
export const queryProduct = (data?: any) =>
|
||||
|
|
|
@ -14,7 +14,7 @@ export const resetRule = (productId:string,deviceId:string,data:any) => server.r
|
|||
/**
|
||||
* 删除设备物模型
|
||||
* @param deviceId 设备ID
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const deleteMetadata = (deviceId: string) => server.remove(`/device-instance/${deviceId}/metadata`)
|
||||
|
||||
|
@ -22,7 +22,7 @@ export const deleteMetadata = (deviceId: string) => server.remove(`/device-insta
|
|||
* 保存设备物模型
|
||||
* @param id 设备ID
|
||||
* @param data 物模型
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const saveMetadata = (id: string, data: DeviceMetadata) => server.put(`/device/instance/${id}/metadata`, data)
|
||||
|
||||
|
@ -36,58 +36,58 @@ export const detail = (id: string) => server.get<DeviceInstance>(`/device-instan
|
|||
/**
|
||||
* 查询数据
|
||||
* @param data 分页搜索数据
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const query = (data?: Record<string, any>) => server.post('/device-instance/_query', data)
|
||||
|
||||
/**
|
||||
* 不分页查询设备
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryNoPagingPost = (data?: Record<string, any>) => server.post('/device-instance/_query/no-paging?paging=false', data)
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
* @param id 设备ID
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const _delete = (id: string) => server.remove(`/device-instance/${id}`)
|
||||
|
||||
/**
|
||||
* 启用设备
|
||||
* @param id 设备ID
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const _deploy = (id: string) => server.post(`/device-instance/${id}/deploy`)
|
||||
|
||||
/**
|
||||
* 禁用设备
|
||||
* @param id 设备ID
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const _undeploy = (id: string) => server.post(`/device-instance/${id}/undeploy`)
|
||||
|
||||
/**
|
||||
* 批量激活设备
|
||||
* @param data 设备id数组
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const batchDeployDevice = (data: string[]) => server.put(`/device-instance/batch/_deploy`, data)
|
||||
|
||||
/**
|
||||
* 批量注销设备
|
||||
* @param data 设备id数组
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const batchUndeployDevice = (data: string[]) => server.put(`/device-instance/batch/_unDeploy`, data)
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param data 设备id数组
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const batchDeleteDevice = (data: string[]) => server.put(`/device-instance/batch/_delete`, data)
|
||||
|
||||
|
@ -95,7 +95,7 @@ export const batchDeleteDevice = (data: string[]) => server.put(`/device-instanc
|
|||
* 下载设备模板
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
|
||||
|
||||
|
@ -104,7 +104,7 @@ export const templateDownload = (productId: string, type: string) => server.get(
|
|||
* 设备导入
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import/_withlog?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
||||
|
||||
|
@ -112,21 +112,21 @@ export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boo
|
|||
* 设备导出
|
||||
* @param productId 产品id
|
||||
* @param type 文件类型
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const deviceExport = (productId: string, type: string, params?: any) => server.get(`/device-instance${!!productId ? `/${productId}` : ''}/export.${type}`, params, {responseType: 'blob'})
|
||||
export const deviceExportPath = (productId: string, type: string) => (`${BASE_API_PATH}/device-instance${!!productId ? `/${productId}` : ''}/export.${type}`)
|
||||
/**
|
||||
* 验证设备ID是否重复
|
||||
* @param id 设备id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const isExists = (id: string) => server.get(`/device-instance/${id}/exists`)
|
||||
|
||||
/**
|
||||
* 修改设备信息
|
||||
* @param data 设备信息
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const update = (data: Partial<DeviceInstance>) => data.id ? server.patch(`/device-instance`, data) : server.post(`/device-instance`, data)
|
||||
|
||||
|
@ -134,27 +134,27 @@ export const update = (data: Partial<DeviceInstance>) => data.id ? server.patch(
|
|||
* 修改设备信息
|
||||
* @param id 设备id
|
||||
* @param data 设备信息
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const modify = (id: string, data: Partial<DeviceInstance>) => server.put(`/device-instance/${id}`, data)
|
||||
|
||||
/**
|
||||
* 获取配置信息
|
||||
* @param id 设备id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const getConfigMetadata = (id: string) => server.get(`/device-instance/${id}/config-metadata`)
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
* @param id 设备id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const _disconnect = (id: string) => server.post(`/device-instance/${id}/disconnect`)
|
||||
|
||||
/**
|
||||
* 查询用户列表
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryUserListNoPaging = () => server.post(`/user/_query/no-paging`, {
|
||||
paging: false,
|
||||
|
@ -164,16 +164,16 @@ export const queryUserListNoPaging = () => server.post(`/user/_query/no-paging`,
|
|||
/**
|
||||
* 保存设备关系
|
||||
* @param id 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveRelations = (id: string, data: Record<string, any>) => server.patch(`/device/instance/${id}/relations`, data)
|
||||
|
||||
/**
|
||||
* 修改标签
|
||||
* @param id 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveTags = (id: string, data: Record<string, any>) => server.patch(`/device/instance/${id}/tag`, data)
|
||||
|
||||
|
@ -181,14 +181,14 @@ export const saveTags = (id: string, data: Record<string, any>) => server.patch(
|
|||
* 删除标签
|
||||
* @param deviceId 设备id
|
||||
* @param id 标签id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const delTags = (deviceId: string, id: string) => server.remove(`/device/instance/${deviceId}/tag/${id}`)
|
||||
|
||||
/**
|
||||
* 恢复默认配置
|
||||
* @param deviceId 设备id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const configurationReset = (deviceId: string) => server.put(`/device-instance/${deviceId}/configuration/_reset`)
|
||||
|
||||
|
@ -196,16 +196,16 @@ export const configurationReset = (deviceId: string) => server.put(`/device-inst
|
|||
* 查询事件详情列表
|
||||
* @param deviceId 设备id
|
||||
* @param eventId 事件id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getEventList = (deviceId: string, eventId: string, data: Record<string, any>) => server.post(`/device-instance/${deviceId}/event/${eventId}?format=true`, data)
|
||||
|
||||
/**
|
||||
* 设置属性至设备
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const setProperty = (deviceId: string, data: Record<string, any>) => server.put(`/device-instance/${deviceId}/property`, data)
|
||||
|
||||
|
@ -213,7 +213,7 @@ export const setProperty = (deviceId: string, data: Record<string, any>) => serv
|
|||
* 获取最新属性值
|
||||
* @param deviceId 设备id
|
||||
* @param type 属性id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const getProperty = (deviceId: string, type: string) => server.get(`/device/standard/${deviceId}/property/${type}`)
|
||||
|
||||
|
@ -221,7 +221,7 @@ export const getProperty = (deviceId: string, type: string) => server.get(`/devi
|
|||
* 查询设备的物模型指标
|
||||
* @param deviceId 设备id
|
||||
* @param propertyId 属性id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryMetric = (deviceId: string, propertyId: string) => server.get(`/device-instance/${deviceId}/metric/property/${propertyId}`)
|
||||
|
||||
|
@ -229,8 +229,8 @@ export const queryMetric = (deviceId: string, propertyId: string) => server.get(
|
|||
* 保存设备的物模型指标
|
||||
* @param deviceId 设备id
|
||||
* @param propertyId 属性id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveMetric = (deviceId: string, propertyId: string, data: Record<string, any>) => server.patch(`/device-instance/${deviceId}/metric/property/${propertyId}`, data)
|
||||
|
||||
|
@ -238,24 +238,24 @@ export const saveMetric = (deviceId: string, propertyId: string, data: Record<st
|
|||
* 解绑子设备
|
||||
* @param deviceId 设备id
|
||||
* @param childrenId 子设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const unbindDevice = (deviceId: string, childrenId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/unbind/${childrenId}`, data)
|
||||
|
||||
/**
|
||||
* 批量解绑子设备
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/unbind`, data)
|
||||
|
||||
/**
|
||||
* 子设备绑定
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
|
||||
|
||||
|
@ -271,7 +271,7 @@ export const queryDeviceMapping = (deviceId: string, data?: any) => server.post(
|
|||
export const saveDeviceMapping = (deviceId: string, data: any) => server.post(`/edge/operations/${deviceId}/device-mapping-save-batch/invoke`, data)
|
||||
|
||||
/**
|
||||
*批量删除云端映射设备
|
||||
*批量删除云端映射设备
|
||||
*/
|
||||
export const deleteDeviceMapping = (deviceId: string, data:any) => server.post(`/edge/operations/${deviceId}/device-mapping-delete-by-deviceid/invoke`, data)
|
||||
|
||||
|
@ -294,64 +294,64 @@ export const addDevice = (params: any) => server.post("/device-instance", params
|
|||
/**
|
||||
* 设备接入网关状态
|
||||
* @param id 设备接入网关id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryGatewayState = (id: string) => server.get(`/gateway/device/${id}/detail`)
|
||||
|
||||
/**
|
||||
* 网络组件状态
|
||||
* @param id 网络组件id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryNetworkState = (id: string) => server.get(`/network/config/${id}`)
|
||||
|
||||
/**
|
||||
* 产品状态
|
||||
* @param id 产品id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryProductState = (id: string) => server.get(`/device/product/${id}`)
|
||||
|
||||
/**
|
||||
* 产品配置
|
||||
* @param id 产品id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryProductConfig = (id: string) => server.get(`/device/product/${id}/config-metadata`)
|
||||
|
||||
/**
|
||||
* 设备配置
|
||||
* @param id 设备id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryDeviceConfig = (id: string) => server.get(`/device-instance/${id}/config-metadata`)
|
||||
|
||||
/**
|
||||
* 查询协议
|
||||
* @param type
|
||||
* @param transport
|
||||
* @returns
|
||||
* @param type
|
||||
* @param transport
|
||||
* @returns
|
||||
*/
|
||||
export const queryProtocolDetail = (type: string, transport: string) => server.get(`/protocol/${type}/transport/${transport}`)
|
||||
|
||||
/**
|
||||
* 网络组件启用
|
||||
* @param id 网络组件ID
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const startNetwork = (id: string) => server.post(`/network/config/${id}/_start`)
|
||||
|
||||
/**
|
||||
* 启用网关
|
||||
* @param id 网关id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const startGateway = (id: string) => server.post(`/gateway/device/${id}/_startup`)
|
||||
|
||||
/**
|
||||
* 网关详情
|
||||
* @param id 网关id
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const getGatewayDetail = (id: string) => server.get(`/gateway/device/${id}`)
|
||||
|
||||
|
@ -366,231 +366,231 @@ export const getUnit = () => server.get<UnitType[]>(`/protocol/units`)
|
|||
* 执行功能
|
||||
* @param deviceId 设备id
|
||||
* @param functionId 功能id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const executeFunctions = (deviceId: string, functionId: string, data: any) => server.post(`/device/invoked/${deviceId}/function/${functionId}`, data)
|
||||
|
||||
/**
|
||||
* 读取属性
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const readProperties = (deviceId: string, data: any) => server.post(`/device/instance/${deviceId}/properties/_read`, data)
|
||||
|
||||
/**
|
||||
* 设置属性
|
||||
* @param deviceId 设备id
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const settingProperties = (deviceId: string, data: any) => server.put(`/device/instance/${deviceId}/property`, data)
|
||||
|
||||
/**
|
||||
* 设备功能-执行
|
||||
* @param id 设备id
|
||||
* @param action
|
||||
* @param data
|
||||
* @returns
|
||||
* @param action
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const execute = (id: string, action: string, data: any) => server.post(`/device/invoked/${id}/function/${action}`, data)
|
||||
|
||||
/**
|
||||
* 查询通道列表不分页
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryChannelNoPaging = (data: any) => server.post(`data-collect/channel/_query/no-paging`, data)
|
||||
|
||||
/**
|
||||
* 查询采集器列表不分页
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryCollectorNoPaging = (data: any) => server.post(`/data-collect/collector/_query/no-paging`, data)
|
||||
|
||||
/**
|
||||
* 查询点位列表不分页
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryPointNoPaging = (data: any) => server.post(`/data-collect/point/_query/no-paging`, data)
|
||||
|
||||
/**
|
||||
* 查询映射列表
|
||||
* @param thingType
|
||||
* @param thingId
|
||||
* @param params
|
||||
* @returns
|
||||
* @param thingType
|
||||
* @param thingId
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export const queryMapping = (thingType: string, thingId: any, params?: any) => server.get(`/things/collector/${thingType}/${thingId}/_query`, params)
|
||||
|
||||
/**
|
||||
* 删除映射
|
||||
* @param thingType
|
||||
* @param thingId
|
||||
* @param data
|
||||
* @returns
|
||||
* @param thingType
|
||||
* @param thingId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const removeMapping = (thingType: string, thingId: any, data?: any) => server.post(`/things/collector/${thingType}/${thingId}/_delete`, data)
|
||||
|
||||
/**
|
||||
* 映射树
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const treeMapping = (data?: any) => server.post(`/data-collect/channel/_all/tree`, data)
|
||||
|
||||
/**
|
||||
* 保存映射
|
||||
* @param thingId
|
||||
* @param provider
|
||||
* @param data
|
||||
* @returns
|
||||
* @param thingId
|
||||
* @param provider
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveMapping = (thingId: any, provider: string, data?: any) => server.patch(`/things/collector/device/${thingId}/${provider}`, data)
|
||||
|
||||
/**
|
||||
* 查询边缘网关通道
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const edgeChannel = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/data-collector-channel-list/invoke`, data)
|
||||
|
||||
/**
|
||||
* 查询边缘网关采集器
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const edgeCollector = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/data-collector-list/invoke`, data)
|
||||
|
||||
/**
|
||||
* 查询边缘网关点位
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const edgePoint = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/data-collector-point-list/invoke`, data)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-collector-list/invoke`, data)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const removeEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-collector-delete/invoke`, data)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const treeEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/data-collector-channel-tree/invoke`, data)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-collector-save/invoke`, data)
|
||||
|
||||
/**
|
||||
* 查询属性详情
|
||||
* @param deviceId
|
||||
* @param params
|
||||
* @returns
|
||||
* @param deviceId
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertyData = (deviceId: string, params: Record<string, unknown>) => server.get(`/device-instance/${deviceId}/properties/_query`, params)
|
||||
|
||||
/**
|
||||
* 聚合查询设备属性
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertiesInfo = (deviceId: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/agg/_query`, data)
|
||||
|
||||
/**
|
||||
* 聚合查询设备属性
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertiesList = (deviceId: string, property: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/property/${property}/_query/no-paging`, data)
|
||||
|
||||
/**
|
||||
* 获取指定协议
|
||||
* @param id
|
||||
* @param transport
|
||||
* @returns
|
||||
* @param id
|
||||
* @param transport
|
||||
* @returns
|
||||
*/
|
||||
export const getProtocal = (id: string, transport: string) => server.get(`/protocol/${id}/transport/${transport}`)
|
||||
|
||||
/**
|
||||
* 获取产品解析规则
|
||||
* @param productId
|
||||
* @returns
|
||||
* @param productId
|
||||
* @returns
|
||||
*/
|
||||
export const productCode = (productId: string) => server.get(`/device/transparent-codec/${productId}`)
|
||||
/**
|
||||
* 保存产品解析规则
|
||||
* @param productId
|
||||
* @returns
|
||||
* @param productId
|
||||
* @returns
|
||||
*/
|
||||
export const saveProductCode = (productId: string, data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}`, data)
|
||||
/**
|
||||
* 获取设备解析规则
|
||||
* @param productId
|
||||
* @param deviceId
|
||||
* @returns
|
||||
* @param productId
|
||||
* @param deviceId
|
||||
* @returns
|
||||
*/
|
||||
export const deviceCode = (productId: string, deviceId: string) => server.get(`device/transparent-codec/${productId}/${deviceId}`)
|
||||
/**
|
||||
* 保存设备解析规则
|
||||
* @param productId
|
||||
* @param productId
|
||||
* 查询设备日志
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveDeviceCode = (productId: string, deviceId: string, data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}/${deviceId}`, data)
|
||||
/**
|
||||
* 编码测试
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const testCode = (data: Record<string, unknown>) => server.post(`/device/transparent-codec/decode-test`, data)
|
||||
/**
|
||||
* 删除设备解析规则
|
||||
* @param productId
|
||||
* @param deviceId
|
||||
* @returns
|
||||
* @param productId
|
||||
* @param deviceId
|
||||
* @returns
|
||||
*/
|
||||
export const delDeviceCode = (productId: string, deviceId: string) => server.remove(`/device/transparent-codec/${productId}/${deviceId}`)
|
||||
/**
|
||||
* 删除产品解析规则
|
||||
* @param productId
|
||||
* @returns
|
||||
* @param productId
|
||||
* @returns
|
||||
*/
|
||||
export const delProductCode = (productId: string) => server.remove(`/device/transparent-codec/${productId}`)
|
||||
export const queryLog = (deviceId: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/logs`, data)
|
||||
|
||||
/**
|
||||
* 查询设备日志类型
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryLogsType = () => server.get(`/dictionary/device-log-type/items`)
|
||||
|
||||
|
@ -626,13 +626,60 @@ export const queryProductCodeTips = (productId: string) => server.get(`/device/t
|
|||
/**
|
||||
* 获取设备物模型规则TS
|
||||
* @param deviceId 设备ID
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryTypescript = (deviceId:string) => server.get(`/device/${deviceId}/virtual-property.d.ts`)
|
||||
export const queryTypescript = (deviceId:string) => server.get(`/device/${deviceId}/virtual-property.d.ts`)
|
||||
|
||||
/**
|
||||
* 获取产品物模型规则TS
|
||||
* @param productId 产品ID
|
||||
* @returns
|
||||
*/
|
||||
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 queryProductTs = (productId:string) => server.get(`/product/${productId}/virtual-property.d.ts`)
|
||||
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,11 +1,12 @@
|
|||
import { OperatorItem } from '@/components/FRuleEditor/Operator/typings'
|
||||
import server from '@/utils/request'
|
||||
import { DeviceMetadata, ProductItem, DepartmentItem, MetadataType } from '@/views/device/Product/typings'
|
||||
import {BASE_API_PATH} from "@/utils/variable";
|
||||
|
||||
/**
|
||||
* 根据条件查询产品(不带翻页)
|
||||
* @param data 查询条件
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const queryNoPagingPost = (data: any) => server.post(`/device-product/_query/no-paging?paging=false`, data)
|
||||
|
||||
|
@ -14,7 +15,7 @@ export const queryNoPagingPost = (data: any) => server.post(`/device-product/_qu
|
|||
* @param direction from|to
|
||||
* @param type 物模型类型
|
||||
* @param data 物模型数据
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const convertMetadata = (direction: 'from' | 'to', type: string, data: any) => server.post<DeviceMetadata>(`/device/product/metadata/convert-${direction}/${type}`, data)
|
||||
|
||||
|
@ -22,20 +23,20 @@ export const convertMetadata = (direction: 'from' | 'to', type: string, data: an
|
|||
* 修改产品
|
||||
* @param id 产品ID
|
||||
* @param data 产品数据
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const modify = (id: string, data: any) => server.put(`/device-product/${id}`, data)
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export const getCodecs = () => server.get<{id: string, name: string}>('/device/product/metadata/codecs')
|
||||
|
||||
/**
|
||||
* 根据产品ID获取产品详情
|
||||
* @param id 产品ID
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const detail = (id: string) => server.get<ProductItem>(`/device-product/${id}`)
|
||||
|
||||
|
@ -80,31 +81,31 @@ export const category = (data: any) => server.get('/device/category/_tree?paging
|
|||
/**
|
||||
* 启用产品
|
||||
* @param productId 产品ID
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const _deploy = (productId: string) => server.post(`/device-product/${productId}/deploy`)
|
||||
|
||||
/**
|
||||
* 禁用产品
|
||||
* @param productId 产品ID
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const _undeploy = (productId: string) => server.post(`/device-product/${productId}/undeploy`)
|
||||
|
||||
/**
|
||||
* 新增产品
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const addProduct = (data:any) => server.post('/device-product',data)
|
||||
|
||||
/**
|
||||
* 修改产品
|
||||
* @param id 产品ID
|
||||
* @param data
|
||||
* @returns
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const editProduct = (data: any) => server.put(`/device-product/${data.id}`, data)
|
||||
|
||||
|
@ -122,7 +123,7 @@ export const deleteProduct = (id: string) => server.remove(`/device-product/${id
|
|||
/**
|
||||
* 保存产品
|
||||
* @param data 产品信息
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const saveProductMetadata = (data: Record<string, unknown>) => server.patch('/device-product', data)
|
||||
|
||||
|
@ -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 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 queryPaginateNot = (data: any) => server.post('/device/aliyun/bridge/_query/no-paging', 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 queryPaginateNot = (data:any) => server.post('/dueros/product/_query/no-paging',data)
|
||||
|
||||
/**
|
||||
* 查询产品列表
|
||||
* @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 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)
|
||||
|
||||
/**
|
||||
* 查询关联的场景执行动作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);
|
||||
|
||||
/**
|
||||
* 设备产品专用查询
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const queryByDevice = (data:any) => server.post(`/alarm/record/device/_query`,data)
|
||||
|
||||
/**
|
||||
* 告警处理
|
||||
*/
|
||||
|
@ -49,4 +56,14 @@ 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 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',
|
||||
'warning': '255, 144, 0',
|
||||
'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) => {
|
||||
const _color = color[code] || color.default
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue';
|
||||
import { BatchActionsType } from './types';
|
||||
import { defineExpose } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
actions: {
|
||||
|
@ -90,14 +91,17 @@ const reload = () => {
|
|||
|
||||
const onPopConfirm = (e: any, fun: any) => {
|
||||
if (fun) {
|
||||
fun(e);
|
||||
onPopCancel();
|
||||
return fun(e);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopCancel = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
defineExpose({
|
||||
reload
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
background: getBackgroundColor(statusNames[status]),
|
||||
}"
|
||||
></div>
|
||||
<div style="display: flex">
|
||||
<div class="card-content-body">
|
||||
<!-- 图片 -->
|
||||
<div class="card-item-avatar">
|
||||
<slot name="img"> </slot>
|
||||
|
@ -57,8 +57,8 @@
|
|||
:text="statusText"
|
||||
:statusNames="statusNames"
|
||||
></BadgeStatus>
|
||||
<CustomBadgeStatus
|
||||
v-else
|
||||
<CustomBadgeStatus
|
||||
v-else
|
||||
:status="status"
|
||||
:text="statusText"
|
||||
:statusNames="statusNames">
|
||||
|
@ -176,7 +176,7 @@ const handleClick = () => {
|
|||
.card {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
|
||||
|
||||
.checked-icon {
|
||||
position: absolute;
|
||||
right: -22px;
|
||||
|
@ -249,6 +249,12 @@ const handleClick = () => {
|
|||
padding: 30px 12px 30px 30px;
|
||||
overflow: hidden;
|
||||
|
||||
.card-content-body {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.card-item-avatar {
|
||||
margin-right: 16px;
|
||||
display: flex;
|
||||
|
@ -317,6 +323,7 @@ const handleClick = () => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.card-content-top-line {
|
||||
&::before {
|
||||
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="header">
|
||||
<j-tabs v-model:activeKey="headerType">
|
||||
<template #rightExtra>
|
||||
<a v-if="virtualRule?.script && isBeginning" @click="beginAction">
|
||||
开始运行
|
||||
</a>
|
||||
</template>
|
||||
<j-tab-pane key="property">
|
||||
<template #tab>
|
||||
<span class="title">
|
||||
|
@ -18,44 +23,6 @@
|
|||
</template>
|
||||
</j-tab-pane>
|
||||
</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 class="description">
|
||||
{{
|
||||
|
@ -71,7 +38,7 @@
|
|||
:pagination="false"
|
||||
bordered
|
||||
size="small"
|
||||
:scroll="{ y: 200 }"
|
||||
:scroll="{ y: 180 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'id'">
|
||||
|
@ -79,8 +46,13 @@
|
|||
showSearch
|
||||
:options="options"
|
||||
v-model:value="record.id"
|
||||
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||
size="small"
|
||||
style="width: 100%; z-index: 1400 !important"
|
||||
style="width: 100%;"
|
||||
:virtual="true"
|
||||
:dropdownStyle="{
|
||||
zIndex: 1072
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'current'">
|
||||
|
@ -131,7 +103,12 @@
|
|||
:options="tagOptions"
|
||||
v-model:value="record.id"
|
||||
size="small"
|
||||
style="width: 100%; z-index: 1400 !important"
|
||||
style="width: 100%;"
|
||||
:virtual="true"
|
||||
:getPopupContainer="(node) => tableWrapperRef || node"
|
||||
:dropdownStyle="{
|
||||
zIndex: 1072
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'current'">
|
||||
|
@ -175,13 +152,13 @@
|
|||
class="action"
|
||||
@click="runScriptAgain"
|
||||
>
|
||||
<a style="margin-left: 75px">发送数据</a>
|
||||
<a>发送数据</a>
|
||||
</div>
|
||||
<div v-if="virtualRule?.script">
|
||||
<a v-if="isBeginning" @click="beginAction">
|
||||
开始运行
|
||||
</a>
|
||||
<a v-else @click="stopAction"> 停止运行 </a>
|
||||
<div v-if="virtualRule?.script && !isBeginning">
|
||||
<!-- <a v-if="isBeginning" @click="beginAction">-->
|
||||
<!-- 开始运行-->
|
||||
<!-- </a>-->
|
||||
<a v-if="!isBeginning" @click="stopAction"> 停止运行 </a>
|
||||
</div>
|
||||
<div>
|
||||
<a @click="clearAction"> 清空 </a>
|
||||
|
@ -219,16 +196,17 @@
|
|||
</template>
|
||||
<script setup lang="ts" name="Debug">
|
||||
import { PropType, Ref } from 'vue';
|
||||
import { useProductStore } from '@/store/product';
|
||||
import { useRuleEditorStore } from '@/store/ruleEditor';
|
||||
import moment from 'moment';
|
||||
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 {message} from "ant-design-vue";
|
||||
|
||||
const props = defineProps({
|
||||
virtualRule: Object as PropType<Record<any, any>>,
|
||||
id: String,
|
||||
propertiesOptions: Array,
|
||||
});
|
||||
const emits = defineEmits(['success']);
|
||||
|
||||
|
@ -238,21 +216,13 @@ type propertyType = {
|
|||
id?: string;
|
||||
current?: string;
|
||||
last?: string;
|
||||
|
||||
type?: string
|
||||
};
|
||||
const property = ref<propertyType[]>([]);
|
||||
const tag = ref<Array<any>>([]);
|
||||
const headerOptions = [
|
||||
{
|
||||
key: 'property',
|
||||
label: '属性赋值',
|
||||
title: '属性赋值',
|
||||
},
|
||||
{
|
||||
key: 'tag',
|
||||
label: '标签赋值',
|
||||
title: '标签赋值',
|
||||
},
|
||||
];
|
||||
const tableWrapperRef = useTableWrapper()
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '属性名称',
|
||||
|
@ -310,21 +280,20 @@ const deleteTagItem = (index: number) => {
|
|||
const ws = ref();
|
||||
|
||||
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 productStore = useProductStore();
|
||||
const ruleEditorStore = useRuleEditorStore();
|
||||
|
||||
const time = ref<number>(0);
|
||||
const timer = ref<any>(null);
|
||||
|
||||
const runScript = () => {
|
||||
const metadata = productStore.current.metadata || '{}';
|
||||
const propertiesList = JSON.parse(metadata).properties || [];
|
||||
const propertiesList = medataSource?.value || []
|
||||
const _properties = property.value.map((item: any) => {
|
||||
const _item = propertiesList.find((i: any) => i.id === item.id);
|
||||
return { ...item, type: _item?.valueType?.type };
|
||||
});
|
||||
console.log('runScript', _properties, propertiesList)
|
||||
let _tags = {};
|
||||
tag.value.forEach((item) => {
|
||||
_tags[item.id] = item.current;
|
||||
|
@ -334,6 +303,11 @@ const runScript = () => {
|
|||
}
|
||||
if (!props.virtualRule?.script) {
|
||||
isBeginning.value = true;
|
||||
message.config({
|
||||
getContainer() {
|
||||
return tableWrapperRef.value || document.body
|
||||
}
|
||||
})
|
||||
onlyMessage('请编辑规则', 'warning');
|
||||
return;
|
||||
}
|
||||
|
@ -385,12 +359,12 @@ const runScriptAgain = async () => {
|
|||
if (wsAgain.value) {
|
||||
wsAgain.value.unsubscribe?.();
|
||||
}
|
||||
const metadata = productStore.current.metadata || '{}';
|
||||
const propertiesList = JSON.parse(metadata).properties || [];
|
||||
const propertiesList = medataSource?.value || []
|
||||
const _properties = property.value.map((item: any) => {
|
||||
const _item = propertiesList.find((i: any) => i.id === item.id);
|
||||
return { ...item, type: _item?.valueType?.type };
|
||||
});
|
||||
console.log('runScriptAgain', _properties, propertiesList)
|
||||
|
||||
wsAgain.value = getWebSocket(
|
||||
`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
||||
|
@ -414,6 +388,14 @@ const getTime = () => {
|
|||
};
|
||||
|
||||
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;
|
||||
runScript();
|
||||
getTime();
|
||||
|
@ -435,13 +417,18 @@ onUnmounted(() => {
|
|||
ws.value.unsubscribe?.();
|
||||
}
|
||||
clearAction();
|
||||
|
||||
message.config({
|
||||
getContainer() {
|
||||
return document.body
|
||||
},
|
||||
})
|
||||
window.clearInterval(timer.value);
|
||||
timer.value = null;
|
||||
});
|
||||
|
||||
const options = computed(() => {
|
||||
return (medataSource.value || [])
|
||||
.filter((p) => p.id !== props.id)
|
||||
return (props.propertiesOptions || [])
|
||||
.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
|
@ -455,6 +442,9 @@ const tagOptions = computed(() => {
|
|||
}));
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
beginAction
|
||||
})
|
||||
// const getProperty = () => {
|
||||
// // const metadata = productStore.current.metadata || '{}';
|
||||
// // const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
|
||||
|
@ -465,10 +455,11 @@ const tagOptions = computed(() => {
|
|||
</script>
|
||||
<style lang="less" scoped>
|
||||
.debug-container {
|
||||
// display: flex;
|
||||
display: flex;
|
||||
// width: 100%;
|
||||
// height: 340px;
|
||||
// margin-top: 20px;
|
||||
gap: 12px;
|
||||
|
||||
.top {
|
||||
// min-width: 0;
|
||||
|
@ -476,35 +467,36 @@ const tagOptions = computed(() => {
|
|||
// overflow-y: auto;
|
||||
height: 350px;
|
||||
border: 1px solid lightgray;
|
||||
margin-bottom: 10px;
|
||||
//margin-bottom: 10px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
//display: flex;
|
||||
//align-items: center;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
height: 46px;
|
||||
border-bottom: 1px solid lightgray;
|
||||
padding: 0 12px;
|
||||
//justify-content: space-around;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
//width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
|
||||
.title {
|
||||
margin: 0 10px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 10px;
|
||||
color: lightgray;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
//div {
|
||||
// display: flex;
|
||||
// //width: 100%;
|
||||
// align-items: center;
|
||||
// justify-content: flex-start;
|
||||
// height: 100%;
|
||||
//
|
||||
// .title {
|
||||
// margin: 0 10px;
|
||||
// font-weight: 600;
|
||||
// font-size: 16px;
|
||||
// }
|
||||
//
|
||||
// .description {
|
||||
// margin-left: 10px;
|
||||
// color: lightgray;
|
||||
// font-size: 12px;
|
||||
// }
|
||||
//}
|
||||
|
||||
.action {
|
||||
width: 150px;
|
||||
|
@ -512,6 +504,11 @@ const tagOptions = computed(() => {
|
|||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.top-bottom {
|
||||
padding: 10px;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
<div
|
||||
:class="
|
||||
node.children?.length > 0
|
||||
!node.isLeaf
|
||||
? 'parent'
|
||||
: 'add'
|
||||
"
|
||||
|
@ -35,6 +35,7 @@
|
|||
}"
|
||||
placement="right"
|
||||
title="请选择使用值"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
>
|
||||
<template #content>
|
||||
<j-space direction="vertical">
|
||||
|
@ -73,6 +74,7 @@
|
|||
}"
|
||||
placement="right"
|
||||
title="请选择使用值"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
>
|
||||
<template #content>
|
||||
<j-space direction="vertical">
|
||||
|
@ -119,7 +121,7 @@ import { treeFilter } from '@/utils/tree';
|
|||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
||||
import { getOperator } from '@/api/device/product';
|
||||
import { inject } from 'vue';
|
||||
import { Descriptions } from 'ant-design-vue';
|
||||
import {useTableWrapper, useTableFullScreen} from "@/components/Metadata/Table/context";
|
||||
import Markdown from '@/components/Markdown'
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -136,6 +138,8 @@ const item = ref<Partial<OperatorItem>>();
|
|||
const data = ref<OperatorItem[]>([]);
|
||||
const dataRef = ref<OperatorItem[]>([]);
|
||||
const tagsMetadata: any = inject('_tagsDataSource');
|
||||
const tableWrapperRef = useTableWrapper()
|
||||
const fullScreen = useTableFullScreen()
|
||||
const search = (value: string) => {
|
||||
if (value) {
|
||||
const nodes = treeFilter(
|
||||
|
@ -176,30 +180,39 @@ const getData = async (id?: string) => {
|
|||
name: '属性',
|
||||
description: '',
|
||||
code: '',
|
||||
isLeaf: false,
|
||||
children: _properties
|
||||
.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,
|
||||
name: p.name,
|
||||
isLeaf: true,
|
||||
description: `### ${p.name}
|
||||
\n 数据类型: ${p.valueType?.type}
|
||||
\n 是否只读: ${p.expands?.readOnly || 'false'}
|
||||
\n 可写数值范围: `,
|
||||
\n 标识: ${p.id}
|
||||
\n 数据类型: ${p.valueType?.type}
|
||||
\n 是否只读: ${readOnly}
|
||||
\n 可写数值范围: `,
|
||||
type: 'property',
|
||||
})),
|
||||
}
|
||||
}),
|
||||
};
|
||||
const tags = {
|
||||
id: 'tags',
|
||||
name: '标签',
|
||||
Description: '',
|
||||
code: '',
|
||||
isLeaf: false,
|
||||
children: tagsMetadata.value.map((i: any) => ({
|
||||
id: i.id,
|
||||
name: i.name,
|
||||
isLeaf: true,
|
||||
description: `### ${i.name}
|
||||
\n 数据类型: ${i.valueType?.type}
|
||||
\n 是否只读: ${i.expands?.readOnly || 'false'}
|
||||
\n 可写数值范围: `,
|
||||
\n 标识: ${i.id}
|
||||
\n 数据类型: ${i.valueType?.type}
|
||||
\n 可写数值范围: `,
|
||||
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(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
|
@ -236,17 +257,21 @@ watch(
|
|||
.operator-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
|
||||
.left,
|
||||
.right {
|
||||
width: 50%;
|
||||
height: 350px;
|
||||
//width: 50%;
|
||||
//height: 350px;
|
||||
height: calc(50% - 7px);
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.left {
|
||||
padding: 10px;
|
||||
margin-right: 10px;
|
||||
//margin-right: 10px;
|
||||
.tree {
|
||||
height: 300px;
|
||||
//overflow-y: auto;
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
<template>
|
||||
<j-modal
|
||||
:zIndex="1030"
|
||||
:zIndex="1072"
|
||||
:mask-closable="false"
|
||||
visible
|
||||
width="70vw"
|
||||
width="1300px"
|
||||
title="编辑规则"
|
||||
:getContainer="(node) => fullRef || node"
|
||||
@cancel="handleCancel"
|
||||
:destroyOnClose="true"
|
||||
:dialogStyle="{
|
||||
zIndex: 1072
|
||||
}"
|
||||
:getContainer="(node) => tableWrapperRef || node"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="header" v-if="virtualRule?.windowType && virtualRule?.windowType !== 'undefined'">
|
||||
<div class="header-item">
|
||||
|
@ -24,27 +27,27 @@
|
|||
<div class="box">
|
||||
<div class="left">
|
||||
<div>
|
||||
<Operator :id="id" :propertiesOptions="propertiesOptions" @add-operator-value="addOperatorValue" />
|
||||
<Editor
|
||||
ref="editor"
|
||||
mode="advance"
|
||||
key="advance"
|
||||
v-model:value="_value"
|
||||
:tips="tips"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-top: 10px;">
|
||||
<Editor
|
||||
ref="editor"
|
||||
mode="advance"
|
||||
key="advance"
|
||||
v-model:value="_value"
|
||||
:tips="tips"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<Debug
|
||||
<Debug
|
||||
:virtualRule="{
|
||||
...virtualRule,
|
||||
script: _value,
|
||||
}"
|
||||
:propertiesOptions="propertiesOptions"
|
||||
:id="id"
|
||||
@success="onSuccess"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<Operator :id="id" :propertiesOptions="propertiesOptions" @add-operator-value="addOperatorValue" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
|
@ -59,9 +62,8 @@
|
|||
import Editor from './Editor/index.vue';
|
||||
import Debug from './Debug/index.vue';
|
||||
import Operator from './Operator/index.vue';
|
||||
import { FULL_CODE } from 'jetlinks-ui-components/es/DataTable'
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
||||
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||
interface Emits {
|
||||
(e: 'save', data: string | undefined): void;
|
||||
(e: 'close'): void;
|
||||
|
@ -77,8 +79,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const _value = ref<string | undefined>(props.value);
|
||||
const _disabled = ref<boolean>(true);
|
||||
const fullRef = inject(FULL_CODE);
|
||||
const tableWrapperRef = useTableWrapper()
|
||||
const tips = ref<any[]>([])
|
||||
const handleCancel = () => {
|
||||
emit('close');
|
||||
|
@ -110,7 +111,7 @@ const getAllCrud = () => {
|
|||
console.log(item)
|
||||
const config = item
|
||||
tips.value.push({
|
||||
label: `${config.name}$recent实时值`,
|
||||
label: `${config.name}$recent实时值`,
|
||||
insertText:`$recent ("${config.id}")`,
|
||||
kind: 18,
|
||||
})
|
||||
|
@ -151,14 +152,14 @@ getAllCrud()
|
|||
width: 100%;
|
||||
|
||||
.left {
|
||||
width: 60%;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 40%;
|
||||
flex: 1 1 0;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid lightgray;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="page-container">
|
||||
<j-input allowClear v-model:value="inputPoint">
|
||||
<template #addonAfter>
|
||||
<environment-outlined @click="modalVis = true" />
|
||||
<environment-outlined @click="showMap" />
|
||||
</template>
|
||||
</j-input>
|
||||
<j-modal
|
||||
|
@ -101,6 +101,11 @@ const clickMap = (e: any) => {
|
|||
position.value = [e.lnglat.lng, e.lnglat.lat];
|
||||
};
|
||||
|
||||
const showMap = () => {
|
||||
mapPoint.value = props.point
|
||||
modalVis.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择搜索结果
|
||||
* @param e
|
||||
|
@ -110,6 +115,8 @@ const selectPoi = (e: any) => {
|
|||
mapPoint.value = selectPoint.join(',');
|
||||
map.setCenter(selectPoint);
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<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,17 +1,33 @@
|
|||
<template>
|
||||
<j-form-item name="type" label="读写类型" :rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择读写类型'
|
||||
}
|
||||
]">
|
||||
<j-select
|
||||
v-model:value="myValue"
|
||||
mode="multiple"
|
||||
:options="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择读写类型"
|
||||
@change="onChange"
|
||||
<j-form-item
|
||||
name="type"
|
||||
label="读写类型"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择读写类型'
|
||||
}
|
||||
]"
|
||||
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"
|
||||
:multiple="true"
|
||||
:options="options"
|
||||
:disabled="disabled"
|
||||
@change="onChange"
|
||||
/>
|
||||
</j-form-item>
|
||||
</template>
|
||||
|
@ -19,6 +35,7 @@
|
|||
<script setup lang="ts" name="ReadType">
|
||||
|
||||
import type {PropType} from "vue";
|
||||
import {useTableWrapper} from "@/components/Metadata/Table/context";
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:value', data: Array<string>): void
|
||||
|
@ -43,6 +60,7 @@ const props = defineProps({
|
|||
const emit = defineEmits<Emit>()
|
||||
|
||||
const myValue = ref<Array<string>>([])
|
||||
const tableWrapperRef = useTableWrapper()
|
||||
|
||||
const onChange = (keys: Array<string>) =>{
|
||||
myValue.value = keys
|
||||
|
@ -58,4 +76,4 @@ watch(() => props.value, () => {
|
|||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -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,123 +1,204 @@
|
|||
<template>
|
||||
<template v-if="isPermission">
|
||||
<template v-if="popConfirm">
|
||||
<j-popconfirm :disabled="!isPermission || props.disabled" :overlayStyle='{width: "220px", zIndex: 1075 }' v-bind="popConfirm">
|
||||
<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">
|
||||
<template v-if="isPermission">
|
||||
<template v-if="popConfirm">
|
||||
<!-- <j-popconfirm
|
||||
:disabled="!isPermission || props.disabled"
|
||||
:overlayStyle="{ width: '220px', zIndex: 1075 }"
|
||||
v-bind="popConfirm"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
<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>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</template>
|
||||
<template v-else-if="tooltip">
|
||||
<j-tooltip v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<j-button
|
||||
v-else
|
||||
v-bind="props"
|
||||
:disabled="_isPermission"
|
||||
:style="props.style"
|
||||
>
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot v-if="noButton"></slot>
|
||||
<j-button
|
||||
v-else
|
||||
v-bind="props"
|
||||
:disabled="_isPermission"
|
||||
:style="props.style"
|
||||
>
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</template>
|
||||
</template>
|
||||
<j-tooltip v-else title="暂无权限,请联系管理员" :placement="placement">
|
||||
<slot v-if="noButton"></slot>
|
||||
<j-button
|
||||
v-else
|
||||
v-bind="props"
|
||||
:disabled="_isPermission"
|
||||
:style="props.style"
|
||||
>
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
<j-button v-else v-bind="props" :disabled="_isPermission" >
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</j-popconfirm>
|
||||
</template>
|
||||
<template v-else-if="tooltip">
|
||||
<j-tooltip v-bind="tooltip">
|
||||
<slot v-if="noButton"></slot>
|
||||
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot v-if="noButton"></slot>
|
||||
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</template>
|
||||
</template>
|
||||
<j-tooltip v-else title="暂无权限,请联系管理员" :placement="placement">
|
||||
<slot v-if="noButton"></slot>
|
||||
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
|
||||
<slot></slot>
|
||||
<template #icon>
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<script setup lang="ts" name="PermissionButton">
|
||||
import { CSSProperties, PropType } from 'vue'
|
||||
import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es'
|
||||
import { buttonProps } from 'ant-design-vue/es/button/button'
|
||||
import { CSSProperties, PropType } from 'vue';
|
||||
import { TooltipProps, PopconfirmProps } from 'ant-design-vue/es';
|
||||
import { buttonProps } from 'ant-design-vue/es/button/button';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
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({
|
||||
noButton: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
tooltip: {
|
||||
type: Object as PropType<TooltipProps>,
|
||||
},
|
||||
popConfirm: {
|
||||
type: Object as PropType<PopconfirmProps>,
|
||||
},
|
||||
hasPermission: {
|
||||
type: [String , Array, Boolean],
|
||||
},
|
||||
style: {
|
||||
type: Object as PropType<CSSProperties>
|
||||
},
|
||||
placement:{
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
...omit(buttonProps(), 'icon')
|
||||
})
|
||||
|
||||
// const { tooltip, popConfirm, hasPermission, noButton, ..._buttonProps } = props;
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
noButton: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
tooltip: {
|
||||
type: Object as PropType<TooltipProps>,
|
||||
},
|
||||
popConfirm: {
|
||||
type: Object as PropType<PopconfirmProps>,
|
||||
},
|
||||
hasPermission: {
|
||||
type: [String, Array, Boolean],
|
||||
},
|
||||
style: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'top',
|
||||
},
|
||||
...omit(buttonProps(), 'icon'),
|
||||
});
|
||||
// const modalVisible = ref(false);
|
||||
// const confirmLoading = ref(false);
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
const isPermission = computed(() => {
|
||||
if (!props.hasPermission || props.hasPermission === true) {
|
||||
return true
|
||||
}
|
||||
return permissionStore.hasPermission(props.hasPermission)
|
||||
})
|
||||
if (!props.hasPermission || props.hasPermission === true) {
|
||||
return true;
|
||||
}
|
||||
return permissionStore.hasPermission(props.hasPermission);
|
||||
});
|
||||
const _isPermission = computed(() =>
|
||||
'hasPermission' in props && isPermission.value
|
||||
? 'disabled' in props
|
||||
? props.disabled as boolean
|
||||
: false
|
||||
: true
|
||||
)
|
||||
'hasPermission' in props && isPermission.value
|
||||
? 'disabled' in props
|
||||
? (props.disabled as boolean)
|
||||
: false
|
||||
: true,
|
||||
);
|
||||
|
||||
// const conform = (e: MouseEvent) => {
|
||||
// props.popConfirm?.onConfirm?.(e)
|
||||
// }
|
||||
// const modalConfirm = async (e: MouseEvent) => {
|
||||
// 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>
|
||||
<style scoped lang="less">
|
||||
|
||||
// .modalContent {
|
||||
// text-align: center;
|
||||
// }
|
||||
// .control {
|
||||
// margin-top: 20px;
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// }
|
||||
</style>
|
||||
|
|
|
@ -75,24 +75,20 @@
|
|||
>
|
||||
<j-space>
|
||||
<span>{{ item.name }}</span>
|
||||
<j-popconfirm
|
||||
title="确认删除?"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="(e: any) => {
|
||||
<PermissionButton
|
||||
type="text"
|
||||
:popConfirm="{
|
||||
title: '确认删除?',
|
||||
onConfirm: (e: any) => {
|
||||
e?.stopPropagation();
|
||||
deleteHistory(item.key);
|
||||
}
|
||||
"
|
||||
}"
|
||||
>
|
||||
<AIcon
|
||||
type="DeleteOutlined"
|
||||
@click="
|
||||
(e:any) =>
|
||||
e?.stopPropagation()
|
||||
"
|
||||
/>
|
||||
</j-popconfirm>
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
</j-menu-item>
|
||||
</j-menu>
|
||||
|
@ -316,12 +312,15 @@ const getHistory = async () => {
|
|||
* 删除历史分屏
|
||||
* @param id
|
||||
*/
|
||||
const deleteHistory = async (id: string) => {
|
||||
const res = await deleteSearchHistory(DEFAULT_SAVE_CODE, id);
|
||||
if (res.success) {
|
||||
getHistory();
|
||||
visible.value = false;
|
||||
}
|
||||
const deleteHistory = (id: string) => {
|
||||
const response = deleteSearchHistory(DEFAULT_SAVE_CODE, id);
|
||||
response.then((res)=>{
|
||||
if(res.success){
|
||||
getHistory();
|
||||
visible.value = false;
|
||||
}
|
||||
})
|
||||
return response
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -458,16 +457,16 @@ defineExpose({
|
|||
|
||||
<style lang="less" scoped>
|
||||
@import './index.less';
|
||||
:deep(.live-player-stretch-btn){
|
||||
display: none;
|
||||
:deep(.live-player-stretch-btn) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.vjs-icon-spinner){
|
||||
display: none;
|
||||
:deep(.vjs-icon-spinner) {
|
||||
display: none;
|
||||
}
|
||||
.refreshBtn{
|
||||
opacity: 0;
|
||||
.refreshBtn {
|
||||
opacity: 0;
|
||||
}
|
||||
.refreshBtn:hover{
|
||||
.refreshBtn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.live-player-warp {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
.live-player-content {
|
||||
display: flex;
|
||||
|
@ -25,6 +26,7 @@
|
|||
position: relative;
|
||||
display: grid;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
|
||||
&.screen-1 {
|
||||
grid-template-columns: 1fr;
|
||||
|
@ -46,7 +48,18 @@
|
|||
}
|
||||
|
||||
.active {
|
||||
border: 2px solid red;
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
border: 2px solid red;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.full-screen {
|
||||
|
|
|
@ -1,28 +1,37 @@
|
|||
<!-- 视频播放 -->
|
||||
<template>
|
||||
<LivePlayer
|
||||
ref="player"
|
||||
fluent
|
||||
:protocol="props.protocol || 'mp4'"
|
||||
:class="props.className"
|
||||
:loading="props.loading"
|
||||
:live="'live' in props ? props.live !== false : true"
|
||||
:autoplay="'autoplay' in props ? props.autoplay !== false : true"
|
||||
:muted="'muted' in props ? props.muted !== false : true"
|
||||
:hide-big-play-button="true"
|
||||
:poster="props.poster || ''"
|
||||
:timeout="props.timeout || 30"
|
||||
:video-url="url || ''"
|
||||
@play="props.onPlay"
|
||||
@pause="props.onPause"
|
||||
@ended="props.onEnded"
|
||||
@error="props.onError"
|
||||
@timeupdate="props.onTimeUpdate"
|
||||
/>
|
||||
<!-- <LivePlayer-->
|
||||
<!-- ref="player"-->
|
||||
<!-- fluent-->
|
||||
<!-- :protocol="props.protocol || 'mp4'"-->
|
||||
<!-- :class="props.className"-->
|
||||
<!-- :loading="props.loading"-->
|
||||
<!-- :live="'live' in props ? props.live !== false : true"-->
|
||||
<!-- :autoplay="'autoplay' in props ? props.autoplay !== false : true"-->
|
||||
<!-- :muted="'muted' in props ? props.muted !== false : true"-->
|
||||
<!-- :hide-big-play-button="true"-->
|
||||
<!-- :poster="props.poster || ''"-->
|
||||
<!-- :timeout="props.timeout || 30"-->
|
||||
<!-- :video-url="url || ''"-->
|
||||
<!-- @play="props.onPlay"-->
|
||||
<!-- @pause="props.onPause"-->
|
||||
<!-- @ended="props.onEnded"-->
|
||||
<!-- @error="props.onError"-->
|
||||
<!-- @timeupdate="props.onTimeUpdate"-->
|
||||
<!-- />-->
|
||||
<div class="media-player-container" >
|
||||
<div ref="playerElement">
|
||||
<span v-if="!props.url">
|
||||
No Video
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LivePlayer from '@liveqing/liveplayer-v3';
|
||||
<script setup lang="ts" name="LivePlayer">
|
||||
import Player, { Events, Sniffer } from 'xgplayer'
|
||||
import { settingEnum } from './utils'
|
||||
|
||||
type PlayerProps = {
|
||||
url?: string;
|
||||
|
@ -35,7 +44,7 @@ type PlayerProps = {
|
|||
updateTime?: number;
|
||||
key?: string | number;
|
||||
loading?: boolean;
|
||||
protocol?: 'mp4' | 'flv' | 'hls' | 'rtc';
|
||||
protocol?: 'mp4' | 'flv' | 'm3u8' | 'rtc';
|
||||
onDestroy?: (e?: any) => void;
|
||||
onMessage?: (msg: any) => void;
|
||||
onError?: (err: any) => void;
|
||||
|
@ -52,34 +61,115 @@ type PlayerProps = {
|
|||
|
||||
const props = defineProps<PlayerProps>();
|
||||
|
||||
const player = ref<HTMLVideoElement>();
|
||||
const url = ref(props.url);
|
||||
|
||||
watchEffect(() => {
|
||||
url.value = props.url;
|
||||
});
|
||||
const playerElement = ref<HTMLVideoElement>();
|
||||
let player: any = null
|
||||
|
||||
/**
|
||||
* 播放
|
||||
*/
|
||||
const play = () => {
|
||||
player.value?.play();
|
||||
player?.play();
|
||||
};
|
||||
|
||||
/**
|
||||
* 暂停
|
||||
*/
|
||||
const pause = () => {
|
||||
player.value?.pause();
|
||||
player?.pause();
|
||||
};
|
||||
|
||||
/**
|
||||
* 暂停状态
|
||||
*/
|
||||
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({
|
||||
play,
|
||||
pause,
|
||||
|
@ -93,4 +183,13 @@ defineExpose({
|
|||
:deep(.vjs-icon-spinner){
|
||||
display: none;
|
||||
}
|
||||
.media-player-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
}
|
||||
</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],
|
||||
}
|
||||
}
|
|
@ -114,15 +114,15 @@ const handleRadio = (item: any) => {
|
|||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
|
||||
cursor: pointer;
|
||||
.img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
&.active {
|
||||
color: #1d39c4;
|
||||
border-color: #1d39c4;
|
||||
color: @primary-color;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,8 +195,8 @@ const handleRadio = (item: any) => {
|
|||
height: 100px;
|
||||
}
|
||||
&.active {
|
||||
color: #1d39c4;
|
||||
border-color: #1d39c4;
|
||||
color: @primary-color;
|
||||
border-color: @primary-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
<div class="value-item-warp">
|
||||
<j-select
|
||||
v-if="typeMap.get(itemType) === 'select'"
|
||||
:mode="mode"
|
||||
v-model:value="myValue"
|
||||
:options="options"
|
||||
allowClear
|
||||
style="width: 100%"
|
||||
allowClear
|
||||
:mode="mode"
|
||||
v-bind="extra"
|
||||
:options="options"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
@change='selectChange'
|
||||
/>
|
||||
|
@ -17,6 +18,7 @@
|
|||
allowClear
|
||||
valueFormat="HH:mm:ss"
|
||||
style="width: 100%"
|
||||
v-bind="extra"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
@change='timeChange'
|
||||
/>
|
||||
|
@ -26,7 +28,8 @@
|
|||
allowClear
|
||||
showTime
|
||||
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
style="width: 100%;"
|
||||
v-bind="extra"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
@change='dateChange'
|
||||
/>
|
||||
|
@ -35,12 +38,14 @@
|
|||
v-model:value="myValue"
|
||||
allowClear
|
||||
style="width: 100%"
|
||||
v-bind="extra"
|
||||
@change='inputChange'
|
||||
/>
|
||||
<j-input
|
||||
allowClear
|
||||
v-else-if="typeMap.get(itemType) === 'object'"
|
||||
v-model:value="myValue"
|
||||
v-bind="extra"
|
||||
@change='inputChange'
|
||||
>
|
||||
<template #addonAfter>
|
||||
|
@ -50,11 +55,13 @@
|
|||
<GeoComponent
|
||||
v-else-if="typeMap.get(itemType) === 'geoPoint'"
|
||||
v-model:point="myValue"
|
||||
v-bind="extra"
|
||||
@change='inputChange'
|
||||
/>
|
||||
<j-input
|
||||
v-else-if="typeMap.get(itemType) === 'file'"
|
||||
v-model:value="myValue"
|
||||
v-bind="extra"
|
||||
placeholder="请输入链接"
|
||||
allowClear
|
||||
@change='inputChange'
|
||||
|
@ -75,6 +82,7 @@
|
|||
v-else-if="typeMap.get(itemType) === 'password'"
|
||||
allowClear
|
||||
type="password"
|
||||
v-bind="extra"
|
||||
v-model:value="myValue"
|
||||
style="width: 100%"
|
||||
@change='inputChange'
|
||||
|
@ -85,6 +93,7 @@
|
|||
allowClear
|
||||
type="text"
|
||||
v-model:value="myValue"
|
||||
v-bind="extra"
|
||||
style="width: 100%"
|
||||
@change='inputChange'
|
||||
/>
|
||||
|
@ -113,11 +122,10 @@ import { PropType } from 'vue';
|
|||
import { UploadChangeParam, UploadFile } from 'ant-design-vue';
|
||||
import { DefaultOptionType } from 'ant-design-vue/lib/select';
|
||||
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 { ItemData, ITypes } from './types';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { Upload } from 'jetlinks-ui-components'
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: string | number | boolean): void;
|
||||
|
@ -157,6 +165,10 @@ const props = defineProps({
|
|||
getPopupContainer: {
|
||||
type: Function,
|
||||
default: undefined
|
||||
},
|
||||
extra: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
// type Props = {
|
||||
|
@ -182,6 +194,7 @@ const componentsType = ref<ITypes>({
|
|||
object: 'object',
|
||||
geoPoint: 'geoPoint',
|
||||
file: 'file',
|
||||
time: 'time',
|
||||
});
|
||||
const typeMap = new Map(Object.entries(componentsType.value));
|
||||
|
||||
|
|
|
@ -16,4 +16,6 @@ export type ITypes = {
|
|||
object: string
|
||||
geoPoint: string
|
||||
file: string
|
||||
}
|
||||
|
||||
time: string
|
||||
}
|
||||
|
|
|
@ -12,12 +12,16 @@ import { BasicLayoutPage, BlankLayoutPage, FullPage } from './Layout'
|
|||
import RadioCard from './RadioCard/index.vue'
|
||||
import { PageContainer, AIcon, Ellipsis } from 'jetlinks-ui-components'
|
||||
import MarkDown from './Markdown'
|
||||
import CardSelect from './CardSelect'
|
||||
// import Ellipsis from './Ellipsis/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 ValueItem from './ValueItem/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 {
|
||||
install(app: App) {
|
||||
|
@ -43,5 +47,9 @@ export default {
|
|||
.component('FullPage', FullPage)
|
||||
.component('RadioCard', RadioCard)
|
||||
.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