diff --git a/package.json b/package.json index b1629925..4fbbf389 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "event-source-polyfill": "^1.0.31", "global": "^4.4.0", "jetlinks-store": "^0.0.3", - "jetlinks-ui-components": "1.0.5", + "jetlinks-ui-components": "^1.0.8", "js-cookie": "^3.0.1", "less": "^4.1.3", "less-loader": "^11.1.0", diff --git a/public/images/access/plugin.png b/public/images/access/plugin.png new file mode 100644 index 00000000..091fe267 Binary files /dev/null and b/public/images/access/plugin.png differ diff --git a/public/images/cascade.png b/public/images/cascade.png new file mode 100644 index 00000000..2a9a33ae Binary files /dev/null and b/public/images/cascade.png differ diff --git a/public/images/channel.png b/public/images/channel.png new file mode 100644 index 00000000..866d15a3 Binary files /dev/null and b/public/images/channel.png differ diff --git a/public/images/plug.png b/public/images/plug.png new file mode 100644 index 00000000..d10b8c83 Binary files /dev/null and b/public/images/plug.png differ diff --git a/src/api/link/accessConfig.ts b/src/api/link/accessConfig.ts index 869dab76..86d03762 100644 --- a/src/api/link/accessConfig.ts +++ b/src/api/link/accessConfig.ts @@ -43,3 +43,7 @@ export const getResourcesCurrent = () => export const getClusters = () => server.get(`network/resources/clusters`); + +export const getPluginList = (data: any) => server.post('/plugin/driver/_query/no-paging', data) + +export const getPluginConfig = (id: string) => server.get(`/plugin/driver/${id}/description`) diff --git a/src/api/link/plugin.ts b/src/api/link/plugin.ts new file mode 100644 index 00000000..7cf5b335 --- /dev/null +++ b/src/api/link/plugin.ts @@ -0,0 +1,30 @@ +import { post, get, remove, patch } from '@/utils/request' +import { BASE_API_PATH } from '@/utils/variable'; + +export const queryPage = (data: any) => post(`/plugin/driver/_query`, data) + +export const uploadFile = `${BASE_API_PATH}/plugin/driver/upload` + +export const add = (data: any) => post('/plugin/driver', data) + +export const removeFn = (id: string) => remove(`/plugin/driver/${id}`) + +export const detail = (id: string) => get(`/plugin/driver/${id}`) + +/** + * 获取插件支持的产品信息 + * 用于在产品选择接入方式后,选择产品类型。 + * 即将平台中当前产品与插件中指定的产品绑定,然后把插件的物模型保存到产品并且绑定产品ID映射关系 + * @param id + */ +export const getProductsById = (id: string) => get(`/plugin/driver/${id}/products`) + +export const savePluginData = (type: string, pluginId: string, internalId: string, externalId: string ) => patch(`/plugin/mapping/${type}/${pluginId}/${internalId}`, externalId) + +export const getPluginData = (type: string, pluginId: string, internalId: string ) => get(`/plugin/mapping/${type}/${pluginId}/${internalId}`) + +export const getPublic = (id: string, path: string) => get(`/plugin/driver/${id}/${path}`) + +export const getTypes = () => get(`/dictionary/internal-plugin-type/items`) + +export const vailIdFn = (id: string ) => get(`/plugin/driver/id/_validate`, { id }) \ No newline at end of file diff --git a/src/components/Form/rules.ts b/src/components/Form/rules.ts index e3e3e7e0..4dbfb81e 100644 --- a/src/components/Form/rules.ts +++ b/src/components/Form/rules.ts @@ -1,7 +1,7 @@ const MaxLengthStringFn = (len: number = 64) => ({ max: len, - message: `最多输入${64}个字符`, + message: `最多输入${len}个字符`, }) export const Max_Length_64 = [MaxLengthStringFn()] @@ -24,4 +24,67 @@ export const ID_Rule = [ message: '请输入英文或者数字或者-或者_', }, Max_Length_64[0] -] \ No newline at end of file +] + +export const CreteRuleByType = (type: string) => { + switch (type){ + case 'int': + return [ + { + validator: (_: any, value: number) => { + const baseNumber = 2147483648 + if (value < -baseNumber) { + return Promise.reject(`最小仅输入-${baseNumber}`); + } + if (value > baseNumber) { + return Promise.reject(`最大可输入${baseNumber}`); + } + return Promise.resolve(); + } + } + ] + case'long': + return [ + { + validator: (_: any, value: number) => { + const baseNumber = 340282346638528860000000000000000000000 + if (value < -baseNumber) { + return Promise.reject(`最小仅输入-${baseNumber}`); + } + if (value > baseNumber) { + return Promise.reject(`最大可输入${baseNumber}`); + } + return Promise.resolve(); + } + } + ] + case'float': + return [ + { + validator: (_: any, value: number) => { + const baseNumber = 9223372036854775807 + if (value < -baseNumber) { + return Promise.reject(`最小仅输入-${baseNumber}`); + } + if (value > baseNumber) { + return Promise.reject(`最大可输入${baseNumber}`); + } + return Promise.resolve(); + } + } + ] + // case'double': + // return [ + // { + // max: 1.7976931348623157, + // message: '最大可输入64位字符' + // } + // ] + case 'string': + return [MaxLengthStringFn()]; + case 'description': + return [MaxLengthStringFn(200)] + default: + return [] + } +} \ No newline at end of file diff --git a/src/style.less b/src/style.less index 09e99ced..87cb6cb8 100644 --- a/src/style.less +++ b/src/style.less @@ -1,3 +1,7 @@ +@import "./style/variable"; + +@DarkMenuItemColor: #808491 !important; + .ant-form-item-required:before { position: absolute; right: -12px; @@ -16,20 +20,16 @@ } .ant-pro-top-nav-header { - .ant-menu-item { padding: 0 10px !important; &:not(:first-child) { - margin-left: 8px; - } - &:hover { - background-color: transparent; + margin-left: 8px !important; } } + } - .ant-menu-item-selected,& .ant-menu-item-active { - transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - } + .dark { + background-color: #3F4960 !important; h1,& .right-content, & .anticon-bell { color: #fff !important; @@ -39,42 +39,111 @@ color: rgba(#fff, 0.55) !important; } - .ant-menu-item-selected { - & .ant-pro-menu-item-title,& .anticon { - color: #fff !important; - } - } - - .ant-menu-item-selected,& .ant-menu-item-active { - transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - background: linear-gradient(0deg, rgba(#fff, 0.25) 0%, rgba(#fff, 0) 82%); - &::after { - left: 0; - right: 0; - border-bottom-color: rgba(#fff, .8); - } - } - - &.light { - background: #3F4960; - box-shadow: 0 1px 0px 0px #E9E9E9; - - h1,& .right-content, & .anticon-bell { - color: #fff !important; - } + .ant-menu { + background-color: #3F4960 !important; .ant-menu-item-selected,& .ant-menu-item-active { - transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - background: linear-gradient(0deg, rgba(#fff, 0.15) 0%, rgba(#fff, 0) 82%); + transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important; + } + + .ant-menu-item-selected { + span { + color: #fff !important; + } + } + + .ant-menu-item { + span{ + color: rgba(255, 255, 255, 0.55) !important; + } + &:hover { + background-color: transparent !important; + } &::after { left: 0; right: 0; - border-bottom-color: rgba(#fff, .7); + } + } + + .ant-menu-item-selected,& .ant-menu-item-active { + transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important; + background: linear-gradient(0deg, rgba(#fff, 0.25) 0%, rgba(#fff, 0) 82%) !important; + &::after { + border-bottom-color: rgba(#fff, .8); } } } } + .ant-layout-sider-dark { + background: #fff !important; + + .ant-menu-dark { + background: #fff !important; + + span { + color: @DarkMenuItemColor; + } + + .ant-menu-submenu-title { + span { + color: @DarkMenuItemColor; + } + + i { + &::after { + background-color: @DarkMenuItemColor; + } + + &::before { + background-color: @DarkMenuItemColor; + } + } + } + + .ant-menu-sub { + background: transparent; + .ant-menu-item { + span { + color: @DarkMenuItemColor; + } + } + } + + .ant-menu-submenu-selected { + .ant-menu-submenu-title { + span { + color: @primary-color !important; + } + + i { + &::after { + background-color: @primary-color !important; + } + + &::before { + background-color: @primary-color !important; + } + } + } + .ant-menu-item-selected { + span { + color: @primary-color !important; + } + } + } + + .ant-menu-item-selected { + background-color: transparent !important; + span { + color: @primary-color !important; + } + } + } + + + } + .ant-layout-sider { box-shadow: 1px 0 0 0 #E9E9E9 !important; } diff --git a/src/views/device/Instance/Import/modal.vue b/src/views/device/Instance/Import/modal.vue new file mode 100644 index 00000000..8f957635 --- /dev/null +++ b/src/views/device/Instance/Import/modal.vue @@ -0,0 +1,69 @@ + + + + + + + + + + 取消 + 上一步 + 下一步 + 确认 + + + + + + + \ No newline at end of file diff --git a/src/views/device/Instance/Import/product.vue b/src/views/device/Instance/Import/product.vue new file mode 100644 index 00000000..f83030b5 --- /dev/null +++ b/src/views/device/Instance/Import/product.vue @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + {{ slotProps.name }} + + + + + + + 设备类型 + + 直连设备 + + + + + + + + + + + \ No newline at end of file diff --git a/src/views/device/Product/Detail/DeviceAccess/index.vue b/src/views/device/Product/Detail/DeviceAccess/index.vue index faeca451..ef91ff68 100644 --- a/src/views/device/Product/Detail/DeviceAccess/index.vue +++ b/src/views/device/Product/Detail/DeviceAccess/index.vue @@ -90,7 +90,7 @@ 返回 + + + + ) => { edge.push(item); } else { item.type = 'network'; - // network.push(item); - /** - * 插件设备接入 暂时不开发 todo - */ - if (item.id !== 'plugin_gateway' || item.name !== '插件设备接入') { - network.push(item); - } + network.push(item); } }); diff --git a/src/views/link/AccessConfig/components/AccessCard/index.vue b/src/views/link/AccessConfig/components/AccessCard/index.vue index e6f1361e..a8304fb2 100644 --- a/src/views/link/AccessConfig/components/AccessCard/index.vue +++ b/src/views/link/AccessConfig/components/AccessCard/index.vue @@ -34,8 +34,8 @@ const emit = defineEmits(['checkedChange']); const props = defineProps({ checked: { - type: Array, - default: () => [], + type: String, + default: undefined, }, data: { type: Object, @@ -115,15 +115,15 @@ const checkedChange = (id: string) => { } } .access-media { - background: url('/public/images/access-media.png') no-repeat; + background: url('/images/access-media.png') no-repeat; background-position: bottom right; } -.access-network { - background: url('/public/images/access-network.png') no-repeat; +.access-network, .access-plugin { + background: url('/images/access-network.png') no-repeat; background-position: bottom right; } .access-protocol { - background: url('/public/images/access-protocol.png') no-repeat; + background: url('/images/access-protocol.png') no-repeat; background-position: bottom right; } diff --git a/src/views/link/AccessConfig/components/Network/index.vue b/src/views/link/AccessConfig/components/Network/index.vue index ffb47c2f..21c435be 100644 --- a/src/views/link/AccessConfig/components/Network/index.vue +++ b/src/views/link/AccessConfig/components/Network/index.vue @@ -294,6 +294,7 @@ :hasPermission="`link/AccessConfig:${ id === ':id' ? 'add' : 'update' }`" + :loading='loading' > 保存 @@ -378,6 +379,7 @@ const formData = ref({ name: '', description: '', }); +const loading = ref(false) const { resetFields, validate, validateInfos } = useForm( formData, @@ -515,10 +517,12 @@ const saveData = () => { ? 'Gateway' : ProtocolMapping.get(props.provider.id), }; + loading.value = true const resp = id === ':id' ? await save(params) : await update({ ...params, id }); + loading.value = false if (resp.status === 200) { onlyMessage('操作成功', 'success'); history.back(); diff --git a/src/views/link/AccessConfig/components/Plugin/index.vue b/src/views/link/AccessConfig/components/Plugin/index.vue new file mode 100644 index 00000000..92ab62d7 --- /dev/null +++ b/src/views/link/AccessConfig/components/Plugin/index.vue @@ -0,0 +1,426 @@ + + + + + + + + + + + + + 新增 + + + + + + + + + + 插件ID: + + + {{ item.id }} + + + + + 版本号: + + + {{ item.version }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 下一步 + + + 保存 + + + 上一步 + + + + + + + + \ No newline at end of file diff --git a/src/views/link/AccessConfig/components/Provider/index.vue b/src/views/link/AccessConfig/components/Provider/index.vue index 065f25c0..90e2a8a6 100644 --- a/src/views/link/AccessConfig/components/Provider/index.vue +++ b/src/views/link/AccessConfig/components/Provider/index.vue @@ -98,7 +98,7 @@ const click = (value: object) => { width: 15%; min-width: 64px; height: 2px; - background-image: url('/public/images/access/rectangle.png'); + background-image: url('/images/access/rectangle.png'); background-repeat: no-repeat; background-size: 100% 100%; content: ' '; diff --git a/src/views/link/AccessConfig/data.ts b/src/views/link/AccessConfig/data.ts index f2c9ec7b..9753405e 100644 --- a/src/views/link/AccessConfig/data.ts +++ b/src/views/link/AccessConfig/data.ts @@ -33,6 +33,7 @@ BackMap.set('modbus-tcp', getImage('/access/modbus.png')); BackMap.set('coap-server-gateway', getImage('/access/coap.png')); BackMap.set('tcp-server-gateway', getImage('/access/tcp.png')); BackMap.set('Ctwing', getImage('/access/ctwing.png')); +BackMap.set('plugin_gateway', getImage('/access/plugin.png')); BackMap.set('child-device', getImage('/access/child-device.png')); BackMap.set('opc-ua', getImage('/access/opc-ua.png')); BackMap.set('http-server-gateway', getImage('/access/http.png')); diff --git a/src/views/link/AccessConfig/index.vue b/src/views/link/AccessConfig/index.vue index 587fd1ee..91d65df9 100644 --- a/src/views/link/AccessConfig/index.vue +++ b/src/views/link/AccessConfig/index.vue @@ -13,18 +13,7 @@ :columns="columns" :request="list" :defaultParams="{ - sorts: [{ name: 'createTime', order: 'desc' }], - terms: [ - { - terms: [ - { - termType: 'nin', - column: 'provider', - value: 'plugin_gateway', //todo 暂时不做插件接入 - }, - ], - }, - ], + sorts: [{ name: 'createTime', order: 'desc' }] }" gridColumn="2" :gridColumns="[1, 2]" diff --git a/src/views/link/plugin/Save.vue b/src/views/link/plugin/Save.vue new file mode 100644 index 00000000..8d13aa0c --- /dev/null +++ b/src/views/link/plugin/Save.vue @@ -0,0 +1,171 @@ + + + + + + + + 插件ID + + + + + + + + + + + + + + + + 插件类型: + {{ TypeMap[modelRef.type] }} + + + 版本: + {{ modelRef.version }} + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/views/link/plugin/UploadFile.vue b/src/views/link/plugin/UploadFile.vue new file mode 100644 index 00000000..c96f619d --- /dev/null +++ b/src/views/link/plugin/UploadFile.vue @@ -0,0 +1,92 @@ + + + + 上传文件 + 格式要求:{文件名}.jar/{文件名}.zip + + + + + + + + diff --git a/src/views/link/plugin/index.vue b/src/views/link/plugin/index.vue new file mode 100644 index 00000000..15331fe8 --- /dev/null +++ b/src/views/link/plugin/index.vue @@ -0,0 +1,274 @@ + + + + + + + + + 新增 + + + + + + + + + + + + + {{ slotProps.version }} + + + + {{ slotProps.name }} + + + + + + + 插件ID + + + {{ slotProps.id }} + + + + + 插件类型 + + + {{ TypeMap[slotProps.type] }} + + + + + + + + + + + {{ item?.text }} + + + + + + + {{ TypeMap[slotProps.type] }} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/views/link/plugin/typings.ts b/src/views/link/plugin/typings.ts new file mode 100644 index 00000000..e637fecc --- /dev/null +++ b/src/views/link/plugin/typings.ts @@ -0,0 +1,16 @@ +export type FileUploadResult = { + id: string + name: string + description: string + version: string + type: { + text: string + value: string + } + accessUrl: string + filename: string + extension: string + length: string + md5: string + sha256: string +} \ No newline at end of file diff --git a/src/views/link/plugin/util.ts b/src/views/link/plugin/util.ts new file mode 100644 index 00000000..62ddc019 --- /dev/null +++ b/src/views/link/plugin/util.ts @@ -0,0 +1,4 @@ +export const TypeMap = { + 'deviceGateway': '设备接入网关', + 'thingsManager': '物管理', +} \ No newline at end of file diff --git a/src/views/rule-engine/Alarm/Config/Io/index.vue b/src/views/rule-engine/Alarm/Config/Io/index.vue index cfd6b37d..4586c557 100644 --- a/src/views/rule-engine/Alarm/Config/Io/index.vue +++ b/src/views/rule-engine/Alarm/Config/Io/index.vue @@ -185,7 +185,7 @@