feat: 2.1
* feat: 优化物模型单个拷贝、新增编辑逻辑 * fix: 修改bug * fix: 应用管理赋权接口报错 * fix: 修改登录后跳转方式 * fix: 修改物模型的其他配置按照类型配置 * feat: 登录密码加密 * fix: 应用管理赋权接口报错 * feat: 优化物模型-标签去掉详情 * fix: 修改bug * fix: 修改bug * fix: 修复权限导入无法编辑问题 * fix: 修复物模型-功能无法编辑问题 * fix: 修复物模型-功能详情 * fix: 修改ui * fix: 修改bug * fix: 修复物模型-编辑表格 * fix: 优化物模型-编辑表格-时间、枚举 * fix: 修改bug * fix: 优化物模型必填校验 * fix: 修改bug * fix: 修改bug * fix: 物模型映射搜索 * fix: 优化物模型-详情 * fix: 优化物模型-规则 * fix: 修改bug * fix: 优化物模型-功能定义 * fix: 修改bug * fix: 修改bug * fix: 优化物模型-功能定义(输入、输出) * fix: 优化物模型-功能定义(输入、输出) * fix: 修改bug * fix: 优化物模型-功能定义-输入 * fix: 优化物模型-事件定义 * fix: 修改bug * fix: 优化物模型-功能定义-输出 * fix: 修改bug * fix: 修改bug * fix: 修改bug * fix: 优化物模型-功能定义-输出 * fix: 优化物模型-功能定义-(输入、输出) * fix: 修改bug * fix: 优化物模型-编辑表格标识校验 * fix: 优化物模型-标签定义 * fix: 优化设备物模型 * fix: 修改bug * fix: allow-scripts * fix: 优化设备物模型 * fix: 修改bug * fix: 修改bug * fix: 修改message * fix: bug#16085、16024、15967、15924、16072、16070、16067 * fix: 修改bug * fix: 优化产品物模型编辑状态 * fix: bug#15155 * fix: bug#15531 * fix: bug#16173、16172、16138、15092 * fix: 修改bug * fix: 去掉cancelSelect * fix: bug#16110 * fix: 10080 * fix: bug#10080 * fix: 修改bug * fix: bug#11210 * fix: bug#15718 * fix: 撤销bug#11210修改 * feat: 更新README * feat: 更改顶部以及侧边菜单配置到store\system.ts * feat: 新增README 更改配置 内容 * feat: 新增README 新增/编辑菜单 * feat: 新增README 去掉导航栏右上角jetlinks文档 * fix: 修复边缘端映射bug * fix: 修改登录后跳转方式 * fix: 修改物模型的其他配置按照类型配置 * feat: 登录密码加密 * fix: 应用管理赋权接口报错 * fix: 修复边缘端映射bug * fix: 修改bug * fix: bug#16191、16070、16087、15717、15420 * fix: bug#10551 * fix: bug#16097 * fix: 修改bug * fix: bug#16195 * fix: bug#10750 * fix: bug#11076 * fix: 边缘网关绑定子设备 * fix: bug#11093 * fix: bug#15420、16072、16195、16208、16218、16220、16222 * fix: bug#16077 * fix: bug#16212、16217、16223 * fix: 修改dueros * fix: bug#16092 * fix: bug#16233 * fix: bug#15649 * fix: bug#15933 * fix: 优化物模型属性-指标值 * fix: bug#15649、16087、16231、16254 * fix: 优化设备物模型属性保存 * fix: 修改bug * fix: 删除多余showSizeChanger * fix: 修改bug * fix: 修改bug * fix: 修改bug * fix: bug#16210 * fix: bug#16267 * fix: 修改bug * fix: bug#16277、16265 * fix: 修复物模型无法新增 * fix: 修改bug * fix: bug#16275、16087 * fix: 角色管理编辑时新增用户表格增加筛选条件,过滤超级管理员 * fix: bug#16232 * fix: bug#16280 * fix: bug#16312 * fix: 优化视频回放进度无法显示 * fix: bug#16315 * fix: 修改bug * fix: bug#15871、#16254、#16317 * fix: 优化物模型保存多次提交 * fix: bug#16329、16310、16287、16234、16135 * fix: bug#16316、#16314 * fix: bug#16263 * fix: 根据协议列表展示数据采集菜单 * fix: bug#16333 * fix: bug#16279 * fix: 协议列表提出公共变量 * fix: bug#16337、16338 * fix: bug#16325 * fix: bug * fix: 修改bug * fix: bug#16301 * fix: bug#16301 * fix: 修改调试 * fix: bug#16364 * fix: bug#16329 * fix: 修改bug * fix: 修复物模型属性-规则窗口回显 * fix: 修改bug * fix: bug#16356 * fix: bug#16279 * fix: 数据采集 * fix: bug#16356 * fix: bug#16365 * fix: bug#16365 * fix: bug#16362 * fix: bug#16358、16366、16374、16375、16379、16385 * fix: 修改bug * fix: bug#16382 * fix: bug#16386 * fix: bug#16373 * fix: 修复bug * fix: bug#16322 * fix: 修改bug * fix: 修改bug * fix: bug#16355、16323、16349 * fix: 优化物模型重置 * fix: bug#10823 * fix: bug#16404 * fix: 修改bug * fix: 修改bug * fix: 修改bug * fix: bug#16395 * fix: bug#16326 * fix: bug#16382、16381、16384 * fix: 修改bug * fix: bug#16351 * fix: 修改bug * fix: 修改bug * fix: bug#16417 * fix: bug#16423 * fix: bug#16325 * fix: 修改bug * fix: 修改bug * fix: bug#16114 * fix: 修复bug * fix: 修改bug * fix: bug#16114 * fix: bug#16430 * fix: bug#15968 * fix: bug#16325 * fix: 修改bug * fix: 修改bug * fix: bug#16179 * fix: bug#16434 * fix: 修改bug * feat: 新增异步新建script标签 * fix: bug#16434 * fix: bug#16436 * fix: 修改bug * fix: 微信登录 * fix: bug#16436 * fix: bug#16441 * fix: 修复TSL不展示虚拟属性 * fix: 修改bug * fix: bug#16436 * fix: bug#16436 * fix: 修改微信 * fix: 修复登录页动态显示可视化
210
README.md
|
@ -37,3 +37,213 @@ yarn dev:force
|
||||||
|
|
||||||
* 项目在开发模式下,首页加载慢属于正常现象;
|
* 项目在开发模式下,首页加载慢属于正常现象;
|
||||||
* 打开F12后页面卡顿是`vuetools`引起,[https://github.com/vuejs/devtools/issues/1987](https://github.com/vuejs/devtools/issues/1987)
|
* 打开F12后页面卡顿是`vuetools`引起,[https://github.com/vuejs/devtools/issues/1987](https://github.com/vuejs/devtools/issues/1987)
|
||||||
|
|
||||||
|
## 更改配置
|
||||||
|
|
||||||
|
### 默认图标以及系统名称
|
||||||
|
|
||||||
|
#### 1.基础配置
|
||||||
|
|
||||||
|
首先启动项目,找到顶部菜单的 系统管理 -> 基础配置
|
||||||
|
|
||||||
|
此处可以更改系统名称、主题色、系统logo、浏览器页签等
|
||||||
|
|
||||||
|
#### 2.默认配置
|
||||||
|
|
||||||
|
在代码根目录找到`config\config.ts`文件
|
||||||
|
|
||||||
|
> 默认图标以及系统名称优先使用基础配置的数据!
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default {
|
||||||
|
...
|
||||||
|
logo: '/favicon.ico', // 浏览器标签页logo(不要修改,如需修改默认图标请在根目录public\favicon.ico替换此文件)
|
||||||
|
title: 'Jetlinks', // 浏览器标签页title(刷新状态时浏览器标签页名称)
|
||||||
|
layout: {
|
||||||
|
title: '物联网平台', // 平台title(默认配置不生效,优先使用基础配置的数据)
|
||||||
|
logo: '/logo.png', // 平台logo(不要修改,如需修改默认logo请在根目录public\logo.png替换此文件)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 去掉或修改备案信息
|
||||||
|
|
||||||
|
#### 修改备案信息
|
||||||
|
|
||||||
|
在`src\views\user\Login\index.vue`文件
|
||||||
|
|
||||||
|
在第16行左右,修改以下代码`备案: xxx(自己的备案信息)`
|
||||||
|
```javascript
|
||||||
|
<a
|
||||||
|
href="https://beian.miit.gov.cn/#/Integrated/index"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="records"
|
||||||
|
>
|
||||||
|
备案: xxx(自己的备案信息)
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 去掉导航栏右上角jetlinks文档
|
||||||
|
|
||||||
|
在`src\components\Layout\BasicLayoutPage.vue`文件
|
||||||
|
在第23行左右,注释以下代码
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
<!-- <AIcon type="QuestionCircleOutlined" @click="toDoc" /> -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### 新增菜单
|
||||||
|
|
||||||
|
新增或者修改菜单有两种方式,第一个是代码内的初始化菜单,第二个系统管理的菜单管理
|
||||||
|
|
||||||
|
* 初始化菜单
|
||||||
|
初始化菜单是默认的菜单,在进行系统初始化会使用到。
|
||||||
|
> 在进行菜单初始化时,如果只在菜单管理新增或修改,但是没有在初始化菜单里新增或者修改,则只会保留初始化菜单!
|
||||||
|
|
||||||
|
* 菜单管理
|
||||||
|
菜单管理是 系统管理 -> 菜单管理 的菜单,可以动态修改,新增或者更改
|
||||||
|
> 如果需要系统初始化时不丢失,请在`src\views\init-home\data\baseMenu.ts`文件下新增或者修改初始化菜单
|
||||||
|
|
||||||
|
|
||||||
|
**新增或者修改菜单之前,确保`src\views`文件夹下有对应的文件夹以及index.vue入口文件**
|
||||||
|
|
||||||
|
#### 1.菜单管理
|
||||||
|
|
||||||
|
新建文件夹以及文件`src\views\test\Home\index.vue`
|
||||||
|
|
||||||
|
##### 新增顶部菜单 test菜单
|
||||||
|
|
||||||
|
1. 启动项目,找到顶部菜单的 系统管理 -> 菜单管理,点击菜单配置旁边的新增按钮。
|
||||||
|
|
||||||
|
2. 完成菜单图标、名称、编码、页面地址、权限配置等
|
||||||
|
> 编码是唯一的,必须和文件路径一致此处是顶级菜单编码填入: test
|
||||||
|
> 页面地址建议和文件路径保持一致: /test
|
||||||
|
|
||||||
|
3. 点击保存,刷新页面后生效
|
||||||
|
|
||||||
|
4. 按钮权限 顶级菜单没有页面可以不用添加按钮权限
|
||||||
|
|
||||||
|
##### 新增子菜单
|
||||||
|
|
||||||
|
1. 在菜单管理 test菜单 新增子菜单
|
||||||
|
|
||||||
|
2. 完成菜单图标、名称、编码、页面地址、权限配置等
|
||||||
|
> 编码是唯一的,必须和文件路径一致此处是test菜单下的二级菜单编码填入: test/Home
|
||||||
|
> 页面地址建议和文件路径保持一致: /test/Home
|
||||||
|
|
||||||
|
4. 点击保存,刷新页面后生效
|
||||||
|
|
||||||
|
5. 按钮权限 如果有权限控制可以添加对应权限
|
||||||
|
|
||||||
|
|
||||||
|
#### 2.初始化菜单
|
||||||
|
|
||||||
|
建议在菜单管理新增或修改菜单之后,再来新增或修改初始化菜单
|
||||||
|
|
||||||
|
##### 新增顶部菜单test以及子菜单
|
||||||
|
|
||||||
|
**确定有对应的文件夹以及文件`src\views\test\Home\index.vue`**
|
||||||
|
|
||||||
|
1. 启动项目,找到顶部菜单的 系统管理 -> 菜单管理
|
||||||
|
打开控制台(F12),选中网络(Network),点击菜单管理的搜索或者重置,直到有 tree 的接口请求。
|
||||||
|
点击接口请求 tree , 并选中响应或者预览选项,找到刚刚新增code为test的数据,复制test菜单的数据
|
||||||
|
|
||||||
|
2. 在`src\views\init-home\data\baseMenu.ts`文件中添加配置
|
||||||
|
|
||||||
|
把第一步test菜单的数据复制到对应位置即可
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default [
|
||||||
|
// 物联网
|
||||||
|
...
|
||||||
|
// 视频中心
|
||||||
|
...
|
||||||
|
// 系统管理
|
||||||
|
...
|
||||||
|
// 物联卡
|
||||||
|
...
|
||||||
|
|
||||||
|
// test菜单
|
||||||
|
{
|
||||||
|
"id": "eb2858ec8dc6d12645a19ee0ed6aba36",
|
||||||
|
"parentId": "",
|
||||||
|
"path": "FwY9",
|
||||||
|
"sortIndex": 5,
|
||||||
|
"level": 1,
|
||||||
|
"owner": "iot",
|
||||||
|
"name": "test菜单",
|
||||||
|
"code": "test",
|
||||||
|
"describe": "",
|
||||||
|
"url": "/test",
|
||||||
|
"icon": "StepForwardOutlined",
|
||||||
|
"status": 1,
|
||||||
|
"permissions": [],
|
||||||
|
"accessSupport": {
|
||||||
|
"text": "不支持",
|
||||||
|
"value": "unsupported"
|
||||||
|
},
|
||||||
|
"indirectMenus": [],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "1995dcd016aaad7c5515f8ead14ca617",
|
||||||
|
"parentId": "eb2858ec8dc6d12645a19ee0ed6aba36",
|
||||||
|
"path": "FwY9-T6lF",
|
||||||
|
"sortIndex": 1,
|
||||||
|
"level": 2,
|
||||||
|
"owner": "iot",
|
||||||
|
"name": "首页",
|
||||||
|
"code": "test/Home",
|
||||||
|
"describe": "",
|
||||||
|
"url": "/test/Home",
|
||||||
|
"icon": "HeatMapOutlined",
|
||||||
|
"status": 1,
|
||||||
|
"permissions": [],
|
||||||
|
"accessSupport": {
|
||||||
|
"text": "不支持",
|
||||||
|
"value": "unsupported"
|
||||||
|
},
|
||||||
|
"indirectMenus": [],
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"id": "add",
|
||||||
|
"name": "新增",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"permission": "role",
|
||||||
|
"actions": [
|
||||||
|
"query",
|
||||||
|
"save",
|
||||||
|
"delete"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"creatorId": "1199596756811550720",
|
||||||
|
"createTime": 1688032521555,
|
||||||
|
"supportDataAccess": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"creatorId": "1199596756811550720",
|
||||||
|
"createTime": 1688032467222,
|
||||||
|
"supportDataAccess": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
> 新增初始化菜单之后需要进行系统初始化才能生效
|
||||||
|
|
||||||
|
##### 修改初始化菜单
|
||||||
|
|
||||||
|
同上,在菜单管理修改对应的数据,复制对应的菜单数据,然后替换掉`baseMenu.ts`对应数据即可。
|
||||||
|
|
||||||
|
> 修改初始化菜单之后需要进行菜单初始化才能生效
|
||||||
|
|
||||||
|
##### 系统初始化
|
||||||
|
|
||||||
|
在浏览器顶部修改页面路径,`/#/`后边输入`init-home`,回车进入系统初始化页面
|
||||||
|
例如: `http://localhost:5174/#/init-home`
|
||||||
|
|
||||||
|
填写好基本信息,角色初始化等,点击确定
|
||||||
|
|
4
build.sh
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.1.0-SNAPSHOT .
|
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.1.0-TEST .
|
||||||
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.1.0-SNAPSHOT
|
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.1.0-TEST
|
||||||
|
|
|
@ -7,9 +7,6 @@ export default {
|
||||||
layout: {
|
layout: {
|
||||||
title: '物联网平台', // 平台title
|
title: '物联网平台', // 平台title
|
||||||
logo: '/logo.png', // 平台logo
|
logo: '/logo.png', // 平台logo
|
||||||
siderWidth: 208, // 左侧菜单栏宽度
|
|
||||||
headerHeight: 48, // 头部高度
|
|
||||||
collapsedWidth: 48,
|
|
||||||
mode: 'inline',
|
mode: 'inline',
|
||||||
theme: 'light', // 'dark' 'light'
|
theme: 'light', // 'dark' 'light'
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,9 @@
|
||||||
"event-source-polyfill": "^1.0.31",
|
"event-source-polyfill": "^1.0.31",
|
||||||
"global": "^4.4.0",
|
"global": "^4.4.0",
|
||||||
"jetlinks-store": "^0.0.3",
|
"jetlinks-store": "^0.0.3",
|
||||||
"jetlinks-ui-components": "^1.0.21",
|
"jetlinks-ui-components": "^1.0.24",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
|
"jsencrypt": "^3.3.2",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"less-loader": "^11.1.0",
|
"less-loader": "^11.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
"v-clipboard3": "^0.1.4",
|
"v-clipboard3": "^0.1.4",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
|
"vue-cropper": "^1.0.9",
|
||||||
"vue-json-viewer": "^3.0.4",
|
"vue-json-viewer": "^3.0.4",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vue3-json-viewer": "^2.2.2",
|
"vue3-json-viewer": "^2.2.2",
|
||||||
|
|
|
@ -136,7 +136,7 @@ const matchComponents: IMatcher[] = [
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
pattern: /^Select/,
|
pattern: /^Select|^SelectBoolean/,
|
||||||
styleDir: 'Select'
|
styleDir: 'Select'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -204,6 +204,10 @@ const matchComponents: IMatcher[] = [
|
||||||
pattern: /^Empty/,
|
pattern: /^Empty/,
|
||||||
styleDir: 'Empty'
|
styleDir: 'Empty'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: /^PopconfirmModal/,
|
||||||
|
styleDir: 'PopconfirmModal'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: /^Popconfirm/,
|
pattern: /^Popconfirm/,
|
||||||
styleDir: 'Popconfirm'
|
styleDir: 'Popconfirm'
|
||||||
|
@ -215,7 +219,15 @@ const matchComponents: IMatcher[] = [
|
||||||
{
|
{
|
||||||
pattern: /^Notification/,
|
pattern: /^Notification/,
|
||||||
styleDir: 'Notification'
|
styleDir: 'Notification'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
pattern: /^DataTable/,
|
||||||
|
styleDir: 'DataTable'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /^CheckButton/,
|
||||||
|
styleDir: 'CheckButton'
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export interface JetlinksVueResolverOptions {
|
export interface JetlinksVueResolverOptions {
|
||||||
|
@ -294,7 +306,19 @@ function getSideEffects(compName: string, options: JetlinksVueResolverOptions, _
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterName = ['message', 'Notification']
|
const filterName = ['message', 'Notification']
|
||||||
const primitiveNames = ['AIcon','Affix', 'Anchor', 'AnchorLink', 'message', 'Notification', 'AutoComplete', 'AutoCompleteOptGroup', 'AutoCompleteOption', 'Alert', 'Avatar', 'AvatarGroup', 'BackTop', 'Badge', 'BadgeRibbon', 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbSeparator', 'Button', 'ButtonGroup', 'Calendar', 'Card', 'CardGrid', 'CardMeta', 'Collapse', 'CollapsePanel', 'Carousel', 'Cascader', 'Checkbox', 'CheckboxGroup', 'Col', 'Comment', 'ConfigProvider', 'DatePicker', 'MonthPicker', 'WeekPicker', 'RangePicker', 'QuarterPicker', 'Descriptions', 'DescriptionsItem', 'Divider', 'Dropdown', 'DropdownButton', 'Drawer', 'Empty', 'Form', 'FormItem', 'FormItemRest', 'Grid', 'Input', 'InputGroup', 'InputPassword', 'InputSearch', 'Textarea', 'Image', 'ImagePreviewGroup', 'InputNumber', 'Layout', 'LayoutHeader', 'LayoutSider', 'LayoutFooter', 'LayoutContent', 'List', 'ListItem', 'ListItemMeta', 'Menu', 'MenuDivider', 'MenuItem', 'MenuItemGroup', 'SubMenu', 'Mentions', 'MentionsOption', 'Modal', 'Statistic', 'StatisticCountdown', 'PageHeader', 'Pagination', 'Popconfirm', 'Popover', 'Progress', 'Radio', 'RadioButton', 'RadioGroup', 'Rate', 'Result', 'Row', 'Select', 'SelectOptGroup', 'SelectOption', 'Skeleton', 'SkeletonButton', 'SkeletonAvatar', 'SkeletonInput', 'SkeletonImage', 'Slider', 'Space', 'Spin', 'Steps', 'Step', 'Switch', 'Table', 'TableColumn', 'TableColumnGroup', 'TableSummary', 'TableSummaryRow', 'TableSummaryCell', 'Transfer', 'Tree', 'TreeNode', 'DirectoryTree', 'TreeSelect', 'TreeSelectNode', 'Tabs', 'TabPane', 'Tag', 'CheckableTag', 'TimePicker', 'TimeRangePicker', 'Timeline', 'TimelineItem', 'Tooltip', 'Typography', 'TypographyLink', 'TypographyParagraph', 'TypographyText', 'TypographyTitle', 'Upload', 'UploadDragger', 'LocaleProvider', 'ProTable', 'Search', 'AdvancedSearch', 'Ellipsis', 'MonacoEditor', 'ProLayout', 'ScrollTable', 'TableCard', 'Scrollbar', 'CardSelect', 'ColorPicker']
|
const primitiveNames = ['AIcon','Affix', 'Anchor', 'AnchorLink', 'message', 'Notification', 'AutoComplete', 'AutoCompleteOptGroup', 'AutoCompleteOption', 'Alert', 'Avatar', 'AvatarGroup', 'BackTop', 'Badge', 'BadgeRibbon', 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbSeparator', 'Button', 'ButtonGroup', 'Calendar', 'Card', 'CardGrid', 'CardMeta', 'Collapse', 'CollapsePanel', 'Carousel', 'Cascader', 'Checkbox', 'CheckboxGroup', 'Col', 'Comment', 'ConfigProvider', 'DatePicker', 'MonthPicker', 'WeekPicker', 'RangePicker', 'QuarterPicker', 'Descriptions', 'DescriptionsItem', 'Divider', 'Dropdown', 'DropdownButton', 'Drawer', 'Empty', 'Form', 'FormItem', 'FormItemRest', 'Grid', 'Input', 'InputGroup', 'InputPassword', 'InputSearch', 'Textarea', 'Image', 'ImagePreviewGroup', 'InputNumber', 'Layout', 'LayoutHeader', 'LayoutSider', 'LayoutFooter', 'LayoutContent', 'List', 'ListItem', 'ListItemMeta', 'Menu', 'MenuDivider', 'MenuItem', 'MenuItemGroup', 'SubMenu', 'Mentions', 'MentionsOption', 'Modal', 'Statistic', 'StatisticCountdown', 'PageHeader', 'Pagination', 'Popconfirm', 'Popover', 'Progress', 'Radio', 'RadioButton', 'RadioGroup', 'Rate', 'Result', 'Row', 'Select', 'SelectOptGroup', 'SelectOption', 'SelectBoolean', 'Skeleton', 'SkeletonButton', 'SkeletonAvatar', 'SkeletonInput', 'SkeletonImage', 'Slider', 'Space', 'Spin', 'Steps', 'Step', 'Switch', 'Table', 'TableColumn', 'TableColumnGroup', 'TableSummary', 'TableSummaryRow', 'TableSummaryCell', 'Transfer', 'Tree', 'TreeNode', 'DirectoryTree', 'TreeSelect', 'TreeSelectNode', 'Tabs', 'TabPane', 'Tag', 'CheckableTag', 'TimePicker', 'TimeRangePicker', 'Timeline', 'TimelineItem', 'Tooltip', 'Typography', 'TypographyLink', 'TypographyParagraph', 'TypographyText', 'TypographyTitle', 'Upload', 'UploadDragger', 'LocaleProvider', 'ProTable', 'Search', 'AdvancedSearch', 'Ellipsis', 'MonacoEditor', 'ProLayout', 'ScrollTable', 'TableCard', 'Scrollbar', 'CardSelect', 'ColorPicker', 'PopconfirmModal', 'DataTable',
|
||||||
|
'DataTableArray',
|
||||||
|
'DataTableString',
|
||||||
|
'DataTableInteger',
|
||||||
|
'DataTableDouble',
|
||||||
|
'DataTableBoolean',
|
||||||
|
'DataTableEnum',
|
||||||
|
'DataTableFile',
|
||||||
|
'DataTableDate',
|
||||||
|
'DataTableTypeSelect',
|
||||||
|
'DataTableObject',
|
||||||
|
'CheckButton',
|
||||||
|
]
|
||||||
const prefix = 'J'
|
const prefix = 'J'
|
||||||
|
|
||||||
let jetlinksNames: Set<string>
|
let jetlinksNames: Set<string>
|
||||||
|
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 898 B |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1015 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -30,3 +30,8 @@ export const checkOldPassword_api = (password:string) => server.post(`/user/me/p
|
||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 我的订阅
|
||||||
|
// 查询当前用户可访问的通道配置
|
||||||
|
export const getAllNotice = () => server.get(`/notify/channel/all`);
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,29 @@
|
||||||
import server from '@/utils/request'
|
import server from '@/utils/request'
|
||||||
|
|
||||||
// 获取记录列表
|
// 获取记录列表
|
||||||
export const getList_api = (data: object): any => server.post(`/notifications/_query`, data)
|
export const getList_api = (data: any): any => server.post(`/notifications/_query`, data)
|
||||||
// 获取未读记录列表
|
// 获取未读记录列表
|
||||||
export const getListByUnRead_api = (data: object): any => server.post(`/notifications/_query`, data)
|
// export const getListByUnRead_api = (data: any): any => server.post(`/notifications/_query`, data)
|
||||||
// 修改记录状态
|
// 修改记录状态
|
||||||
export const changeStatus_api = (type: '_read' | '_unread', data: string[]): any => server.post(`/notifications/${type}`, data)
|
export const changeStatus_api = (type: '_read' | '_unread', data: string[]): any => server.post(`/notifications/${type}`, data)
|
||||||
|
|
||||||
|
export const changeAllStatus = (type: '_read' | '_unread', data: string[]): any => server.post(`/notifications/${type}/provider`, data)
|
||||||
|
|
||||||
const encodeParams = (params: Record<string, any>) => {
|
|
||||||
let result = {}
|
|
||||||
for (const key in params) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(params, key)) {
|
|
||||||
const value = params[key];
|
|
||||||
if (key === 'terms') {
|
|
||||||
result['terms[0].column:'] = 0
|
|
||||||
result['terms[0].value'] = JSON.stringify(value[0])
|
|
||||||
} else result[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
// 查询告警记录详情
|
||||||
};
|
export const getDetail = (id: string): any => server.get(`/alarm/record/${id}`)
|
||||||
|
|
||||||
|
// const encodeParams = (params: Record<string, any>) => {
|
||||||
|
// let result = {}
|
||||||
|
// for (const key in params) {
|
||||||
|
// if (Object.prototype.hasOwnProperty.call(params, key)) {
|
||||||
|
// const value = params[key];
|
||||||
|
// if (key === 'terms') {
|
||||||
|
// result['terms[0].column:'] = 0
|
||||||
|
// result['terms[0].value'] = JSON.stringify(value[0])
|
||||||
|
// } else result[key] = value
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return result
|
||||||
|
// };
|
|
@ -20,3 +20,22 @@ export const getAlarmList_api = () => server.post(`/alarm/config/_query/no-pagin
|
||||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
paging: false,
|
paging: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 判断获取当前用户绑定信
|
||||||
|
export const getIsBindThird = () => server.get(`/user/third-party/me`);
|
||||||
|
|
||||||
|
// 生成OAuth2授权URL
|
||||||
|
export const getWechatOAuth2 = (configId: string, templateId: string, url: string) => server.get(`/notifier/wechat/corp/${configId}/${templateId}/oauth2/binding-user-url?redirectUri=${url}`);
|
||||||
|
|
||||||
|
export const getDingTalkOAuth2 = (configId: string, url: string) => server.get(`/notifier/dingtalk/corp/${configId}/oauth2/binding-user-url?authCode=${url}`);
|
||||||
|
|
||||||
|
// 获取oauth2授权的用户绑定码
|
||||||
|
|
||||||
|
export const getUserBind = (type: 'wechat' | 'dingtalk', params: any) => server.get(`/notifier/${type}/corp/oauth2/user-bind-code`, params);
|
||||||
|
|
||||||
|
// 根据绑定码绑定当前用户
|
||||||
|
export const bindThirdParty = (type: string, provider: string, bindCode: string) => server.post(`/user/third-party/me/${type}/${provider}/${bindCode}/_bind`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,3 +35,5 @@ export const systemVersion = () => server.get<{edition?: string}>('/system/versi
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const queryDashboard = (data: Record<string, any>) => server.post(`/dashboard/_multi`, data)
|
export const queryDashboard = (data: Record<string, any>) => server.post(`/dashboard/_multi`, data)
|
||||||
|
|
||||||
|
export const fileUpload = (data: any) => server.post('/file/static', data)
|
|
@ -99,7 +99,7 @@ export const templateDownload = (productId: string, type: string) => server.get(
|
||||||
* @param type 文件类型
|
* @param type 文件类型
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
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)}`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备导出
|
* 设备导出
|
||||||
|
@ -252,6 +252,17 @@ export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) =
|
||||||
*/
|
*/
|
||||||
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
|
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询是否存在云端映射设备
|
||||||
|
*/
|
||||||
|
export const queryDeviceMapping = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-mapping-list/invoke`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存云端映射设备
|
||||||
|
*/
|
||||||
|
export const saveDeviceMapping = (deviceId: string, data: any) => server.post(`/edge/operations/${deviceId}/device-mapping-save-batch/invoke`, data)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取产品列表
|
* 获取产品列表
|
||||||
* @param data
|
* @param data
|
||||||
|
@ -576,14 +587,23 @@ export const getDeviceNumber = (data?:any) => server.post<number>('/device-insta
|
||||||
/**
|
/**
|
||||||
* 导入映射设备
|
* 导入映射设备
|
||||||
* @param productId
|
* @param productId
|
||||||
* @param data
|
* @param data/
|
||||||
*/
|
*/
|
||||||
export const importDeviceByPlugin = (productId: string, data: any[]) => server.post(`/device/instance/plugin/${productId}/import`, data)
|
export const importDeviceByPlugin = (productId: string, data: any[]) => server.post(`/device/instance/plugin/${productId}/import`, data)
|
||||||
|
|
||||||
export const metadateMapById = (productId: string, data: ant[]) => server.patch(`/device/metadata/mapping/product/${productId}`, data)
|
export const metadataMapById = (type: 'device' | 'product', productId: string, data: any[]) => server.patch(`/device/metadata/mapping/${type}/${productId}`, data)
|
||||||
|
|
||||||
export const getMetadateMapById = (productId: string) => server.get(`/device/metadata/mapping/product/${productId}`)
|
export const getMetadataMapById = (type: 'device' | 'product', productId: string) => server.get(`/device/metadata/mapping/${type}/${productId}`)
|
||||||
|
|
||||||
export const getInkingDevices = (data: string[]) => server.post('/plugin/mapping/device/_all', data)
|
export const getInkingDevices = (data: string[]) => server.post('/plugin/mapping/device/_all', data)
|
||||||
|
|
||||||
|
export const getProtocolMetadata = (id: string, transport: string) => server.get(`/protocol/${id}/${transport}/metadata`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则属性
|
||||||
|
*/
|
||||||
|
export const saveDeviceVirtualProperty = (productId: string, deviceId: string, data: any[]) => server.patch(`/virtual/property/product/${productId}/${deviceId}/_batch`, data)
|
||||||
|
|
||||||
|
export const queryDeviceVirtualProperty = (productId: string, deviceId: string, propertyId: string) => server.get(`/virtual/property/device/${productId}/${deviceId}/${propertyId}`)
|
||||||
|
|
||||||
|
export const queryByParent = (deviceId: string) => server.get(`/device/gateway/${deviceId}/parent`)
|
||||||
|
|
|
@ -212,3 +212,12 @@ export const getMetadataDeviceConfig = (params: {
|
||||||
};
|
};
|
||||||
}) => server.get<Record<any, any>[]>(`/device/instance/${params.deviceId}/config-metadata/${params.metadata.type}/${params.metadata.id}/${params.metadata.dataType}`)
|
}) => server.get<Record<any, any>[]>(`/device/instance/${params.deviceId}/config-metadata/${params.metadata.type}/${params.metadata.id}/${params.metadata.dataType}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则属性
|
||||||
|
*/
|
||||||
|
export const saveProductVirtualProperty = (productId: string, data: any[]) => server.patch(`/virtual/property/product/${productId}/_batch`, data)
|
||||||
|
|
||||||
|
export const queryProductVirtualProperty = (productId: string, propertyId: string) => server.get(`/virtual/property/product/${productId}/${propertyId}`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -61,3 +61,10 @@ export const loginout_api = () => server.get<any>('/user-token/reset')
|
||||||
export const getOAuth2 = (params: any) => server.get<any>('/oauth2/authorize', params)
|
export const getOAuth2 = (params: any) => server.get<any>('/oauth2/authorize', params)
|
||||||
|
|
||||||
export const initApplication = (clientId: string | number) => server.get<{name: string}>(`/application/${clientId}/info`)
|
export const initApplication = (clientId: string | number) => server.get<{name: string}>(`/application/${clientId}/info`)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录加密信息
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const authLoginConfig = () => server.get(`/authorize/login/configs`)
|
|
@ -43,7 +43,7 @@ export default {
|
||||||
server.post<recordsItemType[]>(`/media/device/${deviceId}/${channelId}/records/in-server/files`, data),
|
server.post<recordsItemType[]>(`/media/device/${deviceId}/${channelId}/records/in-server/files`, data),
|
||||||
|
|
||||||
// 播放云端回放
|
// 播放云端回放
|
||||||
playbackStart: (recordId: string) => `${BASE_API_PATH}/record/${recordId}.mp4?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`,
|
playbackStart: (recordId: string) => `${BASE_API_PATH}/media/record/${recordId}.mp4?:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`,
|
||||||
|
|
||||||
downLoadFile: (recordId: string) => `${BASE_API_PATH}/record/${recordId}.mp4?download=true&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
downLoadFile: (recordId: string) => `${BASE_API_PATH}/media/record/${recordId}.mp4?download=true&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
||||||
}
|
}
|
|
@ -25,4 +25,5 @@ export default {
|
||||||
// 短信获取签名
|
// 短信获取签名
|
||||||
getSigns: (id: any) => get(`/notifier/sms/aliyun/${id}/signs`),
|
getSigns: (id: any) => get(`/notifier/sms/aliyun/${id}/signs`),
|
||||||
getListByConfigId: (id: string, data: any): any => post(`/notifier/template/${id}/_query`, data),
|
getListByConfigId: (id: string, data: any): any => post(`/notifier/template/${id}/_query`, data),
|
||||||
|
getListVariableByConfigId: (id: string, data?: any): any => post(`/notifier/template/${id}/detail/_query`, data),
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ export const changeApplyStatus_api = (id: string, data: any) => server.put(`/app
|
||||||
// 删除应用
|
// 删除应用
|
||||||
export const delApply_api = (id: string) => server.remove(`/application/${id}`)
|
export const delApply_api = (id: string) => server.remove(`/application/${id}`)
|
||||||
|
|
||||||
|
export const queryType = () => server.get(`/application/providers`)
|
||||||
|
|
||||||
// 获取组织列表
|
// 获取组织列表
|
||||||
export const getDepartmentList_api = (params: any) => server.get(`/organization/_all/tree`, params);
|
export const getDepartmentList_api = (params: any) => server.get(`/organization/_all/tree`, params);
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const getTreeData_api = (data: object) => server.post(`/organization/_all
|
||||||
// 新增部门
|
// 新增部门
|
||||||
export const addDepartment_api = (data: object) => server.post(`/organization`, data);
|
export const addDepartment_api = (data: object) => server.post(`/organization`, data);
|
||||||
// 更新部门
|
// 更新部门
|
||||||
export const updateDepartment_api = (data: object) => server.patch(`/organization`, data);
|
export const updateDepartment_api = (data: any) => server.put(`/organization/${data.id}`, data);
|
||||||
// 删除部门
|
// 删除部门
|
||||||
export const delDepartment_api = (id: string) => server.remove(`/organization/${id}`);
|
export const delDepartment_api = (id: string) => server.remove(`/organization/${id}`);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
|
||||||
|
// 获取角色列表
|
||||||
|
export const queryRoleList = (data: any): Promise<any> => server.post(`/role/_query/`, data);
|
||||||
|
|
||||||
|
// 查询所有通道配置
|
||||||
|
export const queryChannelConfig = (): Promise<any> => server.get(`/notify/channel/all-for-save`);
|
||||||
|
|
||||||
|
// 查询通知通道类型
|
||||||
|
export const queryChannelProviders = (): Promise<any> => server.get(`/notify/channel/providers`);
|
||||||
|
|
||||||
|
// 保存通道配置
|
||||||
|
export const saveChannelConfig = (data: any[]): Promise<any> => server.patch(`/notify/channel`, data);
|
||||||
|
|
||||||
|
export const updateChannelConfig = (providerId: string, data: any[]): Promise<any> => server.patch(`/notify/channel/${providerId}`, data);
|
||||||
|
|
||||||
|
export const editChannelConfig = (providerId: string, data: any): Promise<any> => server.put(`/notify/channel/${providerId}`, data);
|
||||||
|
|
||||||
|
export const actionChannelConfig = (providerId: string, type: 'enable' | 'disable'): Promise<any> => server.post(`/notify/channel/${providerId}/${type}`);
|
||||||
|
|
||||||
|
export const deleteChannelConfig = (providerId: string): Promise<any> => server.remove(`/notify/channel/${providerId}`);
|
||||||
|
|
||||||
|
export const queryConfigVariables = (providerId: string): Promise<any> => server.get(`/notify/channel/${providerId}/variables`);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,12 @@ export default defineComponent({
|
||||||
this.PathNavigatorRef?.moveToPoint(0, 0);
|
this.PathNavigatorRef?.moveToPoint(0, 0);
|
||||||
this.PathNavigatorRef?.stop();
|
this.PathNavigatorRef?.stop();
|
||||||
},
|
},
|
||||||
|
pause() {
|
||||||
|
this.PathNavigatorRef?.pause()
|
||||||
|
},
|
||||||
|
resume() {
|
||||||
|
this.PathNavigatorRef?.resume()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
pathData: {
|
pathData: {
|
||||||
|
@ -101,6 +107,6 @@ export default defineComponent({
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expose: ['start', 'stop']
|
expose: ['start', 'stop', 'pause', 'resume']
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="card j-table-card-box">
|
<div class="card j-table-card-box">
|
||||||
<div
|
<div
|
||||||
class="card-warp"
|
class="card-warp"
|
||||||
:class="{ active: active ? 'active' : '' }"
|
:class="{ active: active ? 'active' : '', 'disabled': disabled }"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
>
|
>
|
||||||
<div class="card-type" v-if="slots.type">
|
<div class="card-type" v-if="slots.type">
|
||||||
|
@ -140,6 +140,10 @@ const props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const getBackgroundColor = (code: string | number) => {
|
const getBackgroundColor = (code: string | number) => {
|
||||||
|
@ -160,6 +164,7 @@ const handleClick = () => {
|
||||||
.card {
|
.card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
.checked-icon {
|
.checked-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -22px;
|
right: -22px;
|
||||||
|
@ -190,16 +195,20 @@ const handleClick = () => {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid #e6e6e6;
|
border: 1px solid #e6e6e6;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 0 24px rgba(#000, 0.1);
|
box-shadow: 0 0 24px rgba(#000, 0.1);
|
||||||
|
|
||||||
.card-mask {
|
.card-mask {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid #2f54eb;
|
border: 1px solid #2f54eb;
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
<template>
|
||||||
|
<div class="debug-container">
|
||||||
|
<div class="left">
|
||||||
|
<div class="header">
|
||||||
|
<div>
|
||||||
|
<div class="title">
|
||||||
|
属性赋值
|
||||||
|
<div class="description">请对上方规则使用的属性进行赋值</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isBeginning && virtualRule?.type === 'window'" class="action" @click="runScriptAgain">
|
||||||
|
<a style="margin-left: 75px;">发送数据</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<j-table :columns="columns" :data-source="property" :pagination="false" bordered size="small">
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<j-auto-complete :options="options" v-model:value="record.id" size="small" style="width: 130px" />
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'current'">
|
||||||
|
<j-input v-model:value="record.current" size="small"></j-input>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'last'">
|
||||||
|
<j-input v-model:value="record.last" size="small"></j-input>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<AIcon type="DeleteOutlined" @click="deleteItem(index)" />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</j-table>
|
||||||
|
<j-button type="dashed" block style="margin-top: 5px" @click="addItem">
|
||||||
|
<template #icon>
|
||||||
|
<AIcon type="PlusOutlined" />
|
||||||
|
</template>
|
||||||
|
添加条目
|
||||||
|
</j-button>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">
|
||||||
|
<div>运行结果</div>
|
||||||
|
</div>
|
||||||
|
<div class="action">
|
||||||
|
<div>
|
||||||
|
<a v-if="isBeginning" @click="beginAction">
|
||||||
|
开始运行
|
||||||
|
</a>
|
||||||
|
<a v-else @click="stopAction">
|
||||||
|
停止运行
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a @click="clearAction">
|
||||||
|
清空
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="log">
|
||||||
|
<j-descriptions>
|
||||||
|
<j-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')"
|
||||||
|
:key="item.time" :span="3">
|
||||||
|
<j-tooltip placement="top" :title="item.content">
|
||||||
|
{{ item.content }}
|
||||||
|
</j-tooltip>
|
||||||
|
</j-descriptions-item>
|
||||||
|
</j-descriptions>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts" name="Debug">
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-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 { onlyMessage } from '@/utils/comm';
|
||||||
|
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
virtualRule: Object as PropType<Record<any, any>>,
|
||||||
|
id: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const isBeginning = ref(true)
|
||||||
|
|
||||||
|
type propertyType = {
|
||||||
|
id?: string,
|
||||||
|
current?: string,
|
||||||
|
last?: string
|
||||||
|
}
|
||||||
|
const property = ref<propertyType[]>([])
|
||||||
|
|
||||||
|
const columns = [{
|
||||||
|
title: '属性ID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'id'
|
||||||
|
}, {
|
||||||
|
title: '当前值',
|
||||||
|
dataIndex: 'current',
|
||||||
|
key: 'current'
|
||||||
|
}, {
|
||||||
|
title: '上一值',
|
||||||
|
dataIndex: 'last',
|
||||||
|
key: 'last'
|
||||||
|
}, {
|
||||||
|
title: '',
|
||||||
|
key: 'action'
|
||||||
|
}]
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
property.value.push({})
|
||||||
|
}
|
||||||
|
const deleteItem = (index: number) => {
|
||||||
|
property.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = ref()
|
||||||
|
|
||||||
|
const virtualIdRef = ref(new Date().getTime());
|
||||||
|
|
||||||
|
const productStore = useProductStore()
|
||||||
|
const ruleEditorStore = useRuleEditorStore()
|
||||||
|
const runScript = () => {
|
||||||
|
const metadata = productStore.current.metadata || '{}';
|
||||||
|
const propertiesList = JSON.parse(metadata).properties || [];
|
||||||
|
const _properties = property.value.map((item: any) => {
|
||||||
|
const _item = propertiesList.find((i: any) => i.id === item.id);
|
||||||
|
return { ...item, type: _item?.valueType?.type };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ws.value) {
|
||||||
|
ws.value.unsubscribe?.();
|
||||||
|
}
|
||||||
|
if (!props.virtualRule?.script) {
|
||||||
|
isBeginning.value = true;
|
||||||
|
onlyMessage('请编辑规则', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ws.value = getWebSocket(`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
||||||
|
'/virtual-property-debug',
|
||||||
|
{
|
||||||
|
virtualId: `${virtualIdRef.value}-virtual-id`,
|
||||||
|
property: props.id,
|
||||||
|
virtualRule: {
|
||||||
|
...props.virtualRule,
|
||||||
|
},
|
||||||
|
properties: _properties || [],
|
||||||
|
}).subscribe((data: any) => {
|
||||||
|
ruleEditorStore.state.log.push({ time: new Date().getTime(), content: JSON.stringify(data.payload) });
|
||||||
|
if (props.virtualRule?.type !== 'window') {
|
||||||
|
stopAction()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsAgain = ref<any>();
|
||||||
|
const runScriptAgain = async () => {
|
||||||
|
if (wsAgain.value) {
|
||||||
|
wsAgain.value.unsubscribe?.();
|
||||||
|
}
|
||||||
|
const metadata = productStore.current.metadata || '{}';
|
||||||
|
const propertiesList = JSON.parse(metadata).properties || [];
|
||||||
|
const _properties = property.value.map((item: any) => {
|
||||||
|
const _item = propertiesList.find((i: any) => i.id === item.id);
|
||||||
|
return { ...item, type: _item?.valueType?.type };
|
||||||
|
});
|
||||||
|
|
||||||
|
wsAgain.value = getWebSocket(`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
||||||
|
'/virtual-property-debug',
|
||||||
|
{
|
||||||
|
virtualId: `${virtualIdRef.value}-virtual-id`,
|
||||||
|
property: props.id,
|
||||||
|
virtualRule: {
|
||||||
|
...props.virtualRule,
|
||||||
|
},
|
||||||
|
properties: _properties || [],
|
||||||
|
}).subscribe((data: any) => { })
|
||||||
|
}
|
||||||
|
|
||||||
|
const beginAction = () => {
|
||||||
|
isBeginning.value = false;
|
||||||
|
runScript();
|
||||||
|
}
|
||||||
|
const stopAction = () => {
|
||||||
|
isBeginning.value = true;
|
||||||
|
if (ws.value) {
|
||||||
|
ws.value.unsubscribe?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const clearAction = () => {
|
||||||
|
ruleEditorStore.set('log', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (ws.value) {
|
||||||
|
ws.value.unsubscribe?.();
|
||||||
|
}
|
||||||
|
clearAction()
|
||||||
|
})
|
||||||
|
|
||||||
|
const options = ref<{ label: string, value: string }[]>()
|
||||||
|
const getProperty = () => {
|
||||||
|
const metadata = productStore.current.metadata || '{}';
|
||||||
|
const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
|
||||||
|
options.value = _p.filter((p) => p.id !== props.id).map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
getProperty()
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.debug-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 340px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 550px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
border-bottom: 1px solid lightgray;
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
width: 150px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
border-left: none;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
border-bottom: 1px solid lightgray;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
height: 290px;
|
||||||
|
padding: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,213 @@
|
||||||
|
<template>
|
||||||
|
<div class="editor-box">
|
||||||
|
<div class="top">
|
||||||
|
<div class="left">
|
||||||
|
<span v-for="item in symbolList.filter((t: SymbolType, i: number) => i <= 3)" :key="item.key"
|
||||||
|
@click="addOperatorValue(item.value)">
|
||||||
|
{{ item.value }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<j-dropdown>
|
||||||
|
<AIcon type="MoreOutlined" />
|
||||||
|
<template #overlay>
|
||||||
|
<j-menu>
|
||||||
|
<j-menu-item v-for="item in symbolList.filter((t: SymbolType, i: number) => i > 6)" :key="item.key"
|
||||||
|
@click="addOperatorValue(item.value)">
|
||||||
|
{{ item.value }}
|
||||||
|
</j-menu-item>
|
||||||
|
</j-menu>
|
||||||
|
</template>
|
||||||
|
</j-dropdown>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<span v-if="mode !== 'advance'">
|
||||||
|
<j-tooltip :title="!id ? '请先输入标识' : '设置属性规则'">
|
||||||
|
<AIcon type="FullscreenOutlined" :class="!id ? 'disabled' : ''" @click="fullscreenClick" />
|
||||||
|
</j-tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="editor">
|
||||||
|
<j-monaco-editor v-if="loading" v-model:model-value="_value" theme="vs" ref="editor" language="javascript"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts" name="Editor">
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mode?: 'advance' | 'simple';
|
||||||
|
id?: string;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'change', data: string): void;
|
||||||
|
(e: 'update:value', data: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
type editorType = {
|
||||||
|
insert(val: string): void
|
||||||
|
}
|
||||||
|
const editor = ref<editorType>()
|
||||||
|
|
||||||
|
type SymbolType = {
|
||||||
|
key: string,
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
const symbolList = [
|
||||||
|
{
|
||||||
|
key: 'add',
|
||||||
|
value: '+',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'subtract',
|
||||||
|
value: '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'multiply',
|
||||||
|
value: '*',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'divide',
|
||||||
|
value: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'parentheses',
|
||||||
|
value: '()',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'cubic',
|
||||||
|
value: '^',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dayu',
|
||||||
|
value: '>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dayudengyu',
|
||||||
|
value: '>=',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dengyudengyu',
|
||||||
|
value: '==',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'xiaoyudengyu',
|
||||||
|
value: '<=',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'xiaoyu',
|
||||||
|
value: '<',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'jiankuohao',
|
||||||
|
value: '<>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'andand',
|
||||||
|
value: '&&',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'huohuo',
|
||||||
|
value: '||',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fei',
|
||||||
|
value: '!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'and',
|
||||||
|
value: '&',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'huo',
|
||||||
|
value: '|',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bolang',
|
||||||
|
value: '~',
|
||||||
|
},
|
||||||
|
] as SymbolType[];
|
||||||
|
|
||||||
|
const _value = computed({
|
||||||
|
get: () => props.value || '',
|
||||||
|
set: (data: string) => {
|
||||||
|
emit('update:value', data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = true;
|
||||||
|
}, 100);
|
||||||
|
})
|
||||||
|
|
||||||
|
const addOperatorValue = (val: string) => {
|
||||||
|
editor.value?.insert(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullscreenClick = () => {
|
||||||
|
if (props.id) {
|
||||||
|
emit('change', 'advance');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
addOperatorValue
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.editor-box {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
|
||||||
|
.top {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid lightgray;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 60%;
|
||||||
|
margin: 0 5px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
height: 40px;
|
||||||
|
margin: 0 10px;
|
||||||
|
line-height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 10%;
|
||||||
|
margin: 0 5px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
color: rgba(#000, 0.5);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,161 @@
|
||||||
|
<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">
|
||||||
|
<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 'vue3-markdown-it'
|
||||||
|
|
||||||
|
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>
|
|
@ -0,0 +1,10 @@
|
||||||
|
import type { TreeNode } from '@/utils/tree';
|
||||||
|
|
||||||
|
interface OperatorItem extends TreeNode {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
description: string;
|
||||||
|
code: string;
|
||||||
|
children: OperatorItem[];
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<Editor key="simple" @change="change" v-model:value="_value" :id="id" />
|
||||||
|
<Advance v-if="ruleEditorStore.state.model === 'advance'" v-model:value="_value" :model="ruleEditorStore.state.model"
|
||||||
|
:virtualRule="virtualRule" :id="id" @change="change" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRuleEditorStore } from '@/store/ruleEditor'
|
||||||
|
import Editor from './Editor/index.vue'
|
||||||
|
import Advance from './Advance/index.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: string;
|
||||||
|
property?: string;
|
||||||
|
virtualRule?: any;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:value', data: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const _value = computed({
|
||||||
|
get: () => props.value,
|
||||||
|
set: (val: string) => {
|
||||||
|
emit('update:value', val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ruleEditorStore = useRuleEditorStore()
|
||||||
|
|
||||||
|
const change = (v: string) => {
|
||||||
|
ruleEditorStore.set('model', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
ruleEditorStore.set('property', props.property)
|
||||||
|
ruleEditorStore.set('code', props.value);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -1,65 +1,113 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="debug-container">
|
<div class="debug-container">
|
||||||
<div class="left">
|
<div class="top">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div>
|
<div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
属性赋值
|
属性赋值
|
||||||
<div class="description">请对上方规则使用的属性进行赋值</div>
|
<div class="description">
|
||||||
</div>
|
请对上方规则使用的属性进行赋值
|
||||||
<div v-if="!isBeginning && virtualRule?.type === 'window'" class="action" @click="runScriptAgain">
|
|
||||||
<a style="margin-left: 75px;">发送数据</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<j-table :columns="columns" :data-source="property" :pagination="false" bordered size="small">
|
</div>
|
||||||
|
<div class="top-bottom">
|
||||||
|
<j-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="property"
|
||||||
|
:pagination="false"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
:scroll="{ y: 200 }"
|
||||||
|
>
|
||||||
<template #bodyCell="{ column, record, index }">
|
<template #bodyCell="{ column, record, index }">
|
||||||
<template v-if="column.key === 'id'">
|
<template v-if="column.key === 'id'">
|
||||||
<j-auto-complete :options="options" v-model:value="record.id" size="small" style="width: 130px" />
|
<j-select
|
||||||
|
showSearch
|
||||||
|
:options="options"
|
||||||
|
v-model:value="record.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%; z-index: 1400 !important"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'current'">
|
<template v-if="column.key === 'current'">
|
||||||
<j-input v-model:value="record.current" size="small"></j-input>
|
<j-input
|
||||||
|
v-model:value="record.current"
|
||||||
|
size="small"
|
||||||
|
></j-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'last'">
|
<template v-if="column.key === 'last'">
|
||||||
<j-input v-model:value="record.last" size="small"></j-input>
|
<j-input
|
||||||
|
v-model:value="record.last"
|
||||||
|
size="small"
|
||||||
|
></j-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'action'">
|
<template v-if="column.key === 'action'">
|
||||||
<AIcon type="DeleteOutlined" @click="deleteItem(index)" />
|
<AIcon
|
||||||
|
type="DeleteOutlined"
|
||||||
|
@click="deleteItem(index)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</j-table>
|
</j-table>
|
||||||
<j-button type="dashed" block style="margin-top: 5px" @click="addItem">
|
<j-button
|
||||||
|
type="dashed"
|
||||||
|
block
|
||||||
|
style="margin-top: 5px"
|
||||||
|
@click="addItem"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<AIcon type="PlusOutlined" />
|
<AIcon type="PlusOutlined" />
|
||||||
</template>
|
</template>
|
||||||
添加条目
|
添加条目
|
||||||
</j-button>
|
</j-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div>运行结果</div>
|
<div>运行结果</div>
|
||||||
|
<div v-if="virtualRule?.script && !isBeginning">
|
||||||
|
正在运行......
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<div>
|
<div
|
||||||
|
v-if="!isBeginning && virtualRule?.type === 'window'"
|
||||||
|
class="action"
|
||||||
|
@click="runScriptAgain"
|
||||||
|
>
|
||||||
|
<a style="margin-left: 75px">发送数据</a>
|
||||||
|
</div>
|
||||||
|
<div v-if="virtualRule?.script">
|
||||||
<a v-if="isBeginning" @click="beginAction">
|
<a v-if="isBeginning" @click="beginAction">
|
||||||
开始运行
|
开始运行
|
||||||
</a>
|
</a>
|
||||||
<a v-else @click="stopAction">
|
<a v-else @click="stopAction"> 停止运行 </a>
|
||||||
停止运行
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a @click="clearAction">
|
<a @click="clearAction"> 清空 </a>
|
||||||
清空
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="log">
|
<div class="log">
|
||||||
<j-descriptions>
|
<j-descriptions>
|
||||||
<j-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')"
|
<j-descriptions-item
|
||||||
:key="item.time" :span="3">
|
v-for="(item, index) in ruleEditorStore.state.log"
|
||||||
|
:key="item.time"
|
||||||
|
:span="3"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<template v-if="!!runningState(index + 1, item._time)">
|
||||||
|
{{ runningState(index + 1, item._time) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>{{
|
||||||
|
moment(item.time).format('HH:mm:ss')
|
||||||
|
}}</template>
|
||||||
|
</template>
|
||||||
|
<div v-if="!!runningState(index + 1, item._time)">
|
||||||
|
{{ moment(item.time).format('HH:mm:ss') }}
|
||||||
|
</div>
|
||||||
<j-tooltip placement="top" :title="item.content">
|
<j-tooltip placement="top" :title="item.content">
|
||||||
{{ item.content }}
|
{{ item.content }}
|
||||||
</j-tooltip>
|
</j-tooltip>
|
||||||
|
@ -70,60 +118,69 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="Debug">
|
<script setup lang="ts" name="Debug">
|
||||||
import { PropType } from 'vue';
|
import { PropType, Ref } from 'vue';
|
||||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
|
||||||
import { useProductStore } from '@/store/product';
|
import { useProductStore } from '@/store/product';
|
||||||
import { message } from 'jetlinks-ui-components';
|
|
||||||
import { useRuleEditorStore } from '@/store/ruleEditor';
|
import { useRuleEditorStore } from '@/store/ruleEditor';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { getWebSocket } from '@/utils/websocket';
|
import { getWebSocket } from '@/utils/websocket';
|
||||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
import { PropertyMetadata } from '@/views/device/Product/typings';
|
||||||
|
import { onlyMessage } from '@/utils/comm';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
virtualRule: Object as PropType<Record<any, any>>,
|
virtualRule: Object as PropType<Record<any, any>>,
|
||||||
id: String,
|
id: String,
|
||||||
})
|
});
|
||||||
|
const emits = defineEmits(['success']);
|
||||||
|
|
||||||
const isBeginning = ref(true)
|
const isBeginning = ref(true);
|
||||||
|
|
||||||
type propertyType = {
|
type propertyType = {
|
||||||
id?: string,
|
id?: string;
|
||||||
current?: string,
|
current?: string;
|
||||||
last?: string
|
last?: string;
|
||||||
}
|
};
|
||||||
const property = ref<propertyType[]>([])
|
const property = ref<propertyType[]>([]);
|
||||||
|
|
||||||
const columns = [{
|
const columns = [
|
||||||
title: '属性ID',
|
{
|
||||||
|
title: '属性名称',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
key: 'id'
|
key: 'id',
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
title: '当前值',
|
title: '当前值',
|
||||||
dataIndex: 'current',
|
dataIndex: 'current',
|
||||||
key: 'current'
|
key: 'current',
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
title: '上一值',
|
title: '上一值',
|
||||||
dataIndex: 'last',
|
dataIndex: 'last',
|
||||||
key: 'last'
|
key: 'last',
|
||||||
}, {
|
},
|
||||||
title: '',
|
{
|
||||||
key: 'action'
|
title: '操作',
|
||||||
}]
|
key: 'action',
|
||||||
|
width: 50,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const addItem = () => {
|
const addItem = () => {
|
||||||
property.value.push({})
|
property.value.push({});
|
||||||
}
|
};
|
||||||
const deleteItem = (index: number) => {
|
const deleteItem = (index: number) => {
|
||||||
property.value.splice(index, 1)
|
property.value.splice(index, 1);
|
||||||
}
|
};
|
||||||
|
|
||||||
const ws = ref()
|
const ws = ref();
|
||||||
|
|
||||||
const virtualIdRef = ref(new Date().getTime());
|
const virtualIdRef = ref(new Date().getTime());
|
||||||
|
const medataSource = inject<Ref<any[]>>('_dataSource');
|
||||||
|
const productStore = useProductStore();
|
||||||
|
const ruleEditorStore = useRuleEditorStore();
|
||||||
|
|
||||||
|
const time = ref<number>(0);
|
||||||
|
const timer = ref<any>(null);
|
||||||
|
|
||||||
const productStore = useProductStore()
|
|
||||||
const ruleEditorStore = useRuleEditorStore()
|
|
||||||
const runScript = () => {
|
const runScript = () => {
|
||||||
const metadata = productStore.current.metadata || '{}';
|
const metadata = productStore.current.metadata || '{}';
|
||||||
const propertiesList = JSON.parse(metadata).properties || [];
|
const propertiesList = JSON.parse(metadata).properties || [];
|
||||||
|
@ -137,10 +194,12 @@ const runScript = () => {
|
||||||
}
|
}
|
||||||
if (!props.virtualRule?.script) {
|
if (!props.virtualRule?.script) {
|
||||||
isBeginning.value = true;
|
isBeginning.value = true;
|
||||||
message.warning('请编辑规则');
|
onlyMessage('请编辑规则', 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ws.value = getWebSocket(`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
|
||||||
|
ws.value = getWebSocket(
|
||||||
|
`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
||||||
'/virtual-property-debug',
|
'/virtual-property-debug',
|
||||||
{
|
{
|
||||||
virtualId: `${virtualIdRef.value}-virtual-id`,
|
virtualId: `${virtualIdRef.value}-virtual-id`,
|
||||||
|
@ -149,14 +208,29 @@ const runScript = () => {
|
||||||
...props.virtualRule,
|
...props.virtualRule,
|
||||||
},
|
},
|
||||||
properties: _properties || [],
|
properties: _properties || [],
|
||||||
})
|
},
|
||||||
ws.value.subscribe((data: any) => {
|
).subscribe((data: any) => {
|
||||||
ruleEditorStore.state.log.push({ time: new Date().getTime(), content: JSON.stringify(data.payload) });
|
ruleEditorStore.state.log.push({
|
||||||
|
time: new Date().getTime(),
|
||||||
|
content: JSON.stringify(data.payload),
|
||||||
|
_time: unref(time.value)
|
||||||
|
});
|
||||||
|
emits('success', false);
|
||||||
if (props.virtualRule?.type !== 'window') {
|
if (props.virtualRule?.type !== 'window') {
|
||||||
stopAction()
|
stopAction();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const runningState = (_index: number, _time: number) => {
|
||||||
|
if (props.virtualRule?.windowType === 'time') {
|
||||||
|
return `已运行${_time}秒`;
|
||||||
}
|
}
|
||||||
|
if (props.virtualRule?.windowType === 'num') {
|
||||||
|
return `第${_index}次运行`;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const wsAgain = ref<any>();
|
const wsAgain = ref<any>();
|
||||||
const runScriptAgain = async () => {
|
const runScriptAgain = async () => {
|
||||||
|
@ -170,7 +244,8 @@ const runScriptAgain = async () => {
|
||||||
return { ...item, type: _item?.valueType?.type };
|
return { ...item, type: _item?.valueType?.type };
|
||||||
});
|
});
|
||||||
|
|
||||||
wsAgain.value = getWebSocket(`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
wsAgain.value = getWebSocket(
|
||||||
|
`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
||||||
'/virtual-property-debug',
|
'/virtual-property-debug',
|
||||||
{
|
{
|
||||||
virtualId: `${virtualIdRef.value}-virtual-id`,
|
virtualId: `${virtualIdRef.value}-virtual-id`,
|
||||||
|
@ -179,55 +254,74 @@ const runScriptAgain = async () => {
|
||||||
...props.virtualRule,
|
...props.virtualRule,
|
||||||
},
|
},
|
||||||
properties: _properties || [],
|
properties: _properties || [],
|
||||||
})
|
},
|
||||||
wsAgain.value.subscribe((data: any) => { })
|
).subscribe((data: any) => {});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const getTime = () => {
|
||||||
|
time.value = 0;
|
||||||
|
timer.value = setInterval(() => {
|
||||||
|
time.value += 1;
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
const beginAction = () => {
|
const beginAction = () => {
|
||||||
isBeginning.value = false;
|
isBeginning.value = false;
|
||||||
runScript();
|
runScript();
|
||||||
}
|
getTime();
|
||||||
|
};
|
||||||
const stopAction = () => {
|
const stopAction = () => {
|
||||||
isBeginning.value = true;
|
isBeginning.value = true;
|
||||||
if (ws.value) {
|
if (ws.value) {
|
||||||
ws.value.unsubscribe?.();
|
ws.value.unsubscribe?.();
|
||||||
}
|
}
|
||||||
}
|
window.clearInterval(timer.value)
|
||||||
|
timer.value = null
|
||||||
|
};
|
||||||
const clearAction = () => {
|
const clearAction = () => {
|
||||||
ruleEditorStore.set('log', []);
|
ruleEditorStore.set('log', []);
|
||||||
}
|
};
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (ws.value) {
|
if (ws.value) {
|
||||||
ws.value.unsubscribe?.();
|
ws.value.unsubscribe?.();
|
||||||
}
|
}
|
||||||
clearAction()
|
clearAction();
|
||||||
})
|
window.clearInterval(timer.value)
|
||||||
|
timer.value = null
|
||||||
|
});
|
||||||
|
|
||||||
const options = ref<{ label: string, value: string }[]>()
|
const options = computed(() => {
|
||||||
const getProperty = () => {
|
return (medataSource.value || [])
|
||||||
const metadata = productStore.current.metadata || '{}';
|
.filter((p) => p.id !== props.id)
|
||||||
const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
|
.map((item) => ({
|
||||||
options.value = _p.filter((p) => p.id !== props.id).map((item) => ({
|
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id,
|
value: item.id,
|
||||||
}));
|
}));
|
||||||
}
|
});
|
||||||
getProperty()
|
|
||||||
|
// const getProperty = () => {
|
||||||
|
// // const metadata = productStore.current.metadata || '{}';
|
||||||
|
// // const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
|
||||||
|
// console.log(medataSource.value)
|
||||||
|
// options.value =
|
||||||
|
// };
|
||||||
|
// getProperty();
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.debug-container {
|
.debug-container {
|
||||||
display: flex;
|
// display: flex;
|
||||||
width: 100%;
|
// width: 100%;
|
||||||
height: 340px;
|
// height: 340px;
|
||||||
margin-top: 20px;
|
// margin-top: 20px;
|
||||||
|
|
||||||
.left {
|
.top {
|
||||||
flex: 1;
|
// min-width: 0;
|
||||||
min-width: 0;
|
// max-width: 550px;
|
||||||
max-width: 550px;
|
// overflow-y: auto;
|
||||||
overflow-y: auto;
|
height: 350px;
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -262,14 +356,14 @@ getProperty()
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-bottom {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.bottom {
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
border-left: none;
|
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -296,7 +390,7 @@ getProperty()
|
||||||
}
|
}
|
||||||
|
|
||||||
.log {
|
.log {
|
||||||
height: 290px;
|
height: 300px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,77 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="operator-box">
|
<div class="operator-box">
|
||||||
<j-input-search @search="search" allow-clear placeholder="搜索关键字" />
|
<div class="left">
|
||||||
|
<j-input-search
|
||||||
|
@search="search"
|
||||||
|
allow-clear
|
||||||
|
placeholder="搜索关键字"
|
||||||
|
/>
|
||||||
<div class="tree">
|
<div class="tree">
|
||||||
<j-tree @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent
|
<j-scrollbar>
|
||||||
:tree-data="data">
|
|
||||||
|
|
||||||
|
<j-tree
|
||||||
|
@select="selectTree"
|
||||||
|
:field-names="{ title: 'name', key: 'id' }"
|
||||||
|
auto-expand-parent
|
||||||
|
:tree-data="data"
|
||||||
|
>
|
||||||
<template #title="node">
|
<template #title="node">
|
||||||
<div class="node">
|
<div class="node">
|
||||||
<div style="max-width: 180px"><Ellipsis>{{ node.name }}</Ellipsis></div>
|
<div style="max-width: 160px">
|
||||||
<div :class="node.children?.length > 0 ? 'parent' : 'add'">
|
<Ellipsis>{{ node.name }}</Ellipsis>
|
||||||
<j-popover v-if="node.type === 'property'" placement="right" title="请选择使用值">
|
</div>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
node.children?.length > 0 ? 'parent' : 'add'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<j-popover
|
||||||
|
v-if="node.type === 'property'"
|
||||||
|
:overlayStyle="{
|
||||||
|
zIndex: 1200
|
||||||
|
}"
|
||||||
|
placement="right"
|
||||||
|
title="请选择使用值"
|
||||||
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<j-space direction="vertical">
|
<j-space direction="vertical">
|
||||||
<j-tooltip placement="right" title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值">
|
<j-tooltip
|
||||||
<j-button type="text" @click="recentClick(node)">
|
placement="right"
|
||||||
|
title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值"
|
||||||
|
>
|
||||||
|
<j-button
|
||||||
|
type="text"
|
||||||
|
@click="recentClick(node)"
|
||||||
|
>
|
||||||
$recent实时值
|
$recent实时值
|
||||||
</j-button>
|
</j-button>
|
||||||
</j-tooltip>
|
</j-tooltip>
|
||||||
<j-tooltip placement="right" title="实时值的上一有效值">
|
<j-tooltip
|
||||||
<j-button @click="lastClick(node)" type="text">
|
placement="right"
|
||||||
|
title="实时值的上一有效值"
|
||||||
|
>
|
||||||
|
<j-button
|
||||||
|
@click="lastClick(node)"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
上一值
|
上一值
|
||||||
</j-button>
|
</j-button>
|
||||||
</j-tooltip>
|
</j-tooltip>
|
||||||
</j-space>
|
</j-space>
|
||||||
</template>
|
</template>
|
||||||
<a>添加</a>
|
<a class="has-property">添加</a>
|
||||||
</j-popover>
|
</j-popover>
|
||||||
|
|
||||||
<a v-else @click="addClick(node)">
|
<a class="no-property" v-else @click.stop="addClick(node)"> 添加 </a>
|
||||||
添加
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</j-tree>
|
</j-tree>
|
||||||
|
</j-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="explain">
|
</div>
|
||||||
|
<div class="right">
|
||||||
<Markdown :source="item?.description || ''"></Markdown>
|
<Markdown :source="item?.description || ''"></Markdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,27 +79,31 @@
|
||||||
<script setup lang="ts" name="Operator">
|
<script setup lang="ts" name="Operator">
|
||||||
import { useProductStore } from '@/store/product';
|
import { useProductStore } from '@/store/product';
|
||||||
import type { OperatorItem } from './typings';
|
import type { OperatorItem } from './typings';
|
||||||
import { treeFilter } from '@/utils/tree'
|
import { treeFilter } from '@/utils/tree';
|
||||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
import { PropertyMetadata } from '@/views/device/Product/typings';
|
||||||
import { getOperator } from '@/api/device/product'
|
import { getOperator } from '@/api/device/product';
|
||||||
import Markdown from 'vue3-markdown-it'
|
import Markdown from 'vue3-markdown-it';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: String
|
id: String,
|
||||||
})
|
});
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'addOperatorValue', data: string): void;
|
(e: 'addOperatorValue', data: string): void;
|
||||||
}
|
}
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
const item = ref<Partial<OperatorItem>>()
|
const item = ref<Partial<OperatorItem>>();
|
||||||
const data = ref<OperatorItem[]>([])
|
const data = ref<OperatorItem[]>([]);
|
||||||
const dataRef = ref<OperatorItem[]>([])
|
const dataRef = ref<OperatorItem[]>([]);
|
||||||
|
|
||||||
const search = (value: string) => {
|
const search = (value: string) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
const nodes = treeFilter(dataRef.value, value, 'name') as OperatorItem[];
|
const nodes = treeFilter(
|
||||||
|
dataRef.value,
|
||||||
|
value,
|
||||||
|
'name',
|
||||||
|
) as OperatorItem[];
|
||||||
data.value = nodes;
|
data.value = nodes;
|
||||||
} else {
|
} else {
|
||||||
data.value = dataRef.value;
|
data.value = dataRef.value;
|
||||||
|
@ -71,23 +112,25 @@ const search = (value: string) => {
|
||||||
|
|
||||||
const selectTree = (k: any, info: any) => {
|
const selectTree = (k: any, info: any) => {
|
||||||
item.value = info.node as unknown as OperatorItem;
|
item.value = info.node as unknown as OperatorItem;
|
||||||
}
|
};
|
||||||
|
|
||||||
const recentClick = (node: OperatorItem) => {
|
const recentClick = (node: OperatorItem) => {
|
||||||
emit('addOperatorValue', `$recent("${node.id}")`)
|
emit('addOperatorValue', `$recent("${node.id}")`);
|
||||||
}
|
};
|
||||||
const lastClick = (node: OperatorItem) => {
|
const lastClick = (node: OperatorItem) => {
|
||||||
emit('addOperatorValue', `$lastState("${node.id}")`)
|
emit('addOperatorValue', `$lastState("${node.id}")`);
|
||||||
}
|
};
|
||||||
const addClick = (node: OperatorItem) => {
|
const addClick = (node: OperatorItem) => {
|
||||||
emit('addOperatorValue', node.code)
|
console.log(node)
|
||||||
}
|
emit('addOperatorValue', node.code);
|
||||||
|
};
|
||||||
|
|
||||||
const productStore = useProductStore()
|
const productStore = useProductStore();
|
||||||
|
|
||||||
const getData = async (id?: string) => {
|
const getData = async (id?: string) => {
|
||||||
const metadata = productStore.current.metadata || '{}';
|
const metadata = productStore.current.metadata || '{}';
|
||||||
const _properties = JSON.parse(metadata).properties || [] as PropertyMetadata[]
|
const _properties =
|
||||||
|
JSON.parse(metadata).properties || ([] as PropertyMetadata[]);
|
||||||
const properties = {
|
const properties = {
|
||||||
id: 'property',
|
id: 'property',
|
||||||
name: '属性',
|
name: '属性',
|
||||||
|
@ -112,12 +155,13 @@ const getData = async (id?: string) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(() => props.id,
|
watch(
|
||||||
|
() => props.id,
|
||||||
(val) => {
|
(val) => {
|
||||||
getData(val)
|
getData(val);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true },
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.border {
|
.border {
|
||||||
|
@ -128,29 +172,27 @@ watch(() => props.id,
|
||||||
|
|
||||||
.operator-box {
|
.operator-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
.explain {
|
.left,
|
||||||
.border;
|
.right {
|
||||||
|
width: 50%;
|
||||||
|
height: 350px;
|
||||||
|
border: 1px solid lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
padding: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
.tree {
|
.tree {
|
||||||
.border;
|
|
||||||
|
|
||||||
height: 350px;
|
height: 300px;
|
||||||
overflow-y: auto;
|
//overflow-y: auto;
|
||||||
|
|
||||||
.node {
|
.node {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 220px;
|
width: 190px;
|
||||||
|
|
||||||
//.add {
|
|
||||||
// display: none;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//&:hover .add {
|
|
||||||
// display: block;
|
|
||||||
//}
|
|
||||||
|
|
||||||
.parent {
|
.parent {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -158,4 +200,14 @@ watch(() => props.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
.rule-popover {
|
||||||
|
z-index: 1200;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,44 +1,133 @@
|
||||||
<template>
|
<template>
|
||||||
<Editor key="simple" @change="change" v-model:value="_value" :id="id" />
|
<j-modal
|
||||||
<Advance v-if="ruleEditorStore.state.model === 'advance'" v-model:value="_value" :model="ruleEditorStore.state.model"
|
:zIndex="1030"
|
||||||
:virtualRule="virtualRule" :id="id" @change="change" />
|
:mask-closable="false"
|
||||||
|
visible
|
||||||
|
width="70vw"
|
||||||
|
title="编辑规则"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
>
|
||||||
|
<div class="header" v-if="virtualRule?.windowType && virtualRule?.windowType !== 'undefined'">
|
||||||
|
<div class="header-item">
|
||||||
|
{{
|
||||||
|
virtualRule?.windowType === 'time' ? '时间窗口' : '频次窗口'
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="header-item">
|
||||||
|
<div>聚合函数: <span>{{ aggType || '--' }}</span></div>
|
||||||
|
<div>窗口长度(次):<span>{{ virtualRule?.window?.span || '--' }}</span></div>
|
||||||
|
<div>步长(次): <span>{{ virtualRule?.window?.every || '--' }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="left">
|
||||||
|
<div>
|
||||||
|
<Operator :id="id" @add-operator-value="addOperatorValue" />
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<Editor
|
||||||
|
ref="editor"
|
||||||
|
mode="advance"
|
||||||
|
key="advance"
|
||||||
|
v-model:value="_value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<Debug
|
||||||
|
:virtualRule="{
|
||||||
|
...virtualRule,
|
||||||
|
script: _value,
|
||||||
|
}"
|
||||||
|
:id="id"
|
||||||
|
@success="onSuccess"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<j-space>
|
||||||
|
<j-button @click="handleCancel">取消</j-button>
|
||||||
|
<j-button :disabled="_disabled" @click="handleOk" type="primary">确定</j-button>
|
||||||
|
</j-space>
|
||||||
|
</template>
|
||||||
|
</j-modal>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="FRuleEditor">
|
<script setup lang="ts" name="FRuleEditor">
|
||||||
import { useRuleEditorStore } from '@/store/ruleEditor'
|
import Editor from './Editor/index.vue';
|
||||||
import Editor from './Editor/index.vue'
|
import Debug from './Debug/index.vue';
|
||||||
import Advance from './Advance/index.vue'
|
import Operator from './Operator/index.vue';
|
||||||
|
|
||||||
interface Props {
|
|
||||||
value: string;
|
|
||||||
property?: string;
|
|
||||||
virtualRule?: any;
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'update:value', data: string): void;
|
(e: 'save', data: string | undefined): void;
|
||||||
|
(e: 'close'): void;
|
||||||
}
|
}
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
const emit = defineEmits<Emits>()
|
const props = defineProps({
|
||||||
|
value: String,
|
||||||
|
id: String,
|
||||||
|
virtualRule: Object,
|
||||||
|
aggList: Array
|
||||||
|
});
|
||||||
|
|
||||||
const _value = computed({
|
const _value = ref<string | undefined>(props.value);
|
||||||
get: () => props.value,
|
const _disabled = ref<boolean>(true);
|
||||||
set: (val: string) => {
|
|
||||||
emit('update:value', val)
|
const handleCancel = () => {
|
||||||
}
|
emit('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
emit('save', _value.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const aggType = computed(() => {
|
||||||
|
const _item: any = (props?.aggList || []).find((item: any) => {
|
||||||
|
return item?.value === props.virtualRule?.aggType
|
||||||
|
})
|
||||||
|
return _item?.label
|
||||||
})
|
})
|
||||||
|
|
||||||
const ruleEditorStore = useRuleEditorStore()
|
const editor = ref();
|
||||||
|
const addOperatorValue = (val: string) => {
|
||||||
|
editor.value.addOperatorValue(val);
|
||||||
|
};
|
||||||
|
|
||||||
const change = (v: string) => {
|
watch(() => _value.value, () => {
|
||||||
ruleEditorStore.set('model', v);
|
_disabled.value = true
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
ruleEditorStore.set('property', props.property)
|
|
||||||
ruleEditorStore.set('code', props.value);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onSuccess = (bool: boolean) => {
|
||||||
|
_disabled.value = bool;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped>
|
||||||
|
.header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.header-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
div span {
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
width: 40%;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 1px solid lightgray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,7 +4,7 @@
|
||||||
v-model:openKeys="state.openKeys"
|
v-model:openKeys="state.openKeys"
|
||||||
v-model:collapsed="state.collapsed"
|
v-model:collapsed="state.collapsed"
|
||||||
v-model:selectedKeys="state.selectedKeys"
|
v-model:selectedKeys="state.selectedKeys"
|
||||||
:headerHeight='60'
|
:headerHeight='layout.headerHeight'
|
||||||
:pure="state.pure"
|
:pure="state.pure"
|
||||||
:breadcrumb="{ routes: breadcrumb }"
|
:breadcrumb="{ routes: breadcrumb }"
|
||||||
@backClick='routerBack'
|
@backClick='routerBack'
|
||||||
|
@ -54,11 +54,11 @@ const route = useRoute();
|
||||||
const menu = useMenuStore();
|
const menu = useMenuStore();
|
||||||
|
|
||||||
const system = useSystem();
|
const system = useSystem();
|
||||||
const {configInfo} = storeToRefs(system);
|
const {configInfo,layout} = storeToRefs(system);
|
||||||
|
|
||||||
const layoutConf = reactive({
|
const layoutConf = reactive({
|
||||||
theme: DefaultSetting.layout.theme,
|
theme: DefaultSetting.layout.theme,
|
||||||
siderWidth: DefaultSetting.layout.siderWidth,
|
siderWidth: layout.value.siderWidth,
|
||||||
logo: DefaultSetting.layout.logo,
|
logo: DefaultSetting.layout.logo,
|
||||||
title: DefaultSetting.layout.title,
|
title: DefaultSetting.layout.title,
|
||||||
menuData: [...clearMenuItem(menu.siderMenus), AccountMenu],
|
menuData: [...clearMenuItem(menu.siderMenus), AccountMenu],
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class='full-page-warp' ref='fullPage' :style='{ minHeight: `calc(100vh - ${y + 24}px)`}'>
|
<div ref='fullPage' :style="{ minHeight: MinHeight}" class='full-page-warp' >
|
||||||
<div class="full-page-warp-content">
|
<div class="full-page-warp-content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,9 +9,21 @@
|
||||||
<script setup lang='ts' name='FullPage'>
|
<script setup lang='ts' name='FullPage'>
|
||||||
import { useElementBounding } from '@vueuse/core'
|
import { useElementBounding } from '@vueuse/core'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
extraHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const fullPage = ref(null)
|
const fullPage = ref(null)
|
||||||
const { y } = useElementBounding(fullPage)
|
const { y } = useElementBounding(fullPage)
|
||||||
|
|
||||||
|
const MinHeight = computed(() => {
|
||||||
|
const _y = (y.value < 0 ? 0 : y.value) + props.extraHeight
|
||||||
|
return `calc(100vh - ${_y + 24}px)`
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -3,18 +3,15 @@
|
||||||
<j-dropdown
|
<j-dropdown
|
||||||
v-model:visible="visible"
|
v-model:visible="visible"
|
||||||
:trigger="['click']"
|
:trigger="['click']"
|
||||||
|
:destroyPopupOnHide="true"
|
||||||
@visible-change="visibleChange"
|
@visible-change="visibleChange"
|
||||||
>
|
>
|
||||||
<!-- <div class="icon-content">
|
|
||||||
<AIcon type="BellOutlined" style="font-size: 16px" />
|
|
||||||
<span class="unread" v-show="total > 0">{{ total }}</span>
|
|
||||||
</div> -->
|
|
||||||
<j-badge :count="total" :offset="[3, -3]">
|
<j-badge :count="total" :offset="[3, -3]">
|
||||||
<AIcon type="BellOutlined" style="font-size: 16px" />
|
<AIcon type="BellOutlined" style="font-size: 16px" />
|
||||||
</j-badge>
|
</j-badge>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<div>
|
<div>
|
||||||
<NoticeInfo :data="list" @on-action="handleRead" />
|
<NoticeInfo :tabs="tabs" @action="handleRead" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</j-dropdown>
|
</j-dropdown>
|
||||||
|
@ -22,7 +19,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { getListByUnRead_api } from '@/api/account/notificationRecord';
|
import { getList_api } from '@/api/account/notificationRecord';
|
||||||
import NoticeInfo from './NoticeInfo.vue';
|
import NoticeInfo from './NoticeInfo.vue';
|
||||||
import { getWebSocket } from '@/utils/websocket';
|
import { getWebSocket } from '@/utils/websocket';
|
||||||
import { notification, Button } from 'jetlinks-ui-components';
|
import { notification, Button } from 'jetlinks-ui-components';
|
||||||
|
@ -30,13 +27,16 @@ import { changeStatus_api } from '@/api/account/notificationRecord';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
|
|
||||||
import { useMenuStore } from '@/store/menu';
|
import { useMenuStore } from '@/store/menu';
|
||||||
|
import { getAllNotice } from '@/api/account/center';
|
||||||
|
import { flatten } from 'lodash-es';
|
||||||
|
|
||||||
const { jumpPage } = useMenuStore();
|
const updateCount = computed(() => useUserInfo().alarmUpdateCount);
|
||||||
const updateCount = computed(() => useUserInfo().$state.alarmUpdateCount);
|
const menuStory = useMenuStore();
|
||||||
|
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const list = ref<any[]>([]);
|
// const list = ref<any[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const visible = ref(false);
|
||||||
|
|
||||||
const subscribeNotice = () => {
|
const subscribeNotice = () => {
|
||||||
getWebSocket('notification', '/notifications', {})
|
getWebSocket('notification', '/notifications', {})
|
||||||
|
@ -63,7 +63,6 @@ const subscribeNotice = () => {
|
||||||
)
|
)
|
||||||
,
|
,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// changeStatus_api('_read', [resp.id])
|
|
||||||
read('', resp);
|
read('', resp);
|
||||||
},
|
},
|
||||||
key: resp.payload.id,
|
key: resp.payload.id,
|
||||||
|
@ -93,14 +92,41 @@ const read = (type: string, data: any) => {
|
||||||
notification.close(data.payload.id);
|
notification.close(data.payload.id);
|
||||||
getList();
|
getList();
|
||||||
if (type !== '_read') {
|
if (type !== '_read') {
|
||||||
jumpPage('account/NotificationRecord', {
|
menuStory.routerPush('account/center', {
|
||||||
|
tabKey: 'StationMessage',
|
||||||
row: data.payload.detail,
|
row: data.payload.detail,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tab = [
|
||||||
|
{
|
||||||
|
key: 'alarm',
|
||||||
|
tab: '告警',
|
||||||
|
type: [
|
||||||
|
'alarm-product',
|
||||||
|
'alarm-device',
|
||||||
|
'alarm-other',
|
||||||
|
'alarm-org',
|
||||||
|
'alarm',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system-monitor',
|
||||||
|
tab: '系统监控',
|
||||||
|
type: ['system-event'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system-business',
|
||||||
|
tab: '业务监控',
|
||||||
|
type: ['device-transparent-codec'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 查询未读数量
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
|
if(tabs.value.length <= 0) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const params = {
|
const params = {
|
||||||
sorts: [{
|
sorts: [{
|
||||||
|
@ -111,34 +137,63 @@ const getList = () => {
|
||||||
{
|
{
|
||||||
terms: [
|
terms: [
|
||||||
{
|
{
|
||||||
type: 'or',
|
type: 'and',
|
||||||
value: 'unread',
|
value: 'unread',
|
||||||
termType: 'eq',
|
termType: 'eq',
|
||||||
column: 'state',
|
column: 'state',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
type: 'and',
|
||||||
|
value: flatten(tabs.value.map((i: any) => i?.type)),
|
||||||
|
termType: 'in',
|
||||||
|
column: 'topicProvider',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
getListByUnRead_api(params)
|
getList_api(params)
|
||||||
.then((resp: any) => {
|
.then((resp: any) => {
|
||||||
list.value = resp.result.data;
|
|
||||||
total.value = resp.result.total;
|
total.value = resp.result.total;
|
||||||
})
|
})
|
||||||
.finally(() => (loading.value = false));
|
.finally(() => (loading.value = false));
|
||||||
};
|
};
|
||||||
subscribeNotice();
|
|
||||||
getList();
|
|
||||||
watch(updateCount, () => getList());
|
|
||||||
const visibleChange = (bool: boolean) => {
|
const visibleChange = (bool: boolean) => {
|
||||||
bool && getList();
|
bool && getList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const visible = ref(false);
|
|
||||||
const handleRead = () => {
|
const handleRead = () => {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
getList();
|
getList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(updateCount, () => getList());
|
||||||
|
|
||||||
|
const tabs = ref<any>([]);
|
||||||
|
|
||||||
|
const queryTypeList = async () => {
|
||||||
|
const resp: any = await getAllNotice();
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const provider = resp.result.map((i: any) => i.provider) || [];
|
||||||
|
const arr = tab.filter((item: any) => {
|
||||||
|
return item.type.some((i: any) => provider.includes(i))
|
||||||
|
});
|
||||||
|
tabs.value = arr;
|
||||||
|
if(arr.length > 0) {
|
||||||
|
subscribeNotice();
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queryTypeList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -1,57 +1,156 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="notice-info-container">
|
<div class="notice-info-container">
|
||||||
<j-tabs :activeKey="'default'">
|
<j-tabs
|
||||||
<j-tab-pane key="default" tab="未读消息">
|
v-model:activeKey="activeKey"
|
||||||
<div class="no-data" v-if="props.data.length === 0">
|
:destroyInactiveTabPane="true"
|
||||||
<img
|
@change="onChange"
|
||||||
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
|
v-if="tabs.length"
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="content">
|
|
||||||
<j-scrollbar class="list" max-height="400">
|
|
||||||
<div
|
|
||||||
class="list-item"
|
|
||||||
v-for="item in props.data"
|
|
||||||
@click.stop="read(item.id)"
|
|
||||||
>
|
>
|
||||||
<h5>{{ item.topicName }}</h5>
|
<j-tab-pane v-for="item in tabs" :key="item.key">
|
||||||
<p>{{ item.message }}</p>
|
<template #tab>
|
||||||
|
<NoticeTab
|
||||||
|
:refresh="refreshObj[item.key]"
|
||||||
|
:tab="item?.tab"
|
||||||
|
:type="item.type"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<j-spin :spinning="loading">
|
||||||
|
<div class="content">
|
||||||
|
<j-scrollbar class="list" :max-height="450" v-if="list.length">
|
||||||
|
<template v-for="i in list" :key="i.id">
|
||||||
|
<NoticeItem
|
||||||
|
:data="i"
|
||||||
|
:type="item.key"
|
||||||
|
@action="emits('action')"
|
||||||
|
@refresh="onRefresh(item.key)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-if="list.length < 12"
|
||||||
|
style="
|
||||||
|
color: #666666;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
这是最后一条数据了
|
||||||
</div>
|
</div>
|
||||||
</j-scrollbar>
|
</j-scrollbar>
|
||||||
|
<div class="no-data" v-else>
|
||||||
|
<j-empty />
|
||||||
|
</div>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<span @click="read()">当前标记为已读</span>
|
<j-button type="link" @click="onMore(item.key)"
|
||||||
<span @click="jumpPage('account/NotificationRecord')"
|
>查看更多</j-button
|
||||||
>查看更多</span
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</j-spin>
|
||||||
</j-tab-pane>
|
</j-tab-pane>
|
||||||
</j-tabs>
|
</j-tabs>
|
||||||
|
<div class="no-data" v-else>
|
||||||
|
<j-empty />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { changeStatus_api } from '@/api/account/notificationRecord';
|
import { getList_api } from '@/api/account/notificationRecord';
|
||||||
import { useMenuStore } from '@/store/menu';
|
import { useMenuStore } from '@/store/menu';
|
||||||
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
import NoticeItem from './NoticeItem.vue';
|
||||||
|
import NoticeTab from './NoticeTab.vue';
|
||||||
|
|
||||||
const emits = defineEmits(['onAction']);
|
const emits = defineEmits(['action']);
|
||||||
const props = defineProps<{
|
|
||||||
data: any[];
|
|
||||||
}>();
|
|
||||||
const { jumpPage } = useMenuStore();
|
|
||||||
|
|
||||||
const read = (id?: string) => {
|
type DataType = 'alarm' | 'system-monitor' | 'system-business';
|
||||||
const ids = id ? [id] : props.data.map((item) => item.id);
|
|
||||||
changeStatus_api('_read', ids).then((resp: any) => {
|
const refreshObj = ref({
|
||||||
if (resp.status === 200) {
|
'alarm': true,
|
||||||
jumpPage('account/NotificationRecord', {
|
'system-monitor': true,
|
||||||
row: props.data.find((f: any) => f.id === id),
|
'system-business': true,
|
||||||
});
|
});
|
||||||
emits('onAction');
|
|
||||||
|
const props = defineProps({
|
||||||
|
tabs: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
const list = ref<any[]>([]);
|
||||||
|
const activeKey = ref<DataType>(props.tabs?.[0]?.key || 'alarm');
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const userInfo = useUserInfo();
|
||||||
|
|
||||||
|
const getData = (type: string[]) => {
|
||||||
|
loading.value = true;
|
||||||
|
const params = {
|
||||||
|
sorts: [
|
||||||
|
{
|
||||||
|
name: 'notifyTime',
|
||||||
|
order: 'desc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageSize: 12,
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
type: 'or',
|
||||||
|
value: type,
|
||||||
|
termType: 'in',
|
||||||
|
column: 'topicProvider',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
getList_api(params)
|
||||||
|
.then((resp: any) => {
|
||||||
|
total.value = resp.result.total;
|
||||||
|
list.value = resp.result?.data || [];
|
||||||
|
})
|
||||||
|
.finally(() => (loading.value = false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = (_key: string) => {
|
||||||
|
const type = props.tabs.find((item: any) => item.key === _key)?.type || [];
|
||||||
|
getData(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
onChange(props.tabs?.[0]?.key || "alarm");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onRefresh = (id: string) => {
|
||||||
|
const flag = cloneDeep(refreshObj.value[id]);
|
||||||
|
refreshObj.value = {
|
||||||
|
...refreshObj.value,
|
||||||
|
[id]: !flag,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMore = (key: string) => {
|
||||||
|
// 判断当前是否为/account/center
|
||||||
|
if (route.path === '/account/center') {
|
||||||
|
userInfo.tabKey = 'StationMessage';
|
||||||
|
userInfo.other.tabKey = key;
|
||||||
|
} else {
|
||||||
|
menuStory.routerPush('account/center', {
|
||||||
|
tabKey: 'StationMessage',
|
||||||
|
other: {
|
||||||
|
tabKey: key,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
emits('action');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -81,7 +180,7 @@ const read = (id?: string) => {
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
.list {
|
.list {
|
||||||
max-height: 400px;
|
max-height: 450px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -89,41 +188,12 @@ const read = (id?: string) => {
|
||||||
//隐藏或取消滚动条
|
//隐藏或取消滚动条
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item {
|
|
||||||
padding: 12px 24px;
|
|
||||||
list-style: none;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
cursor: pointer;
|
|
||||||
h5 {
|
|
||||||
color: rgba(0, 0, 0, 0.85);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(0, 0, 0, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f0f5ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.btns {
|
.btns {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
line-height: 46px;
|
justify-content: center;
|
||||||
span {
|
align-items: center;
|
||||||
display: block;
|
|
||||||
width: 50%;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
border-right: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
<template>
|
||||||
|
<div class="list-items">
|
||||||
|
<div
|
||||||
|
class="list-item"
|
||||||
|
@click="onMove"
|
||||||
|
:style="{
|
||||||
|
transform: `translate(${num}px, 0)`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="list-item-left">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">
|
||||||
|
<div>
|
||||||
|
{{ props.data?.topicName }}
|
||||||
|
</div>
|
||||||
|
<span :style="{color: state === 'unread' ? 'red' : '#AAAAAA'}">{{ state === 'unread' ? '未读' : '已读' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="time">
|
||||||
|
{{
|
||||||
|
dayjs(props.data?.notifyTime).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<j-ellipsis :lineClamp="2">
|
||||||
|
{{ props.data?.message }}
|
||||||
|
</j-ellipsis>
|
||||||
|
</div>
|
||||||
|
<div class="list-item-right">
|
||||||
|
<j-button style="margin-bottom: 5px;" class="btn" @click.stop="detail">查看详情</j-button>
|
||||||
|
<j-button class="btn" v-if="state === 'unread'" @click.stop="read('_read')">标为已读</j-button>
|
||||||
|
<j-button class="btn" v-else @click.stop="read('_unread')">标为未读</j-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { changeStatus_api } from '@/api/account/notificationRecord';
|
||||||
|
import { useMenuStore } from '@/store/menu';
|
||||||
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
|
import { onlyMessage } from '@/utils/comm';
|
||||||
|
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const userInfo = useUserInfo();
|
||||||
|
|
||||||
|
const emits = defineEmits(['action', 'refresh']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: "alarm"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const num = ref<-100 | 0>(0);
|
||||||
|
|
||||||
|
const state = ref(props.data.state?.value)
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
state.value = props.data.state?.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const onMove = () => {
|
||||||
|
num.value = num.value === 0 ? -100 : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const detail = () => {
|
||||||
|
// 判断当前是否为/account/center
|
||||||
|
if (route.path === '/account/center') {
|
||||||
|
userInfo.tabKey = 'StationMessage';
|
||||||
|
userInfo.messageInfo = props.data;
|
||||||
|
userInfo.other.tabKey = props.type;
|
||||||
|
} else {
|
||||||
|
menuStory.routerPush('account/center', {
|
||||||
|
row: props.data,
|
||||||
|
tabKey: 'StationMessage',
|
||||||
|
other: {
|
||||||
|
tabKey: props.type
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
emits('action');
|
||||||
|
};
|
||||||
|
|
||||||
|
const read = (type: '_read' | '_unread') => {
|
||||||
|
changeStatus_api(type, [props.data.id]).then((resp: any) => {
|
||||||
|
if (resp.status === 200) {
|
||||||
|
if(type === '_read') {
|
||||||
|
userInfo.alarmUpdateCount -= 1;
|
||||||
|
} else {
|
||||||
|
userInfo.alarmUpdateCount += 1;
|
||||||
|
}
|
||||||
|
num.value = 0;
|
||||||
|
state.value = type === '_read' ? 'read' : 'unread'
|
||||||
|
onlyMessage('操作成功!');
|
||||||
|
emits('refresh')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.list-items {
|
||||||
|
width: 312px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
margin: 0 24px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
|
||||||
|
// &:hover {
|
||||||
|
// background-color: #F9FAFF;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
.list-item {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
width: 412px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
gap: 24px;
|
||||||
|
|
||||||
|
.list-item-left {
|
||||||
|
padding: 12px 0;
|
||||||
|
width: 312px;
|
||||||
|
// height: 100px;
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: calc(100% - 120px);
|
||||||
|
|
||||||
|
div {
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 10px;
|
||||||
|
max-width: calc(100% - 40px);
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: red;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-right {
|
||||||
|
width: 100px;
|
||||||
|
padding: 5px 12px 5px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border: none;
|
||||||
|
background-color: #F1F4FF;
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<j-badge :count="total" :offset="[3, -3]">
|
||||||
|
{{ tab }}
|
||||||
|
</j-badge>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getList_api } from '@/api/account/notificationRecord';
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
tab: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
refresh: {
|
||||||
|
type: Boolean
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const total = ref<number>(0);
|
||||||
|
|
||||||
|
const getData = (type: string[]) => {
|
||||||
|
const params = {
|
||||||
|
sorts: [
|
||||||
|
{
|
||||||
|
name: 'notifyTime',
|
||||||
|
order: 'desc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
type: 'and',
|
||||||
|
value: type,
|
||||||
|
termType: 'in',
|
||||||
|
column: 'topicProvider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'and',
|
||||||
|
value: 'unread',
|
||||||
|
termType: 'eq',
|
||||||
|
column: 'state',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
getList_api(params).then((resp: any) => {
|
||||||
|
total.value = resp.result.total;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.refresh,
|
||||||
|
() => {
|
||||||
|
getData(props.type);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
|
@ -1,13 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<j-dropdown placement="bottomRight">
|
<j-dropdown placement="bottomRight">
|
||||||
<div style="cursor: pointer;height: 100%;">
|
<div style="cursor: pointer;height: 100%;white-space: nowrap;overflow: hidden;text-overflow:ellipsis; max-width: 170px;" >
|
||||||
<img
|
<j-avatar
|
||||||
:src="userInfo.avatar"
|
:src="userInfo.userInfos?.avatar"
|
||||||
alt=""
|
alt=""
|
||||||
style="width: 24px; margin-right: 12px"
|
:size="24"
|
||||||
|
style="margin-right: 12px"
|
||||||
/>
|
/>
|
||||||
<span>{{ userInfo.name }}</span>
|
<span>{{ userInfo.userInfos?.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<j-menu>
|
<j-menu>
|
||||||
|
@ -32,8 +33,7 @@ import { LoginPath } from '@/router/menu'
|
||||||
|
|
||||||
const {push} = useRouter();
|
const {push} = useRouter();
|
||||||
|
|
||||||
const userInfo = useUserInfo().$state.userInfos as any;
|
const userInfo = useUserInfo() as any;
|
||||||
|
|
||||||
|
|
||||||
const logOut = () => {
|
const logOut = () => {
|
||||||
loginout_api().then(() => {
|
loginout_api().then(() => {
|
||||||
|
@ -43,4 +43,5 @@ const logOut = () => {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<template>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-btn" v-if="pageIndex > 0">
|
||||||
|
<div class="box-item-action" @click="onLeft">
|
||||||
|
<AIcon type="LeftOutlined" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-item" v-for="item in getData" :key="item.id">
|
||||||
|
<slot name="card" v-bind="item"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="box-item">
|
||||||
|
<slot name="add"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="box-btn" v-if="(pageIndex + 1) * showLength < data.length">
|
||||||
|
<div class="box-item-action" @click="onRight">
|
||||||
|
<AIcon type="RightOutlined" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array as PropType<any[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
showLength: {
|
||||||
|
type: Number,
|
||||||
|
default: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageIndex = ref<number>(0);
|
||||||
|
|
||||||
|
const getData = computed(() => {
|
||||||
|
const start = pageIndex.value >= 0 ? pageIndex.value * props.showLength : 0;
|
||||||
|
const end =
|
||||||
|
(pageIndex.value + 1) * props.showLength < props.data.length
|
||||||
|
? props.showLength * (pageIndex.value + 1)
|
||||||
|
: props.data.length;
|
||||||
|
return props.data.slice(start, end);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onRight = () => {
|
||||||
|
const flag = pageIndex.value + 1;
|
||||||
|
if (flag < props.data.length) {
|
||||||
|
pageIndex.value = flag;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLeft = () => {
|
||||||
|
const flag = pageIndex.value - 1;
|
||||||
|
if (flag >= 0) {
|
||||||
|
pageIndex.value -= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5px 0;
|
||||||
|
.box-item {
|
||||||
|
margin: 0 6px;
|
||||||
|
max-width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-btn {
|
||||||
|
margin-right: 12px;
|
||||||
|
.box-item-action {
|
||||||
|
width: 12px;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
padding: 15px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666666;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background-color: #EFF2FE;
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -8,18 +8,32 @@
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div style="max-width: 400px;" class="ant-form-vertical">
|
<div style="max-width: 400px;" class="ant-form-vertical">
|
||||||
<j-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name">
|
<j-form-item v-for="item in config.properties" :key="item.property" :name="name.concat([item.property])" :label="item.name">
|
||||||
<template v-if='item.type?.type === "string"'>
|
<!-- <template v-if='item.type?.type === "string"'>
|
||||||
<j-input v-model:value='value[item.property]' size="small" :placeholder="`请输入${item.name}`"/>
|
<j-input v-model:value='value[item.property]' size="small" :placeholder="`请输入${item.name}`"/>
|
||||||
</template>
|
</template>
|
||||||
<j-select v-else v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
|
<template v-else-if='item.type?.type === "int"'>
|
||||||
|
<j-input-number style="width: 100%;" v-model:value='value[item.property]' size="small" :placeholder="`请输入${item.name}`"/>
|
||||||
|
</template>
|
||||||
|
<j-select v-else :mode="item.type?.multi ? 'multiple' : ''" v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
|
||||||
label: e.text,
|
label: e.text,
|
||||||
value: e.value,
|
value: e.value,
|
||||||
}))" size="small" :placeholder="`请输入${item.name}`"></j-select>
|
}))" size="small" :placeholder="`请输入${item.name}`"></j-select> -->
|
||||||
|
<ValueItem
|
||||||
|
v-model:modelValue="value[item.property]"
|
||||||
|
:itemType="item.type?.type"
|
||||||
|
:mode="item?.type?.multi ? 'multiple' : ''"
|
||||||
|
:options="
|
||||||
|
item.type?.elements?.map(e => ({
|
||||||
|
label: e.text,
|
||||||
|
value: e.value,
|
||||||
|
}))
|
||||||
|
"
|
||||||
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
{{ config.name || 存储配置 }}
|
{{ config.name || '存储配置' }}
|
||||||
<AIcon type="EditOutlined" class="item-icon" />
|
<AIcon type="EditOutlined" class="item-icon" />
|
||||||
</j-popover>
|
</j-popover>
|
||||||
</j-button>
|
</j-button>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<j-modal
|
||||||
|
:mask-closable="false"
|
||||||
|
visible width="70vw"
|
||||||
|
title="设置属性规则"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@ok="handleOk"
|
||||||
|
>
|
||||||
|
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="RuleModal">
|
||||||
|
const handleCancel = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,161 @@
|
||||||
|
<template>
|
||||||
|
<j-popconfirm-modal @confirm="confirm" bodyStyle="width: 450px;height: 300px">
|
||||||
|
<template #content>
|
||||||
|
<j-scrollbar>
|
||||||
|
<j-form ref="formRef" layout="vertical" :model="formData">
|
||||||
|
<ReadType v-model:value="formData.type" :disabled="true" />
|
||||||
|
<j-form-item name="promi">
|
||||||
|
<template #label>
|
||||||
|
触发属性
|
||||||
|
<j-popover>
|
||||||
|
<template #title>
|
||||||
|
<div>选择当前产品物模型下的属性作为触发属性</div>
|
||||||
|
<div>任意属性值更新时将触发下方计算规则</div>
|
||||||
|
</template>
|
||||||
|
<AIcon style="padding-left: 4px" type="icon-bangzhu" />
|
||||||
|
</j-popover>
|
||||||
|
</template>
|
||||||
|
<j-select />
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item label="计算规则">
|
||||||
|
<div class="rule-add" @click="showRuleWindow">
|
||||||
|
编辑规则
|
||||||
|
</div>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item label="窗口" :name="['virtualRule', 'windowType']" required>
|
||||||
|
<j-select
|
||||||
|
v-model:value="formData.virtualRule.windowType"
|
||||||
|
:options="[
|
||||||
|
{ label: '无', value: 'undefined' },
|
||||||
|
{ label: '时间窗口', value: 'time' },
|
||||||
|
{ label: '频次窗口', value: 'num' },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
<template v-if="showWindow">
|
||||||
|
<j-form-item label="聚合函数" :name="['virtualRule', 'aggType']">
|
||||||
|
<j-select
|
||||||
|
v-model:value="formData.virtualRule.aggType"
|
||||||
|
:options="[
|
||||||
|
{ label: '时间窗口', value: 'time' },
|
||||||
|
{ label: '频次窗口', value: 'num' },
|
||||||
|
]"
|
||||||
|
placeholder="请选择聚合函数"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item :name="['virtualRule', 'window', 'span']">
|
||||||
|
<template #label>
|
||||||
|
窗口长度({{ formData.virtualRule.aggType === 'num' ? '次' : 's' }})
|
||||||
|
</template>
|
||||||
|
<j-input-number v-model:value="formData.virtualRule.window.span" style="width: 100%" />
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item :name="['virtualRule', 'window', 'every']">
|
||||||
|
<template #label>
|
||||||
|
步长({{ formData.virtualRule.aggType === 'num' ? '次' : 's' }})
|
||||||
|
</template>
|
||||||
|
<j-input-number v-model:value="formData.virtualRule.window.every" style="width: 100%" />
|
||||||
|
</j-form-item>
|
||||||
|
</template>
|
||||||
|
</j-form>
|
||||||
|
</j-scrollbar>
|
||||||
|
</template>
|
||||||
|
<j-button style="padding: 4px 8px;">
|
||||||
|
<AIcon type="EditOutlined" />
|
||||||
|
</j-button>
|
||||||
|
</j-popconfirm-modal>
|
||||||
|
<Modal
|
||||||
|
v-if="visible"
|
||||||
|
@ok="ruleOk"
|
||||||
|
@cancel="ruleCancel"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="Rule">
|
||||||
|
import { ReadType } from '../components'
|
||||||
|
import Modal from './Modal.vue'
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(e: 'update:value', data: Record<string, any>): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
const formRef = ref<any>(null)
|
||||||
|
const visible = ref(false)
|
||||||
|
|
||||||
|
const formData = reactive<{
|
||||||
|
type?: string[]
|
||||||
|
virtualRule: Record<string, any>
|
||||||
|
}>({
|
||||||
|
type: ['report'],
|
||||||
|
virtualRule: {
|
||||||
|
windowType: 'undefined',
|
||||||
|
aggType: undefined,
|
||||||
|
isVirtualRule: false,
|
||||||
|
type: undefined,
|
||||||
|
window: {
|
||||||
|
every: undefined,
|
||||||
|
span: undefined
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const showWindow = computed(() => {
|
||||||
|
const hasWindowType = formData.virtualRule.windowType !== 'undefined'
|
||||||
|
|
||||||
|
if (!hasWindowType) {
|
||||||
|
formData.virtualRule.window = {
|
||||||
|
every: undefined,
|
||||||
|
span: undefined
|
||||||
|
}
|
||||||
|
formData.virtualRule.aggType = undefined
|
||||||
|
}
|
||||||
|
formData.virtualRule.isVirtualRule = hasWindowType
|
||||||
|
formData.virtualRule.type = hasWindowType ? 'window' : 'script'
|
||||||
|
return hasWindowType
|
||||||
|
})
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const data = await formRef.value!.validate().catch(() => {
|
||||||
|
reject()
|
||||||
|
})
|
||||||
|
if (data) {
|
||||||
|
emit('update:value', formData)
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showRuleWindow = () => {
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleCancel = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleOk = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, () => {
|
||||||
|
Object.assign(formData, props.value)
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rule-add {
|
||||||
|
padding: 8px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
border:1px solid rgba(0,0,0,.3);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Rule from './Rule.vue'
|
||||||
|
|
||||||
|
export default Rule
|
|
@ -24,12 +24,12 @@
|
||||||
<j-form-item :label="spanLabel" :name="name.concat(['window', 'span'])" :rules="[
|
<j-form-item :label="spanLabel" :name="name.concat(['window', 'span'])" :rules="[
|
||||||
{ required: true, message: '请输入窗口长度' },
|
{ required: true, message: '请输入窗口长度' },
|
||||||
]">
|
]">
|
||||||
<j-input-number v-model:value="value.window.span" size="small" style="width: 100%;"></j-input-number>
|
<j-input-number stringMode v-model:value="value.window.span" size="small" style="width: 100%;"></j-input-number>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
<j-form-item :label="everyLabel" :name="name.concat(['window', 'every'])" :rules="[
|
<j-form-item :label="everyLabel" :name="name.concat(['window', 'every'])" :rules="[
|
||||||
{ required: true, message: '请输入步长' },
|
{ required: true, message: '请输入步长' },
|
||||||
]">
|
]">
|
||||||
<j-input-number v-model:value="value.window.every" size="small" style="width: 100%;"></j-input-number>
|
<j-input-number stringMode :maxlength="10" v-model:value="value.window.every" size="small" style="width: 100%;"></j-input-number>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
<template>
|
||||||
|
<j-form-item name="type" label="读写类型" required>
|
||||||
|
<j-select
|
||||||
|
v-model:value="myValue"
|
||||||
|
mode="multiple"
|
||||||
|
:options="options"
|
||||||
|
:disabled="disabled"
|
||||||
|
placeholder="请选择读写类型"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="ReadType">
|
||||||
|
|
||||||
|
import type {PropType} from "vue";
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(e: 'update:value', data: Array<string>): void
|
||||||
|
(e: 'change', data: Array<string>): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Array as PropType<Array<string>>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array as PropType<Array<{label: string, value: string}>>,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
|
const myValue = ref<Array<string>>([])
|
||||||
|
|
||||||
|
const onChange = (keys: Array<string>) =>{
|
||||||
|
myValue.value = keys
|
||||||
|
emit('update:value', keys)
|
||||||
|
emit('change', keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, () => {
|
||||||
|
myValue.value = props.value
|
||||||
|
}, { immediate: true})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as ReadType } from './ReadType.vue'
|
|
@ -30,9 +30,21 @@
|
||||||
</j-space>
|
</j-space>
|
||||||
<div style="margin-top: 20px" v-if="importLoading">
|
<div style="margin-top: 20px" v-if="importLoading">
|
||||||
<j-badge v-if="flag" status="processing" text="进行中" />
|
<j-badge v-if="flag" status="processing" text="进行中" />
|
||||||
<j-badge v-else status="success" text="已完成" />
|
<div v-else>
|
||||||
|
<div>
|
||||||
|
<j-space size="large">
|
||||||
|
<j-badge status="success" text="已完成" />
|
||||||
<span>总数量:{{ count }}</span>
|
<span>总数量:{{ count }}</span>
|
||||||
<p style="color: red">{{ errMessage }}</p>
|
</j-space>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<j-space size="large">
|
||||||
|
<j-badge status="error" text="失败 " />
|
||||||
|
<span>总数量:{{ failCount }}</span>
|
||||||
|
<a :href="detailFile" v-if="failCount">下载</a>
|
||||||
|
</j-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -46,7 +58,6 @@ import {
|
||||||
templateDownload,
|
templateDownload,
|
||||||
} from '@/api/device/instance';
|
} from '@/api/device/instance';
|
||||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||||
import { message } from 'jetlinks-ui-components';
|
|
||||||
|
|
||||||
type Emits = {
|
type Emits = {
|
||||||
(e: 'update:modelValue', data: string[]): void;
|
(e: 'update:modelValue', data: string[]): void;
|
||||||
|
@ -86,7 +97,9 @@ const props = defineProps({
|
||||||
const importLoading = ref<boolean>(false);
|
const importLoading = ref<boolean>(false);
|
||||||
const flag = ref<boolean>(false);
|
const flag = ref<boolean>(false);
|
||||||
const count = ref<number>(0);
|
const count = ref<number>(0);
|
||||||
|
const failCount = ref(0);
|
||||||
const errMessage = ref<string>('');
|
const errMessage = ref<string>('');
|
||||||
|
const detailFile = ref('');
|
||||||
|
|
||||||
const downFile = async (type: string) => {
|
const downFile = async (type: string) => {
|
||||||
const res: any = await templateDownload(props.product, type);
|
const res: any = await templateDownload(props.product, type);
|
||||||
|
@ -113,6 +126,7 @@ const beforeUpload = (_file: any) => {
|
||||||
const submitData = async (fileUrl: string) => {
|
const submitData = async (fileUrl: string) => {
|
||||||
if (!!fileUrl) {
|
if (!!fileUrl) {
|
||||||
count.value = 0;
|
count.value = 0;
|
||||||
|
failCount.value = 0;
|
||||||
errMessage.value = '';
|
errMessage.value = '';
|
||||||
flag.value = true;
|
flag.value = true;
|
||||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||||
|
@ -127,8 +141,11 @@ const submitData = async (fileUrl: string) => {
|
||||||
const temp = res.result.total;
|
const temp = res.result.total;
|
||||||
dt += temp;
|
dt += temp;
|
||||||
count.value = dt;
|
count.value = dt;
|
||||||
} else {
|
} else if(!res.success && !res.detailFile) {
|
||||||
|
failCount.value++;
|
||||||
errMessage.value = res.message || '失败';
|
errMessage.value = res.message || '失败';
|
||||||
|
} else if(res.detailFile) {
|
||||||
|
detailFile.value = res.detailFile;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
source.onerror = (e: { status: number }) => {
|
source.onerror = (e: { status: number }) => {
|
||||||
|
@ -138,7 +155,7 @@ const submitData = async (fileUrl: string) => {
|
||||||
};
|
};
|
||||||
source.onopen = () => {};
|
source.onopen = () => {};
|
||||||
} else {
|
} else {
|
||||||
message.error('请先上传文件');
|
onlyMessage('请先上传文件', 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ const props = defineProps({
|
||||||
type: Object as PropType<PopconfirmProps>,
|
type: Object as PropType<PopconfirmProps>,
|
||||||
},
|
},
|
||||||
hasPermission: {
|
hasPermission: {
|
||||||
type: String || Array || Boolean,
|
type: [String , Array, Boolean],
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
type: Object as PropType<CSSProperties>
|
type: Object as PropType<CSSProperties>
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
@click="playerActive = index"
|
@click="playerActive = index"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="media-btn-refresh"
|
class="media-btn-refresh refreshBtn"
|
||||||
:style="{
|
:style="{
|
||||||
display: item.url ? 'block' : 'none',
|
display: item.url ? 'block' : 'none',
|
||||||
}"
|
}"
|
||||||
|
@ -149,9 +149,9 @@ import {
|
||||||
getSearchHistory,
|
getSearchHistory,
|
||||||
saveSearchHistory,
|
saveSearchHistory,
|
||||||
} from '@/api/comm';
|
} from '@/api/comm';
|
||||||
import { message } from 'jetlinks-ui-components';
|
|
||||||
import LivePlayer from '@/components/Player/index.vue';
|
import LivePlayer from '@/components/Player/index.vue';
|
||||||
import MediaTool from '@/components/Player/mediaTool.vue';
|
import MediaTool from '@/components/Player/mediaTool.vue';
|
||||||
|
import { onlyMessage } from '@/utils/comm';
|
||||||
|
|
||||||
type Player = {
|
type Player = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -347,10 +347,10 @@ const saveHistory = async () => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
getHistory();
|
getHistory();
|
||||||
message.success('保存成功');
|
onlyMessage('保存成功');
|
||||||
formRef.value.resetFields();
|
formRef.value.resetFields();
|
||||||
} else {
|
} else {
|
||||||
message.error('保存失败');
|
onlyMessage('保存失败', 'error');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
|
@ -458,4 +458,16 @@ defineExpose({
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './index.less';
|
@import './index.less';
|
||||||
|
:deep(.live-player-stretch-btn){
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:deep(.vjs-icon-spinner){
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.refreshBtn{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.refreshBtn:hover{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -86,3 +86,11 @@ defineExpose({
|
||||||
paused,
|
paused,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.live-player-stretch-btn){
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:deep(.vjs-icon-spinner){
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -11,11 +11,8 @@
|
||||||
layout === 'horizontal'
|
layout === 'horizontal'
|
||||||
? 'm-radio-checked-item'
|
? 'm-radio-checked-item'
|
||||||
: 'm-radio-item',
|
: 'm-radio-item',
|
||||||
{ active: myValue === item.value },
|
|
||||||
checkStyle && myValue === item.value ? 'checked' : '',
|
checkStyle && myValue === item.value ? 'checked' : '',
|
||||||
disabled && myValue === item.value
|
{ active: myValue === item.value },
|
||||||
? 'active-checked-disabled'
|
|
||||||
: '',
|
|
||||||
item.disabled ? 'disabled' : '',
|
item.disabled ? 'disabled' : '',
|
||||||
]"
|
]"
|
||||||
v-for="(item, index) in options"
|
v-for="(item, index) in options"
|
||||||
|
@ -100,10 +97,13 @@ const handleRadio = (item: any) => {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
.disabled {
|
.disabled {
|
||||||
|
>div {
|
||||||
color: rgba(0, 0, 0, 0.25);
|
color: rgba(0, 0, 0, 0.25);
|
||||||
border-color: #f5f5f5;
|
border-color: #f5f5f5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
&-item {
|
&-item {
|
||||||
width: 49%;
|
width: 49%;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
|
@ -152,14 +152,14 @@ const handleRadio = (item: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled {
|
//.disabled {
|
||||||
color: rgba(0, 0, 0, 0.25) !important;
|
// color: rgba(0, 0, 0, 0.25) !important;
|
||||||
cursor: not-allowed;
|
// cursor: not-allowed;
|
||||||
}
|
//}
|
||||||
.active-checked-disabled {
|
//.active-checked-disabled {
|
||||||
color: rgba(0, 0, 0, 0.25) !important;
|
// color: rgba(0, 0, 0, 0.25) !important;
|
||||||
border: 1px #d9d9d9 solid !important;
|
// border: 1px #d9d9d9 solid !important;
|
||||||
}
|
//}
|
||||||
.checked-icon-disabled {
|
.checked-icon-disabled {
|
||||||
color: rgba(0, 0, 0, 0.25) !important;
|
color: rgba(0, 0, 0, 0.25) !important;
|
||||||
border-color: #e6e6e6 !important;
|
border-color: #e6e6e6 !important;
|
||||||
|
@ -169,17 +169,25 @@ const handleRadio = (item: any) => {
|
||||||
|
|
||||||
.m-radio {
|
.m-radio {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
>div {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
width: 140px;
|
width: 140px;
|
||||||
height: 140px;
|
height: 140px;
|
||||||
padding: 10px 15px;
|
padding: 10px 16px;
|
||||||
margin-right: 15px;
|
|
||||||
border: 1px solid #d9d9d9;
|
border: 1px solid #d9d9d9;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
.img {
|
.img {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
@ -189,6 +197,8 @@ const handleRadio = (item: any) => {
|
||||||
color: #1d39c4;
|
color: #1d39c4;
|
||||||
border-color: #1d39c4;
|
border-color: #1d39c4;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<j-advanced-search
|
<j-advanced-search
|
||||||
:target='target'
|
:target='target'
|
||||||
:type='type'
|
:type='type'
|
||||||
:request='saveSearchHistory'
|
:request='(data) => saveSearchHistory(data, target)'
|
||||||
:historyRequest='getSearchHistory'
|
:historyRequest='() => getSearchHistory(target)'
|
||||||
:deleteRequest='deleteSearchHistory'
|
:deleteRequest='(_target: string, id: string) => deleteSearchHistory(target, id)'
|
||||||
:columns='columns'
|
:columns='columns'
|
||||||
:class='props.class'
|
:class='props.class'
|
||||||
style='padding-top: 18px; padding-bottom: 18px;'
|
style='padding-top: 18px; padding-bottom: 18px;'
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
<template>
|
||||||
|
<j-modal
|
||||||
|
:title="title"
|
||||||
|
visible
|
||||||
|
:width="400"
|
||||||
|
@cancel="cancel"
|
||||||
|
@ok="ok"
|
||||||
|
:confirmLoading="loading"
|
||||||
|
>
|
||||||
|
<div style="height: 300px; width: 100%;">
|
||||||
|
<vue-cropper
|
||||||
|
ref="cropper"
|
||||||
|
:img="img"
|
||||||
|
:fixed-box="true"
|
||||||
|
:autoCrop="true"
|
||||||
|
:auto-crop-width="200"
|
||||||
|
:auto-crop-height="200"
|
||||||
|
outputType="jpg"
|
||||||
|
></vue-cropper>
|
||||||
|
</div>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="UploadCropper">
|
||||||
|
import 'vue-cropper/dist/index.css'
|
||||||
|
import { VueCropper } from 'vue-cropper';
|
||||||
|
import { fileUpload } from '@/api/comm';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
img: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '图片编辑'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['cancel', 'ok'])
|
||||||
|
|
||||||
|
const imgUrl = ref()
|
||||||
|
const cropper = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const ok = () => {
|
||||||
|
cropper.value.getCropBlob(async (data: Blob) => {
|
||||||
|
console.log(data)
|
||||||
|
let formData = new FormData()
|
||||||
|
formData.append('file', data, new Date().getTime() + '.jpg')
|
||||||
|
|
||||||
|
imgUrl.value = data
|
||||||
|
loading.value = true
|
||||||
|
fileUpload(formData).then(res => {
|
||||||
|
if (res.success) {
|
||||||
|
emit('ok', res.result)
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="upload-image-warp">
|
<div class="upload-image-warp">
|
||||||
<div class="upload-image-border">
|
<div class="upload-image-border" :style="borderStyle">
|
||||||
<j-upload
|
<j-upload
|
||||||
name="file"
|
name="file"
|
||||||
list-type="picture-card"
|
list-type="picture-card"
|
||||||
|
@ -16,13 +16,7 @@
|
||||||
>
|
>
|
||||||
<div class="upload-image-content" :style="props.style">
|
<div class="upload-image-content" :style="props.style">
|
||||||
<template v-if="imageUrl">
|
<template v-if="imageUrl">
|
||||||
<!-- <div class="upload-image"
|
<img :src="imageUrl" width="100%" class="upload-image" />
|
||||||
:style="{
|
|
||||||
backgroundSize: props.backgroundSize,
|
|
||||||
backgroundImage: `url(${imageUrl})`
|
|
||||||
}"
|
|
||||||
></div> -->
|
|
||||||
<img :src="imageUrl" class="upload-image" />
|
|
||||||
<div class="upload-image-mask">点击修改</div>
|
<div class="upload-image-mask">点击修改</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
@ -52,15 +46,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ImageCropper
|
||||||
|
v-if="cropperVisible"
|
||||||
|
:img="cropperImg"
|
||||||
|
@cancel="cropperVisible = false"
|
||||||
|
@ok="saveImage"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name='JProUpload'>
|
<script lang="ts" setup name='JProUpload'>
|
||||||
import { UploadChangeParam, UploadProps } from 'ant-design-vue';
|
import { UploadChangeParam, UploadProps } from 'ant-design-vue';
|
||||||
import { message } from 'jetlinks-ui-components';
|
|
||||||
import { FILE_UPLOAD } from '@/api/comm';
|
import { FILE_UPLOAD } from '@/api/comm';
|
||||||
import { TOKEN_KEY } from '@/utils/variable';
|
import { TOKEN_KEY } from '@/utils/variable';
|
||||||
import { LocalStore } from '@/utils/comm';
|
import {getBase64, LocalStore, onlyMessage} from '@/utils/comm';
|
||||||
import { CSSProperties } from 'vue';
|
import { CSSProperties } from 'vue';
|
||||||
|
import ImageCropper from './Cropper.vue';
|
||||||
|
|
||||||
type Emits = {
|
type Emits = {
|
||||||
(e: 'update:modelValue', data: string): void;
|
(e: 'update:modelValue', data: string): void;
|
||||||
|
@ -73,6 +73,7 @@ interface JUploadProps extends UploadProps {
|
||||||
size?: number;
|
size?: number;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
bgImage?: string;
|
bgImage?: string;
|
||||||
|
borderStyle?:CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
@ -93,6 +94,14 @@ const props: JUploadProps = defineProps({
|
||||||
accept:{
|
accept:{
|
||||||
type: String,
|
type: String,
|
||||||
default: undefined
|
default: undefined
|
||||||
|
},
|
||||||
|
borderStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,6 +109,9 @@ const loading = ref<boolean>(false);
|
||||||
const imageUrl = ref<string>(props?.modelValue || '');
|
const imageUrl = ref<string>(props?.modelValue || '');
|
||||||
const imageTypes = props.types ? props.types : ['image/jpeg', 'image/png'];
|
const imageTypes = props.types ? props.types : ['image/jpeg', 'image/png'];
|
||||||
|
|
||||||
|
const cropperImg = ref()
|
||||||
|
const cropperVisible = ref(false)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
@ -123,26 +135,41 @@ const handleChange = (info: UploadChangeParam) => {
|
||||||
}
|
}
|
||||||
if (info.file.status === 'error') {
|
if (info.file.status === 'error') {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
message.error('上传失败');
|
onlyMessage('上传失败', 'error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const beforeUpload = (file: UploadProps['fileList'][number]) => {
|
const beforeUpload = (file: UploadProps['fileList'][number]) => {
|
||||||
const isType = imageTypes.includes(file.type);
|
const isType = imageTypes.includes(file.type);
|
||||||
|
const maxSize = props.size || 2 // 最大值
|
||||||
if (!isType) {
|
if (!isType) {
|
||||||
if (props.errorMessage) {
|
if (props.errorMessage) {
|
||||||
message.error(props.errorMessage);
|
onlyMessage(props.errorMessage, 'error');
|
||||||
} else {
|
} else {
|
||||||
message.error(`请上传正确格式的图片`);
|
onlyMessage(`请上传正确格式的图片`, 'error');
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const isSize = file.size / 1024 / 1024 < (props.size || 4);
|
const isSize = file.size / 1024 / 1024 < maxSize;
|
||||||
if (!isSize) {
|
if (!isSize) {
|
||||||
message.error(`图片大小必须小于${props.size || 4}M`);
|
onlyMessage(`图片大小必须小于${maxSize}M`, 'error');
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return isType && isSize;
|
|
||||||
|
getBase64(file, (base64Url) => {
|
||||||
|
cropperImg.value = base64Url
|
||||||
|
cropperVisible.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const saveImage = (url: string) => {
|
||||||
|
cropperVisible.value = false
|
||||||
|
imageUrl.value = url
|
||||||
|
emit('update:modelValue', url);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<div class="value-item-warp">
|
<div class="value-item-warp">
|
||||||
<j-select
|
<j-select
|
||||||
v-if="typeMap.get(itemType) === 'select'"
|
v-if="typeMap.get(itemType) === 'select'"
|
||||||
|
:mode="mode"
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
:options="options"
|
:options="options"
|
||||||
allowClear
|
allowClear
|
||||||
|
@ -77,6 +78,7 @@
|
||||||
/>
|
/>
|
||||||
<j-input
|
<j-input
|
||||||
v-else
|
v-else
|
||||||
|
:placeholder="placeholder"
|
||||||
allowClear
|
allowClear
|
||||||
type="text"
|
type="text"
|
||||||
v-model:value="myValue"
|
v-model:value="myValue"
|
||||||
|
@ -139,6 +141,15 @@ const props = defineProps({
|
||||||
type: Array as PropType<DefaultOptionType[]>,
|
type: Array as PropType<DefaultOptionType[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
// 多选框
|
||||||
|
mode: {
|
||||||
|
type: String as PropType<'multiple' | 'tags' | 'combobox' | ''>,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// type Props = {
|
// type Props = {
|
||||||
// itemData?: Object;
|
// itemData?: Object;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import NormalUpload from './NormalUpload/index.vue'
|
||||||
import FileFormat from './FileFormat/index.vue'
|
import FileFormat from './FileFormat/index.vue'
|
||||||
import JProUpload from './Upload/index.vue'
|
import JProUpload from './Upload/index.vue'
|
||||||
import { BasicLayoutPage, BlankLayoutPage, FullPage } from './Layout'
|
import { BasicLayoutPage, BlankLayoutPage, FullPage } from './Layout'
|
||||||
|
import RadioCard from './RadioCard/index.vue'
|
||||||
import { PageContainer, AIcon, Ellipsis } from 'jetlinks-ui-components'
|
import { PageContainer, AIcon, Ellipsis } from 'jetlinks-ui-components'
|
||||||
// import Ellipsis from './Ellipsis/index.vue'
|
// import Ellipsis from './Ellipsis/index.vue'
|
||||||
import JEmpty from './Empty/index.vue'
|
import JEmpty from './Empty/index.vue'
|
||||||
|
@ -39,5 +40,6 @@ export default {
|
||||||
.component('ValueItem', ValueItem)
|
.component('ValueItem', ValueItem)
|
||||||
.component('RowPagination', RowPagination)
|
.component('RowPagination', RowPagination)
|
||||||
.component('FullPage', FullPage)
|
.component('FullPage', FullPage)
|
||||||
|
.component('RadioCard', RadioCard)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,34 +23,34 @@ export const AccountMenu = {
|
||||||
name: 'account/center',
|
name: 'account/center',
|
||||||
code: 'account/center',
|
code: 'account/center',
|
||||||
meta: {
|
meta: {
|
||||||
title: '基本设置',
|
title: '个人中心',
|
||||||
icon: '',
|
icon: '',
|
||||||
hideInMenu: false
|
hideInMenu: true
|
||||||
},
|
},
|
||||||
component: () => import('@/views/account/Center/index.vue')
|
component: () => import('@/views/account/Center/index.vue')
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
path: '/account/NotificationSubscription',
|
// path: '/account/NotificationSubscription',
|
||||||
name: 'account/NotificationSubscription',
|
// name: 'account/NotificationSubscription',
|
||||||
code: 'account/NotificationSubscription',
|
// code: 'account/NotificationSubscription',
|
||||||
meta: {
|
// meta: {
|
||||||
title: '通知订阅',
|
// title: '通知订阅',
|
||||||
icon: '',
|
// icon: '',
|
||||||
hideInMenu: false
|
// hideInMenu: false
|
||||||
},
|
// },
|
||||||
component: () => import('@/views/account/NotificationSubscription/index.vue')
|
// component: () => import('@/views/account/NotificationSubscription/index.vue')
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
path: '/account/NotificationRecord',
|
// path: '/account/NotificationRecord',
|
||||||
name: 'account/NotificationRecord',
|
// name: 'account/NotificationRecord',
|
||||||
code: 'account/NotificationRecord',
|
// code: 'account/NotificationRecord',
|
||||||
meta: {
|
// meta: {
|
||||||
title: '通知记录',
|
// title: '通知记录',
|
||||||
icon: '',
|
// icon: '',
|
||||||
hideInMenu: false
|
// hideInMenu: false
|
||||||
},
|
// },
|
||||||
component: () => import('@/views/account/NotificationRecord/index.vue')
|
// component: () => import('@/views/account/NotificationRecord/index.vue')
|
||||||
},
|
// },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,13 @@ export default [
|
||||||
title: '授权页'
|
title: '授权页'
|
||||||
},
|
},
|
||||||
component: () => import('@/views/oauth/index.vue')
|
component: () => import('@/views/oauth/index.vue')
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: '/oauth/wechat',
|
||||||
|
meta: {
|
||||||
|
title: '微信授权页'
|
||||||
|
},
|
||||||
|
component: () => import('@/views/oauth/WeChat.vue')
|
||||||
|
},
|
||||||
|
AccountMenu
|
||||||
]
|
]
|
|
@ -5,8 +5,8 @@ import { cloneDeep, isArray } from 'lodash-es'
|
||||||
import { usePermissionStore } from './permission'
|
import { usePermissionStore } from './permission'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { onlyMessage } from '@/utils/comm'
|
import { onlyMessage } from '@/utils/comm'
|
||||||
import { AccountMenu, NotificationRecordCode, NotificationSubscriptionCode } from '@/router/menu'
|
// import { AccountMenu, NotificationRecordCode, NotificationSubscriptionCode } from '@/router/menu'
|
||||||
import { MESSAGE_SUBSCRIBE_MENU_CODE, USER_CENTER_MENU_CODE } from '@/utils/consts'
|
import { USER_CENTER_MENU_CODE } from '@/utils/consts'
|
||||||
import {isNoCommunity} from "@/utils/utils";
|
import {isNoCommunity} from "@/utils/utils";
|
||||||
|
|
||||||
const defaultOwnParams = [
|
const defaultOwnParams = [
|
||||||
|
@ -90,6 +90,12 @@ export const useMenuStore = defineStore({
|
||||||
console.warn(`没有找到对应的页面: ${name}`)
|
console.warn(`没有找到对应的页面: ${name}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
routerPush(name: string, params?: Record<string, any>, query?: Record<string, any>) {
|
||||||
|
this.params = { [name]: params || {}}
|
||||||
|
router.push({
|
||||||
|
name, params, query, state: { params }
|
||||||
|
})
|
||||||
|
},
|
||||||
queryMenuTree(isCommunity = false): Promise<any[]> {
|
queryMenuTree(isCommunity = false): Promise<any[]> {
|
||||||
return new Promise(async (res) => {
|
return new Promise(async (res) => {
|
||||||
//过滤非集成的菜单
|
//过滤非集成的菜单
|
||||||
|
@ -103,12 +109,7 @@ export const useMenuStore = defineStore({
|
||||||
permission.permissions = {}
|
permission.permissions = {}
|
||||||
const { menusData, silderMenus } = filterAsyncRouter(resultData)
|
const { menusData, silderMenus } = filterAsyncRouter(resultData)
|
||||||
|
|
||||||
// 是否存在通知订阅
|
this.menus = findCodeRoute([...resultData]) // AccountMenu
|
||||||
const hasMessageSub = resultData.some((item: { code: string }) => item.code === MESSAGE_SUBSCRIBE_MENU_CODE)
|
|
||||||
if (!hasMessageSub) {
|
|
||||||
AccountMenu.children = AccountMenu.children.filter((item: { code: string }) => ![NotificationSubscriptionCode, NotificationRecordCode].includes(item.code) )
|
|
||||||
}
|
|
||||||
this.menus = findCodeRoute([...resultData, AccountMenu])
|
|
||||||
Object.keys(this.menus).forEach((item) => {
|
Object.keys(this.menus).forEach((item) => {
|
||||||
const _item = this.menus[item]
|
const _item = this.menus[item]
|
||||||
if (_item.buttons?.length) {
|
if (_item.buttons?.length) {
|
||||||
|
@ -123,8 +124,8 @@ export const useMenuStore = defineStore({
|
||||||
hideInMenu: true
|
hideInMenu: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
menusData.push(AccountMenu)
|
// menusData.push(AccountMenu)
|
||||||
this.siderMenus = silderMenus.filter((item: { name: string }) => ![USER_CENTER_MENU_CODE, MESSAGE_SUBSCRIBE_MENU_CODE].includes(item.name))
|
this.siderMenus = silderMenus.filter((item: { name: string }) => ![USER_CENTER_MENU_CODE].includes(item.name))
|
||||||
res(menusData)
|
res(menusData)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,7 +21,8 @@ export const useMetadataStore = defineStore({
|
||||||
action: 'add',
|
action: 'add',
|
||||||
import: false,
|
import: false,
|
||||||
importMetadata: false,
|
importMetadata: false,
|
||||||
} as MetadataModelType
|
} as MetadataModelType,
|
||||||
|
tabActiveKey: 'properties',
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
set(key: string, value: any) {
|
set(key: string, value: any) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ type RuleEditorType = {
|
||||||
log: {
|
log: {
|
||||||
content: string;
|
content: string;
|
||||||
time: number;
|
time: number;
|
||||||
|
_time: number;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,23 @@ import { SystemConst } from '@/utils/consts'
|
||||||
type SystemStateType = {
|
type SystemStateType = {
|
||||||
isCommunity: boolean;
|
isCommunity: boolean;
|
||||||
configInfo: Partial<ConfigInfoType>;
|
configInfo: Partial<ConfigInfoType>;
|
||||||
|
layout:{
|
||||||
|
siderWidth: string | number | undefined; // 左侧菜单栏宽度
|
||||||
|
headerHeight: string | number | undefined; // 头部高度
|
||||||
|
collapsedWidth: string | number | undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSystem = defineStore('system', {
|
export const useSystem = defineStore('system', {
|
||||||
state: (): SystemStateType => ({
|
state: (): SystemStateType => ({
|
||||||
isCommunity: false,
|
isCommunity: false,
|
||||||
// configInfo: [] as any[]
|
// configInfo: [] as any[]
|
||||||
configInfo: {}
|
configInfo: {},
|
||||||
|
layout:{
|
||||||
|
siderWidth: 208, // 左侧菜单栏宽度
|
||||||
|
headerHeight: 60, // 头部高度
|
||||||
|
collapsedWidth: 48,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
getSystemVersion(): Promise<any[]> {
|
getSystemVersion(): Promise<any[]> {
|
||||||
|
|
|
@ -19,8 +19,19 @@ export const useUserInfo = defineStore('userInfo', {
|
||||||
roles: [],
|
roles: [],
|
||||||
token: '',
|
token: '',
|
||||||
user: {},
|
user: {},
|
||||||
|
name: '',
|
||||||
|
orgList: [],
|
||||||
|
roleList: [],
|
||||||
|
telephone: '',
|
||||||
|
email: '',
|
||||||
|
avatar: ''
|
||||||
},
|
},
|
||||||
alarmUpdateCount: 0
|
alarmUpdateCount: 0,
|
||||||
|
tabKey: 'HomeView', // 个人中心的tabKey,
|
||||||
|
messageInfo: {}, // 站内信的row
|
||||||
|
other: {
|
||||||
|
tabKey: '' // 站内信的tabkey
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
|
@ -175,3 +175,13 @@ export const openKeysByTree = (data: any[], search: any, searchKey: string = 'id
|
||||||
findKey(filterTree)
|
findKey(filterTree)
|
||||||
return openKeys
|
return openKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getBase64 = (img: File, callback: (base64Url: string) => void) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(img);
|
||||||
|
|
||||||
|
reader.onload = (result: any) => {
|
||||||
|
console.log(result)
|
||||||
|
callback(result.target.result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
import { MESSAGE_SUBSCRIBE_MENU_DATA } from '@/views/init-home/data/baseMenu'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态颜色
|
* 状态颜色
|
||||||
*/
|
*/
|
||||||
|
@ -52,5 +50,9 @@ export const SystemConst = {
|
||||||
|
|
||||||
export const USER_CENTER_MENU_CODE = 'account-center'
|
export const USER_CENTER_MENU_CODE = 'account-center'
|
||||||
export const USER_CENTER_MENU_BUTTON_CODE = 'user-center-passwd-update'
|
export const USER_CENTER_MENU_BUTTON_CODE = 'user-center-passwd-update'
|
||||||
export const MESSAGE_SUBSCRIBE_MENU_CODE = 'message-subscribe'
|
|
||||||
export const MESSAGE_SUBSCRIBE_MENU_BUTTON_CODE = 'message-subscribe-view'
|
/**协议列表 */
|
||||||
|
export const protocolList = [
|
||||||
|
{ label: 'OPC-UA', value: 'OPC_UA', alias: 'opc-ua' },
|
||||||
|
{ label: 'Modbus/TCP', value: 'MODBUS_TCP', alias: 'modbus-tcp' },
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const createScript = (src: string) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.onload = () => {
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
script.setAttribute('type', 'text/javascript')
|
||||||
|
script.setAttribute('src', src)
|
||||||
|
document.body.appendChild(script)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import JSEncrypt from "jsencrypt";
|
||||||
|
|
||||||
|
export const encrypt =(txt:string,publicKey:string)=>{
|
||||||
|
const encryptor = new JSEncrypt()
|
||||||
|
encryptor.setPublicKey(publicKey)
|
||||||
|
return encryptor.encrypt(txt)
|
||||||
|
}
|
|
@ -134,6 +134,9 @@ export const postStream = function(url: string, data = {}, params = {}) {
|
||||||
const showNotification = (message: string, description: string, key?: string, show: boolean = true) => {
|
const showNotification = (message: string, description: string, key?: string, show: boolean = true) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
Notification.error({
|
Notification.error({
|
||||||
|
style: {
|
||||||
|
zIndex: 1040
|
||||||
|
},
|
||||||
key,
|
key,
|
||||||
message: '',
|
message: '',
|
||||||
description
|
description
|
||||||
|
|
|
@ -145,3 +145,41 @@ export const ArrayToTree = (list: any[]): any[] => {
|
||||||
// 返回出去
|
// 返回出去
|
||||||
return treeList;
|
return treeList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const EventEmitter = {
|
||||||
|
list: {},
|
||||||
|
subscribe: function(events: string[], fn: Function) {
|
||||||
|
const list = this.list
|
||||||
|
events.forEach(event => {
|
||||||
|
(list[event] || (list[event] = [])).push(fn)
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
emit: function(events:string, data?: any) {
|
||||||
|
const list = this.list
|
||||||
|
const fns: Function[] = list[events] ? [...list[events]] : []
|
||||||
|
|
||||||
|
if (!fns.length) return false;
|
||||||
|
|
||||||
|
fns.forEach(fn => {
|
||||||
|
fn(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
unSubscribe: function(events:string[], fn: Function) {
|
||||||
|
const list = this.list
|
||||||
|
events.forEach(key => {
|
||||||
|
if (key in list) {
|
||||||
|
const fns = list[key]
|
||||||
|
for (let i = 0; i < fns.length; i++) {
|
||||||
|
if (fns[i] === fn) {
|
||||||
|
fns.splice(i, 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,9 @@ export const initWebSocket = () => {
|
||||||
const data = JSON.parse(msg.data)
|
const data = JSON.parse(msg.data)
|
||||||
|
|
||||||
if (data.type === 'error') {
|
if (data.type === 'error') {
|
||||||
notification.error({ key: 'wserr', message: data.message })
|
notification.error({ key: 'wserr', message: data.message, style: {
|
||||||
|
zIndex: 1040
|
||||||
|
} })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subs[data.requestId]) {
|
if (subs[data.requestId]) {
|
||||||
|
|
|
@ -197,6 +197,7 @@ import { FormValidate, FormState } from '../data';
|
||||||
import type { FormInstance } from 'ant-design-vue';
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
import type { FormDataType } from '../type.d';
|
import type { FormDataType } from '../type.d';
|
||||||
import { cloneDeep, isArray } from 'lodash-es';
|
import { cloneDeep, isArray } from 'lodash-es';
|
||||||
|
import { protocolList } from '@/utils/consts';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
@ -281,20 +282,16 @@ const getCertificateList = async () => {
|
||||||
const getProvidersList = async () => {
|
const getProvidersList = async () => {
|
||||||
const resp: any = await getProviders();
|
const resp: any = await getProviders();
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
const list = [
|
|
||||||
{ label: 'OPC UA', value: 'OPC_UA' },
|
|
||||||
{ label: 'Modbus TCP', value: 'MODBUS_TCP' },
|
|
||||||
];
|
|
||||||
const arr = resp.result
|
const arr = resp.result
|
||||||
.filter(
|
.filter(
|
||||||
(item: any) => item.id === 'modbus-tcp' || item.id === 'opc-ua',
|
(item: any) => item.id === 'modbus-tcp' || item.id === 'opc-ua',
|
||||||
)
|
)
|
||||||
.map((it: any) => (it?.id === 'opc-ua' ? 'OPC_UA' : 'MODBUS_TCP'));
|
.map((it: any) => it.id);
|
||||||
const providers: any = list.filter((item: any) =>
|
const providers: any = protocolList.filter((item: any) =>
|
||||||
arr.includes(item.value),
|
arr.includes(item.alias),
|
||||||
);
|
);
|
||||||
providersList.value = providers;
|
providersList.value = providers;
|
||||||
if (arr.includes('OPC_UA')) {
|
if (arr.includes('opc-ua')) {
|
||||||
getOptionsList();
|
getOptionsList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,9 +50,19 @@ export const regDomain = new RegExp(
|
||||||
);
|
);
|
||||||
export const checkEndpoint = (_rule: Rule, value: string): Promise<any> =>
|
export const checkEndpoint = (_rule: Rule, value: string): Promise<any> =>
|
||||||
new Promise(async (resolve, reject) => {
|
new Promise(async (resolve, reject) => {
|
||||||
|
if(!value) return resolve('');
|
||||||
const res: any = await validateField(value);
|
const res: any = await validateField(value);
|
||||||
return res.result.passed ? resolve('') : reject(res.result.reason);
|
return res.result.passed ? resolve('') : reject(res.result.reason);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const checkHost = (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
if(!value) return resolve('');
|
||||||
|
if(!(regIP.test(value) || regIPv6.test(value) || regDomain.test(value))) {
|
||||||
|
return reject('请输入正确格式的Modbus主机IP地址')
|
||||||
|
}
|
||||||
|
return resolve('')
|
||||||
|
});
|
||||||
export const FormValidate = {
|
export const FormValidate = {
|
||||||
name: [
|
name: [
|
||||||
{ required: true, message: '请输入名称', trigger: 'blur' },
|
{ required: true, message: '请输入名称', trigger: 'blur' },
|
||||||
|
@ -65,8 +75,9 @@ export const FormValidate = {
|
||||||
message: '请输入Modbus主机IP',
|
message: '请输入Modbus主机IP',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: regIP || regIPv6 || regDomain,
|
validator: checkHost,
|
||||||
message: '请输入正确格式的Modbus主机IP地址',
|
trigger: 'blur',
|
||||||
|
// message: '请输入正确格式的Modbus主机IP地址',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
port: [
|
port: [
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div>
|
<div>
|
||||||
<pro-search
|
<pro-search
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
target="search"
|
target="search-datacollect-channel"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
/>
|
/>
|
||||||
<FullPage>
|
<FullPage>
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
</j-tooltip>
|
</j-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</j-col>
|
</j-col>
|
||||||
<j-col :span="12">
|
<!-- <j-col :span="12">
|
||||||
<div class="card-item-content-text">
|
<div class="card-item-content-text">
|
||||||
地址
|
地址
|
||||||
</div>
|
</div>
|
||||||
|
@ -98,6 +98,14 @@
|
||||||
>
|
>
|
||||||
</j-tooltip>
|
</j-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
</j-col> -->
|
||||||
|
<j-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
说明
|
||||||
|
</div>
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
<j-ellipsis>{{slotProps.description}}</j-ellipsis>
|
||||||
|
</div>
|
||||||
</j-col>
|
</j-col>
|
||||||
</j-row>
|
</j-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
<j-form-item
|
<j-form-item
|
||||||
label="地址"
|
label="地址"
|
||||||
:name="['pointKey']"
|
:name="['pointKey']"
|
||||||
|
validateFirst
|
||||||
:rules="[
|
:rules="[
|
||||||
...ModBusRules.pointKey,
|
...ModBusRules.pointKey,
|
||||||
{
|
{
|
||||||
|
@ -57,7 +58,7 @@
|
||||||
placeholder="请输入地址"
|
placeholder="请输入地址"
|
||||||
v-model:value="formData.pointKey"
|
v-model:value="formData.pointKey"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="999999999"
|
:max="999999"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
|
@ -131,7 +132,7 @@
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="请输入小数保留位数"
|
placeholder="请输入小数保留位数"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="255"
|
:max="65535"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.configuration.codec.configuration.scale
|
formData.configuration.codec.configuration.scale
|
||||||
|
@ -220,7 +221,8 @@
|
||||||
placeholder="请输入采集频率"
|
placeholder="请输入采集频率"
|
||||||
v-model:value="formData.configuration.interval"
|
v-model:value="formData.configuration.interval"
|
||||||
addon-after="ms"
|
addon-after="ms"
|
||||||
:max="9999999999999998"
|
:max="2147483648"
|
||||||
|
:min="0"
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
|
|
||||||
|
@ -378,8 +380,7 @@ const changeWriteByteCount = (value: Array<string>) => {
|
||||||
formData.value.configuration.parameter.writeByteCount = value[0];
|
formData.value.configuration.parameter.writeByteCount = value[0];
|
||||||
};
|
};
|
||||||
const changeFunction = (value: string) => {
|
const changeFunction = (value: string) => {
|
||||||
formData.value.accessModes =
|
formData.value.accessModes = ['InputRegisters', 'DiscreteInputs'].includes(value) ? ['read'] : ['read', 'write'];
|
||||||
value === 'InputRegisters' ? ['read'] : ['read', 'write'];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkProvider = (_rule: Rule, value: string): Promise<any> =>
|
const checkProvider = (_rule: Rule, value: string): Promise<any> =>
|
||||||
|
|
|
@ -58,7 +58,8 @@
|
||||||
placeholder="请输入采集频率"
|
placeholder="请输入采集频率"
|
||||||
v-model:value="formData.configuration.interval"
|
v-model:value="formData.configuration.interval"
|
||||||
addon-after="ms"
|
addon-after="ms"
|
||||||
:max="9999999999999998"
|
:max="2147483648"
|
||||||
|
:min="0"
|
||||||
/>
|
/>
|
||||||
</j-form-item>
|
</j-form-item>
|
||||||
<j-form-item label="" :name="['features']">
|
<j-form-item label="" :name="['features']">
|
||||||
|
|