fix: 设备替换组件

This commit is contained in:
100011797 2023-03-06 17:42:26 +08:00
parent 9d411034f6
commit 4ce8a15b6e
37 changed files with 1061 additions and 559 deletions

View File

@ -1,14 +1,14 @@
<!-- 参数类型输入组件 -->
<template>
<div class="wrapper">
<a-select
<j-select
v-if="typeMap.get(itemType) === 'select'"
v-model:value="myValue"
:options="options"
allowClear
style="width: 100%"
/>
<a-date-picker
<j-date-picker
v-else-if="typeMap.get(itemType) === 'date'"
v-model:value="myValue"
allowClear
@ -17,13 +17,13 @@
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
<a-input-number
<j-input-number
v-else-if="typeMap.get(itemType) === 'inputNumber'"
v-model:value="myValue"
allowClear
style="width: 100%"
/>
<a-input
<j-input
allowClear
v-else-if="typeMap.get(itemType) === 'object'"
v-model:value="myValue"
@ -31,19 +31,19 @@
<template #addonAfter>
<form-outlined @click="modalVis = true" />
</template>
</a-input>
</j-input>
<GeoComponent
v-else-if="typeMap.get(itemType) === 'geoPoint'"
v-model:point="myValue"
/>
<a-input
<j-input
v-else-if="typeMap.get(itemType) === 'file'"
v-model:value="myValue"
placeholder="请输入图片链接"
allowClear
>
<template #addonAfter>
<a-upload
<j-upload
name="file"
:action="FILE_UPLOAD"
:headers="headers"
@ -51,10 +51,10 @@
@change="handleFileChange"
>
<cloud-upload-outlined />
</a-upload>
</j-upload>
</template>
</a-input>
<a-input
</j-input>
<j-input
v-else
allowClear
type="text"
@ -63,7 +63,7 @@
/>
<!-- 代码编辑器弹窗 -->
<a-modal
<j-modal
title="编辑"
ok-text="确认"
cancel-text="取消"
@ -75,13 +75,12 @@
<div style="width: 100%; height: 400px">
<MonacoEditor v-model:modelValue="objectValue" />
</div>
</a-modal>
</j-modal>
</div>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { FormOutlined, CloudUploadOutlined } from '@ant-design/icons-vue';
import { UploadChangeParam, UploadFile } from 'ant-design-vue';
import { DefaultOptionType } from 'ant-design-vue/lib/select';
import MonacoEditor from '@/components/MonacoEditor/index.vue';

View File

@ -16,7 +16,7 @@
<div class="dialog-box">
<div class="dialog-header">
<div class="dialog-title">
<a-badge
<j-badge
:color="
statusColor.get(
item.error ? 'error' : 'success',
@ -41,7 +41,7 @@
class="dialog-editor"
v-if="visible.includes(item.key)"
>
<a-textarea autoSize :bordered="false" :value="item?.detail" />
<j-textarea autoSize :bordered="false" :value="item?.detail" />
</div>
</div>
</div>
@ -50,8 +50,9 @@
</template>
<script lang="ts" setup>
const operationMap = new Map();
import moment from 'moment';
const operationMap = new Map();
operationMap.set('connection', '连接');
operationMap.set('auth', '权限验证');
operationMap.set('decode', '解码');
@ -80,19 +81,9 @@ const getDetail = (item: any) => {
visible.value.splice(index, 1);
}
};
watchEffect(() => {
console.log(props.data)
})
</script>
<style lang="less" scoped>
// @import 'ant-design-vue/es/style/themes/default.less';
// :root {
// --dialog-primary-color: @primary-color;
// }
.dialog-item {
display: flex;
justify-content: flex-start;

View File

@ -1,8 +1,8 @@
<template>
<a-table
<j-table
rowKey="id"
:columns="columns"
:data-source="dataSource"
:datj-source="dataSource"
bordered
:pagination="false"
>
@ -36,7 +36,7 @@
</template>
</div>
</template>
</a-table>
</j-table>
</template>
<script lang="ts" setup>

View File

@ -1,110 +1,110 @@
<template>
<div class="function">
<a-form :layout="'vertical'" ref="formRef" :model="modelRef">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-row :gutter="24">
<j-col :span="6">
<j-form-item
name="type"
:rules="{
required: true,
message: '请选择',
}"
>
<a-select
<j-select
placeholder="请选择"
v-model:value="modelRef.type"
show-search
:filter-option="filterOption"
>
<a-select-option value="READ_PROPERTY"
>读取属性</a-select-option
<j-select-option value="READ_PROPERTY"
>读取属性</j-select-option
>
<a-select-option value="WRITE_PROPERTY"
>修改属性</a-select-option
<j-select-option value="WRITE_PROPERTY"
>修改属性</j-select-option
>
<a-select-option value="INVOKE_FUNCTION"
>调用功能</a-select-option
<j-select-option value="INVOKE_FUNCTION"
>调用功能</j-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col
</j-select>
</j-form-item>
</j-col>
<j-col
:span="6"
v-if="
['READ_PROPERTY', 'WRITE_PROPERTY'].includes(
modelRef.type,
modelRef?.type || '',
)
"
>
<a-form-item
<j-form-item
name="properties"
:rules="{
required: true,
message: '请选择属性',
}"
>
<a-select
<j-select
placeholder="请选择属性"
v-model:value="modelRef.properties"
show-search
:filter-option="filterOption"
>
<a-select-option
<j-select-option
v-for="i in metadata?.properties || []"
:key="i.id"
:value="i.id"
:label="i.name"
>{{ i.name }}</a-select-option
>{{ i.name }}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6" v-if="modelRef.type === 'WRITE_PROPERTY'">
<a-form-item
</j-select>
</j-form-item>
</j-col>
<j-col :span="6" v-if="modelRef.type === 'WRITE_PROPERTY'">
<j-form-item
name="propertyValue"
:rules="{
required: true,
message: '请输入值',
}"
>
<a-input v-model:value="modelRef.propertyValue" />
</a-form-item>
</a-col>
<a-col :span="6" v-if="modelRef.type === 'INVOKE_FUNCTION'">
<a-form-item
<j-input v-model:value="modelRef.propertyValue" />
</j-form-item>
</j-col>
<j-col :span="6" v-if="modelRef.type === 'INVOKE_FUNCTION'">
<j-form-item
name="function"
:rules="{
required: true,
message: '请选择功能',
}"
>
<a-select
<j-select
placeholder="请选择功能"
v-model:value="modelRef.function"
show-search
:filter-option="filterOption"
@change="funcChange"
>
<a-select-option
<j-select-option
v-for="i in metadata?.functions || []"
:key="i.id"
:value="i.id"
:label="i.name"
>{{ i.name }}</a-select-option
>{{ i.name }}</j-select-option
>
</a-select>
</a-form-item>
</a-col>
<a-col :span="4">
<a-button type="primary" @click="saveBtn">发送</a-button>
</a-col>
<a-col
</j-select>
</j-form-item>
</j-col>
<j-col :span="4">
<j-button type="primary" @click="saveBtn">发送</j-button>
</j-col>
<j-col
:span="24"
v-if="
modelRef.type === 'INVOKE_FUNCTION' && modelRef.function && modelRef.inputs.length
"
>
<a-form-item
<j-form-item
name="inputs"
label="参数列表"
:rules="{
@ -113,10 +113,10 @@
}"
>
<EditTable v-model="modelRef.inputs" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</j-form-item>
</j-col>
</j-row>
</j-form>
</div>
</template>

View File

@ -1,3 +1,106 @@
<template>
log
</template>
<div class="log-item" :key="data.id">
<div class="log-card">
<div class="log-icon" @click="visible = !visible">
<AIcon :type="visible ? 'DownOutlined' : 'RightOutlined'" />
</div>
<div class="log-box">
<div class="log-header">
<div class="log-title">
<j-tag color="error">ERROR</j-tag>
{{ operationMap.get(data.operation) }}
</div>
<div class="log-time">
{{ dayjs(data.endTime).format('YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
<div className="log-editor" v-if="visible">
<j-textarea
autoSize
:bordered="false"
:value="data?.detail"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import dayjs from 'dayjs';
const operationMap = new Map();
operationMap.set('connection', '连接');
operationMap.set('auth', '权限验证');
operationMap.set('decode', '解码');
operationMap.set('encode', '编码');
operationMap.set('request', '请求');
operationMap.set('response', '响应');
operationMap.set('downstream', '下行消息');
operationMap.set('upstream', '上行消息');
const visible = ref<boolean>(false);
const props = defineProps({
data: {
type: Object,
default: () => {},
},
});
</script>
<style lang="less" scoped>
.log-item {
display: flex;
justify-content: flex-start;
width: 100%;
padding-bottom: 12px;
.log-card {
display: flex;
width: 100%;
background-color: #fff;
.log-icon {
margin-right: 10px;
color: rgba(0, 0, 0, 0.75);
font-weight: 500;
font-size: 12px;
}
.log-box {
display: flex;
flex-direction: column;
width: 100%;
.log-header {
.log-title {
color: rgba(0, 0, 0, 0.75);
font-weight: 700;
font-size: 14px;
}
.log-time {
color: rgba(0, 0, 0, 0.65);
font-size: 12px;
}
}
.log-editor {
width: 100%;
margin-top: 10px;
color: rgba(0, 0, 0, 0.75);
textarea {
color: black !important;
background-color: #fafafa !important;
}
textarea::-webkit-scrollbar {
width: 5px !important;
}
}
}
}
}
</style>

View File

@ -1,20 +1,20 @@
<template>
<a-row :gutter="24">
<a-col :span="16">
<a-row :gutter="24" style="margin-bottom: 20px">
<a-col :span="12" v-for="item in messageArr" :key="item">
<j-row :gutter="24">
<j-col :span="16">
<j-row :gutter="24" style="margin-bottom: 20px">
<j-col :span="12" v-for="item in messageArr" :key="item">
<div
:style="messageStyleMap.get(item.status)"
class="message-status"
>
<a-badge
<j-badge
:status="messageStatusMap.get(item.status)"
style="margin-right: 5px"
/>
<span>{{ item.text }}</span>
</div>
</a-col>
</a-row>
</j-col>
</j-row>
<div>
<TitleComponent data="调试" />
<div class="content">
@ -26,8 +26,8 @@
</div>
<div><Function /></div>
</div>
</a-col>
<a-col :span="8">
</j-col>
<j-col :span="8">
<div class="right-log">
<TitleComponent data="日志" />
<div :style="{ marginTop: '10px' }">
@ -38,11 +38,11 @@
:key="item.key"
/>
</template>
<a-empty v-else />
<j-empty v-else />
</div>
</div>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
<script lang="ts" setup>

View File

@ -1,7 +1,6 @@
import { Badge, Descriptions, Modal, Tooltip } from "ant-design-vue"
import { Badge, Descriptions, Modal, Tooltip, AIcon, DescriptionsItem } from "jetlinks-ui-components"
import TitleComponent from '@/components/TitleComponent/index.vue'
import styles from './index.module.less'
import AIcon from "@/components/AIcon";
import _ from "lodash";
const DiagnosticAdvice = defineComponent({
@ -42,11 +41,11 @@ const DiagnosticAdvice = defineComponent({
<div style={{ marginTop: 15 }}>
<TitleComponent data="连接信息" />
<Descriptions column={2}>
<Descriptions.Item span={1} label="设备ID">
<DescriptionsItem span={1} label="设备ID">
{data?.info?.id || ''}
</Descriptions.Item>
</DescriptionsItem>
{data?.info?.address?.length > 0 && (
<Descriptions.Item span={1} label="连接地址">
<DescriptionsItem span={1} label="连接地址">
<Tooltip
placement="topLeft"
title={
@ -69,11 +68,11 @@ const DiagnosticAdvice = defineComponent({
))}
</div>
</Tooltip>
</Descriptions.Item>
</DescriptionsItem>
)}
{(_.flatten(_.map(data?.info?.config, 'properties')) || []).map((item: any, index: number) => (
<Descriptions.Item
<DescriptionsItem
key={index}
span={1}
label={
@ -90,7 +89,7 @@ const DiagnosticAdvice = defineComponent({
}
>
{data?.info?.configValue[item?.property] || ''}
</Descriptions.Item>
</DescriptionsItem>
))}
</Descriptions>
</div>

View File

@ -1,7 +1,6 @@
import AIcon from "@/components/AIcon";
import { useInstanceStore } from "@/store/instance";
import { useMenuStore } from "@/store/menu";
import { Button, Descriptions, Modal } from "ant-design-vue"
import { AIcon, Button, Modal, Descriptions, DescriptionsItem, Space } from "jetlinks-ui-components"
import styles from './index.module.less'
const ManualInspection = defineComponent({
@ -39,12 +38,12 @@ const ManualInspection = defineComponent({
<div style={{ marginTop: '10px' }}>
<Descriptions title={data?.data?.name} layout="vertical" bordered>
{(data?.data?.properties || []).map((item: any) => (
<Descriptions.Item
<DescriptionsItem
key={item.property}
label={`${item.name}${item?.description ? `(${item.description})` : ''}`}
>
{data?.configuration[item.property] || ''}
</Descriptions.Item>
</DescriptionsItem>
))}
</Descriptions>
</div>
@ -80,30 +79,30 @@ const ManualInspection = defineComponent({
<Descriptions title={data?.data?.name} layout="vertical" bordered>
{data.configuration?.provider === 'OneNet' ? (
<>
<Descriptions.Item label={'接口地址'}>
<DescriptionsItem label={'接口地址'}>
{data?.configuration?.configuration?.apiAddress || ''}
</Descriptions.Item>
<Descriptions.Item label={'apiKey'}>
</DescriptionsItem>
<DescriptionsItem label={'apiKey'}>
{data?.configuration?.configuration?.apiKey || ''}
</Descriptions.Item>
<Descriptions.Item label={'通知Token'}>
</DescriptionsItem>
<DescriptionsItem label={'通知Token'}>
{data?.configuration?.configuration?.validateToken || ''}
</Descriptions.Item>
<Descriptions.Item label={'aesKey'}>
</DescriptionsItem>
<DescriptionsItem label={'aesKey'}>
{data?.configuration?.configuration?.aesKey || ''}
</Descriptions.Item>
</DescriptionsItem>
</>
) : (
<>
<Descriptions.Item label={'接口地址'}>
<DescriptionsItem label={'接口地址'}>
{data?.configuration?.configuration?.apiAddress || ''}
</Descriptions.Item>
<Descriptions.Item label={'appKey'}>
</DescriptionsItem>
<DescriptionsItem label={'appKey'}>
{data?.configuration?.configuration?.appKey || ''}
</Descriptions.Item>
<Descriptions.Item label={'appSecret'}>
</DescriptionsItem>
<DescriptionsItem label={'appSecret'}>
{data?.configuration?.configuration?.appSecret || ''}
</Descriptions.Item>
</DescriptionsItem>
</>
)}
</Descriptions>
@ -140,45 +139,45 @@ const ManualInspection = defineComponent({
<Descriptions title={data?.data?.name} layout="vertical" bordered>
{data?.configuration?.configuration?.shareCluster ? (
<>
<Descriptions.Item label={'SIP 域'}>
<DescriptionsItem label={'SIP 域'}>
{data?.configuration?.configuration?.domain || ''}
</Descriptions.Item>
<Descriptions.Item label={'SIP ID'}>
</DescriptionsItem>
<DescriptionsItem label={'SIP ID'}>
{data?.configuration?.configuration?.sipId || ''}
</Descriptions.Item>
<Descriptions.Item label={'集群'}>
</DescriptionsItem>
<DescriptionsItem label={'集群'}>
{data?.configuration?.configuration?.shareCluster ? '共享配置' : '独立配置'}
</Descriptions.Item>
<Descriptions.Item label={'SIP 地址'}>
</DescriptionsItem>
<DescriptionsItem label={'SIP 地址'}>
{`${data?.configuration?.configuration?.hostPort?.host}:${data?.configuration?.configuration?.hostPort?.port}`}
</Descriptions.Item>
<Descriptions.Item label={'公网 Host'}>
</DescriptionsItem>
<DescriptionsItem label={'公网 Host'}>
{`${data?.configuration?.configuration?.hostPort?.publicHost}:${data?.configuration?.configuration?.hostPort?.publicPort}`}
</Descriptions.Item>
</DescriptionsItem>
</>
) : (
<>
<Descriptions.Item label={'SIP 域'}>
<DescriptionsItem label={'SIP 域'}>
{data?.configuration?.configuration?.domain || ''}
</Descriptions.Item>
<Descriptions.Item label={'SIP ID'}>
</DescriptionsItem>
<DescriptionsItem label={'SIP ID'}>
{data?.configuration?.configuration?.sipId || ''}
</Descriptions.Item>
<Descriptions.Item label={'集群'}>
</DescriptionsItem>
<DescriptionsItem label={'集群'}>
{data?.configuration?.configuration?.shareCluster ? '共享配置' : '独立配置'}
</Descriptions.Item>
</DescriptionsItem>
{data?.configuration?.configuration?.cluster.map((i: any, it: number) => (
<div key={it}>
<div>{it + 1}</div>
<Descriptions.Item label={'节点名称'}>
<DescriptionsItem label={'节点名称'}>
{i?.clusterNodeId || ''}
</Descriptions.Item>
<Descriptions.Item label={'SIP 地址'}>
</DescriptionsItem>
<DescriptionsItem label={'SIP 地址'}>
{`${i.host}:${i?.port}`}
</Descriptions.Item>
<Descriptions.Item label={'公网 Host'}>
</DescriptionsItem>
<DescriptionsItem label={'公网 Host'}>
{`${i?.publicHost}:${i?.publicPort}`}
</Descriptions.Item>
</DescriptionsItem>
</div>
))}
</>
@ -207,20 +206,29 @@ const ManualInspection = defineComponent({
title="人工检查"
visible
width={1000}
cancelText="去修改"
okText="确认无误"
onOk={() => {
emit('save', data)
}}
onCancel={() => {
if (data.type === 'device') {
instanceStore.tabActiveKey = 'Info'
} else if (data.type === 'product') {
menuStory.jumpPage('device/Product/Detail', { id: data.productId, tab: 'access' });
} else {
menuStory.jumpPage('link/AccessConfig/Detail', { id: data.configuration?.id });
}
}}>
emit('close')
}}
v-slots={{
footer: <Space>
<Button onClick={() => {
if (data.type === 'device') {
instanceStore.tabActiveKey = 'Info'
} else if (data.type === 'product') {
menuStory.jumpPage('device/Product/Detail', { id: data.productId, tab: 'access' });
} else {
menuStory.jumpPage('link/AccessConfig/Detail', { id: data.configuration?.id });
}
}}></Button>
<Button onClick={() => {
emit('save', data)
}}></Button>
</Space>
}}
>
<div style={{ display: 'flex' }}>{dataRender()}</div>
</Modal>
}

View File

@ -1,4 +1,4 @@
import { Badge, Button, message, Popconfirm, Space } from "ant-design-vue"
import { Badge, Button, message, Popconfirm, Space } from "jetlinks-ui-components"
import TitleComponent from '@/components/TitleComponent/index.vue'
import styles from './index.module.less'
import type { ListProps } from './util'
@ -13,6 +13,7 @@ import ManualInspection from './ManualInspection'
import { deployDevice } from "@/api/initHome"
import PermissionButton from '@/components/PermissionButton/index.vue'
import { useMenuStore } from "@/store/menu"
import BindParentDevice from '../../components/BindParentDevice/index.vue'
type TypeProps = 'network' | 'child-device' | 'media' | 'cloud' | 'channel'
@ -1691,6 +1692,102 @@ const Status = defineComponent({
}}
/>
)}
{
bindParentVisible && (
<BindParentDevice
data={device.value}
onCancel={() => {
bindParentVisible.value = false
}}
onOk={async (parentId: string) => {
let item: ListProps | undefined = undefined;
const response = await detail(parentId);
if (response.status === 200) {
if (response?.result?.state?.value === 'notActive') {
item = {
key: 'parent-device',
name: '网关父设备',
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
status: 'error',
text: '异常',
info: (
<div>
<div class={styles.infoItem}>
<Badge
status="default"
text={
<span>
<PermissionButton
hasPermission="device/Product:action"
popConfirm={{
title: '确认启用',
onConfirm: async () => {
const resp = await _deploy(response?.result?.id || '');
if (resp.status === 200) {
message.success('操作成功!');
list.value = modifyArrayList(
list.value,
{
key: 'parent-device',
name: '网关父设备',
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
status: 'success',
text: '正常',
info: null,
},
);
}
}
}}
>
</PermissionButton>
</span>
}
/>
</div>
</div>
),
};
} else if (response?.state?.value === 'online') {
item = {
key: 'parent-device',
name: '网关父设备',
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
status: 'success',
text: '正常',
info: null,
};
} else {
item = {
key: 'parent-device',
name: '网关父设备',
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
status: 'error',
text: '异常',
info: (
<div>
<div class={styles.infoItem}>
<Badge
status="default"
text={<span>线</span>}
/>
</div>
</div>
),
};
}
if (item) {
list.value = modifyArrayList(unref(list), item);
}
instanceStore.current.parentId = parentId;
bindParentVisible.value = false
}
}}
/>
)
}
</div>
},
})

View File

@ -1,5 +1,5 @@
<template>
<a-card>
<j-card>
<div class="diagnose">
<div class="diagnose-header" :style="{background: headerColorMap.get(topState)}">
<div class="diagnose-top">
@ -19,7 +19,7 @@
</div>
</div>
<div class="diagnose-progress">
<a-progress
<j-progress
:percent="percent"
:showInfo="false"
size="small"
@ -38,7 +38,7 @@
<Status v-show="activeKey !== 'message'" :providerType="providerType" @countChange="countChange" @percentChange="percentChange" @stateChange="stateChange" />
</div>
</div>
</a-card>
</j-card>
</template>
<script lang="ts" setup>

View File

@ -19,7 +19,7 @@
</div>
</template>
<j-form layout="vertical" ref="formRef" :model="modelRef">
<template v-for="(item, index) in props.config" :key="index">
<template v-for="(item, index) in (props.config || [])" :key="index">
<j-form-item
:name="item.property"
v-for="i in item.properties"
@ -54,7 +54,7 @@
<script lang="ts" setup>
import { modify } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const emit = defineEmits(['close', 'save']);

View File

@ -97,7 +97,7 @@ import {
_deploy,
configurationReset,
} from '@/api/device/instance';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import Save from './Save.vue';
const instanceStore = useInstanceStore();

View File

@ -46,7 +46,7 @@
<script lang="ts" setup>
import { queryUserListNoPaging, saveRelations } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const emit = defineEmits(['close', 'save']);

View File

@ -49,7 +49,7 @@
<script lang="ts" setup>
import { useInstanceStore } from '@/store/instance';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import _ from 'lodash';
import { saveTags, delTags } from '@/api/device/instance'

View File

@ -1,9 +1,11 @@
<template>
<j-card>
<Search
<j-advanced-search
:columns="columns"
target="device-instance-log"
@search="handleSearch"
type="simple"
class="search"
/>
<JProTable
ref="instanceRefLog"
@ -12,6 +14,7 @@
model="TABLE"
:defaultParams="{ sorts: [{ name: 'timestamp', order: 'desc' }] }"
:params="params"
:bodyStyle="{ padding: 0 }"
>
<template #type="slotProps">
{{ slotProps?.type?.text }}
@ -150,4 +153,7 @@ const handleSearch = (_params: any) => {
</script>
<style lang="less" scoped>
.search {
padding: 0;
}
</style>

View File

@ -1,28 +1,30 @@
<template>
<Search :columns="columns" target="device-instance-running-events" />
<JTable
<j-advanced-search class="search" type="simple" :columns="columns" target="device-instance-running-events" @search="handleSearch" />
<JProTable
ref="eventsRef"
:columns="columns"
:request="_getEventList"
model="TABLE"
:bodyStyle="{ padding: '0 24px' }"
:params="params"
:bodyStyle="{ padding: '0 0 0 24px' }"
>
<template #timestamp="slotProps">
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
{{ dayjs(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #action="slotProps">
<a-button type="link" @click="detail(slotProps)">
<j-button type="link" @click="detail(slotProps)">
<AIcon type="SearchOutlined" />
</a-button>
</j-button>
</template>
</JTable>
</JProTable>
</template>
<script lang="ts" setup>
import moment from 'moment';
import dayjs from 'dayjs';
import { getEventList } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import { Modal } from 'ant-design-vue';
import { Modal } from 'jetlinks-ui-components';
import JsonViewer from 'vue-json-viewer';
const events = defineProps({
data: {
@ -51,11 +53,11 @@ const columns = ref<Record<string, any>>([
]);
const params = ref<Record<string, any>>({});
const _getEventList = () =>
const _getEventList = (_params: any) =>
getEventList(
instanceStore.current.id || '',
events.data.id || '',
params.value,
_params
);
watchEffect(() => {
@ -78,12 +80,25 @@ watchEffect(() => {
}
});
const detail = () => {
const handleSearch = (_params: any) => {
params.value = _params;
};
const detail = (_info: any) => {
Modal.info({
title: () => '详情',
width: 850,
content: () => h('div', {}, [h('p', '暂未开发')]),
content: () => h('JsonViewer', {
'expand-depth': 5,
value: _info
}),
okText: '关闭',
});
};
</script>
</script>
<style lang="less" scoped>
.search {
padding: 0 0 0 24px;
}
</style>

View File

@ -1,36 +1,36 @@
<template>
<a-spin :spinning="loading">
<j-spin :spinning="loading">
<div>
<a-space>
<j-space>
<div>
统计周期
<a-select v-model:value="cycle" style="width: 120px">
<a-select-option value="*" v-if="_type"
>实际值</a-select-option
<j-select v-model:value="cycle" style="width: 120px">
<j-select-option value="*" v-if="_type"
>实际值</j-select-option
>
<a-select-option value="1m">按分钟统计</a-select-option>
<a-select-option value="1h">按小时统计</a-select-option>
<a-select-option value="1d">按天统计</a-select-option>
<a-select-option value="1w">按周统计</a-select-option>
<a-select-option value="1M">按月统计</a-select-option>
</a-select>
<j-select-option value="1m">按分钟统计</j-select-option>
<j-select-option value="1h">按小时统计</j-select-option>
<j-select-option value="1d">按天统计</j-select-option>
<j-select-option value="1w">按周统计</j-select-option>
<j-select-option value="1M">按月统计</j-select-option>
</j-select>
</div>
<div v-if="cycle !== '*' && _type">
统计规则
<a-select v-model:value="agg" style="width: 120px">
<a-select-option value="AVG">平均值</a-select-option>
<a-select-option value="MAX">最大值</a-select-option>
<a-select-option value="MIN">最小值</a-select-option>
<a-select-option value="COUNT">总数</a-select-option>
</a-select>
<j-select v-model:value="agg" style="width: 120px">
<j-select-option value="AVG">平均值</j-select-option>
<j-select-option value="MAX">最大值</j-select-option>
<j-select-option value="MIN">最小值</j-select-option>
<j-select-option value="COUNT">总数</j-select-option>
</j-select>
</div>
</a-space>
</j-space>
</div>
<div style="width: 100%; height: 500px">
<Chart :options="options" v-if="chartsList.length" />
<JEmpty v-else />
</div>
</a-spin>
</j-spin>
</template>
<script lang="ts" setup>

View File

@ -1,17 +1,17 @@
<template>
<a-spin :spinning="loading">
<j-spin :spinning="loading">
<div style="position: relative">
<div style="position: absolute; right: 0; top: 5px; z-index: 999">
<a-space>
<a-button type="primary" @click="onStart">开始动画</a-button>
<a-button type="primary" @click="onStop">停止动画</a-button>
</a-space>
<j-space>
<j-button type="primary" @click="onStart">开始动画</j-button>
<j-button type="primary" @click="onStop">停止动画</j-button>
</j-space>
</div>
</div>
<AMapComponent style="height: 500px">
<PathSimplifier :pathData="geoList" ref="amapPath"></PathSimplifier>
</AMapComponent>
</a-spin>
</j-spin>
</template>
<script lang="ts" setup>

View File

@ -1,6 +1,6 @@
<template>
<div>
<a-table
<j-table
:columns="columns"
size="small"
rowKey="id"
@ -11,7 +11,7 @@
pageSize: dataSource?.pageSize || 10,
showSizeChanger: true,
total: dataSource?.total || 0,
pageSizeOptions: [5, 10, 20, 50],
pageSizeOptions: ['8', '12', '24', '60', '100']
}"
>
<template #bodyCell="{ column, record }">
@ -26,8 +26,8 @@
/>
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button
<j-space>
<j-button
v-if="
showLoad ||
(!getType(record?.value) &&
@ -36,16 +36,16 @@
type="link"
@click="_download(record)"
><AIcon type="DownloadOutlined"
/></a-button>
<a-button type="link" @click="showDetail(record)"
/></j-button>
<j-button type="link" @click="showDetail(record)"
><AIcon type="SearchOutlined"
/></a-button>
</a-space>
/></j-button>
</j-space>
</template>
</template>
</a-table>
</j-table>
</div>
<a-modal
<j-modal
title="详情"
:visible="visible"
@ok="visible = false"
@ -60,13 +60,13 @@
:expand-depth="5"
:value="current.formatValue"
/>
<a-textarea
<j-textarea
v-else-if="data?.valueType?.type === 'file'"
:value="current.formatValue"
:row="3"
/>
<a-input v-else disabled :value="current.formatValue" />
</a-modal>
<j-input v-else disabled :value="current.formatValue" />
</j-modal>
</template>
<script lang="ts" setup>
@ -90,7 +90,12 @@ const _props = defineProps({
});
const instanceStore = useInstanceStore();
const dataSource = ref({});
const dataSource = ref({
pageIndex: 0,
pageSize: 10,
data: [],
total: 0
});
const current = ref<any>({});
const visible = ref<boolean>(false);

View File

@ -1,22 +1,22 @@
<template>
<a-space>
<a-radio-group
<j-space>
<j-radio-group
:value="radioValue"
button-style="solid"
@change="onRadioChange"
>
<a-radio-button value="today">今日</a-radio-button>
<a-radio-button value="week">近一周</a-radio-button>
<a-radio-button value="month">近一月</a-radio-button>
</a-radio-group>
<a-range-picker
<j-radio-button value="today">今日</j-radio-button>
<j-radio-button value="week">近一周</j-radio-button>
<j-radio-button value="month">近一月</j-radio-button>
</j-radio-group>
<j-range-picker
show-time
v-model:value="dateValue"
:placeholder="['开始时间', '结束时间']"
@change="onRangeChange"
:allowClear="false"
/>
</a-space>
</j-space>
</template>
<script lang="ts" setup>

View File

@ -1,20 +1,20 @@
<template>
<a-modal title="详情" visible width="50vw" @ok="onCancel" @cancel="onCancel">
<j-modal title="详情" visible width="50vw" @ok="onCancel" @cancel="onCancel">
<div style="margin-bottom: 10px"><TimeComponent v-model="dateValue" /></div>
<div>
<a-tabs :destroyInactiveTabPane="true" v-model:activeKey="activeKey" style="max-height: 600px; overflow-y: auto">
<a-tab-pane key="table" tab="列表">
<j-tabs :destroyInactiveTabPane="true" v-model:activeKey="activeKey" style="max-height: 600px; overflow-y: auto">
<j-tab-pane key="table" tab="列表">
<Table :data="props.data" :time="_getTimes" />
</a-tab-pane>
<a-tab-pane key="charts" tab="图表">
</j-tab-pane>
<j-tab-pane key="charts" tab="图表">
<Charts :data="props.data" :time="_getTimes" />
</a-tab-pane>
<a-tab-pane key="geo" tab="轨迹" v-if="data?.valueType?.type === 'geoPoint'">
</j-tab-pane>
<j-tab-pane key="geo" tab="轨迹" v-if="data?.valueType?.type === 'geoPoint'">
<PropertyAMap :data="props.data" :time="_getTimes" />
</a-tab-pane>
</a-tabs>
</j-tab-pane>
</j-tabs>
</div>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
:maskClosable="false"
:visible="true"
title="编辑指标"
@ -7,17 +7,32 @@
@cancel="handleCancel"
:confirmLoading="loading"
>
<a-alert message="场景联动页面可引用指标配置触发条件" type="warning" showIcon />
<a-form layout="vertical" ref="formRef" :model="modelRef" style="margin-top: 20px">
<j-alert
message="场景联动页面可引用指标配置触发条件"
type="warning"
showIcon
/>
<j-form
layout="vertical"
ref="formRef"
:model="modelRef"
style="margin-top: 20px"
>
<template v-for="(item, index) in modelRef.metrics" :key="index">
<a-row type="flex" justify="space-between" align="bottom">
<a-col :span="11">
<a-form-item
<j-row type="flex" justify="space-between" :align="'bottom'">
<j-col :span="item.range ? 11 : 24">
<j-form-item
:rules="{
required: true,
message: `${['date', 'boolean'].includes(data?.valueType?.type)? '选择': '输入'}指标值`,
}"
:name="['metrics', index, 'value', 0]"
message: `${
['date', 'boolean'].includes(
data?.valueType?.type,
)
? '选择'
: '输入'
}指标值`,
}"
:name="['metrics', index, 'value', 0]"
:label="item?.name || '指标值'"
>
<ValueItem
@ -26,100 +41,131 @@
:options="
data.valueType?.type === 'boolean'
? [
{
label: data.valueType?.trueText,
value: String(data.valueType?.trueValue),
},
{
label: data.valueType?.falseText,
value: String(data.valueType?.falseValue),
},
]
{
label: data.valueType
?.trueText,
value: String(
data.valueType?.trueValue,
),
},
{
label: data.valueType
?.falseText,
value: String(
data.valueType
?.falseValue,
),
},
]
: undefined
"
/>
</a-form-item>
</a-col>
</j-form-item>
</j-col>
<template v-if="item.range">
<a-col><div class="center-icon">~</div></a-col>
<a-col :span="11">
<a-form-item
:name="['metrics', index, 'value', 1]"
<j-col><div class="center-icon">~</div></j-col>
<j-col :span="11">
<j-form-item
:name="['metrics', index, 'value', 1]"
:rules="{
required: true,
message: `${['date', 'boolean'].includes(data?.valueType?.type)? '选择': '输入'}指标值`,
}"
message: `${
['date', 'boolean'].includes(
data?.valueType?.type,
)
? '选择'
: '输入'
}指标值`,
}"
>
<ValueItem
v-model:modelValue="item.value[1]"
:itemType="data.valueType?.type"
/>
</a-form-item>
</a-col>
</j-form-item>
</j-col>
</template>
</a-row>
</j-row>
</template>
</a-form>
</a-modal>
</j-form>
</j-modal>
</template>
<script lang="ts" setup>
import { queryMetric, saveMetric } from '@/api/device/instance'
const emit = defineEmits(['close']);
import { useInstanceStore } from "@/store/instance"
import { message } from 'ant-design-vue';
import { queryMetric, saveMetric } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import { message } from 'jetlinks-ui-components';
const props = defineProps({
data: {
type: Object,
default: () => {}
}
})
default: () => {},
},
});
const loading = ref<boolean>(false)
const instanceStore = useInstanceStore()
const emit = defineEmits(['close']);
const loading = ref<boolean>(false);
const instanceStore = useInstanceStore();
const formRef = ref();
const modelRef = reactive({
metrics: []
metrics: [],
});
const handleCancel = () => {
emit('close')
}
emit('close');
};
watch(() => props.data.id, (newVal) => {
if(newVal && instanceStore.current.id){
queryMetric(instanceStore.current.id, props.data.id).then(resp => {
if (resp.status === 200) {
if (Array.isArray(resp?.result) && resp?.result.length) {
const list = resp?.result.map((item: any) => {
const val = Array.isArray(item?.value) ? [item?.value] : item?.value?.split(',')
return {
...item,
value: val
};
});
modelRef.metrics = list as any
} else {
const type = props.data.valueType?.type;
if (type === 'boolean') {
const list = props.data.expands?.metrics.map((item: any) => {
const value = (item?.value || {}).map((i: any) => String(i)) || {};
return {
...item,
value,
};
});
modelRef.metrics = list || []
} else {
modelRef.metrics = props.data.expands?.metrics || []
}
}
watch(
() => props.data.id,
(newVal) => {
if (newVal && instanceStore.current.id) {
queryMetric(instanceStore.current.id, props.data.id).then(
(resp) => {
if (resp.status === 200) {
if (
Array.isArray(resp?.result) &&
resp?.result.length
) {
const list = resp?.result.map((item: any) => {
const val = Array.isArray(item?.value)
? [item?.value]
: item?.value?.split(',');
return {
...item,
value: val,
};
});
modelRef.metrics = list as any;
} else {
const type = props.data.valueType?.type;
if (type === 'boolean') {
const list = props.data.expands?.metrics.map(
(item: any) => {
const value =
(item?.value || {}).map((i: any) =>
String(i),
) || {};
return {
...item,
value,
};
},
);
modelRef.metrics = list || [];
} else {
modelRef.metrics =
props.data.expands?.metrics || [];
}
}
}
},
);
}
})
}
}, {immediate: true, deep: true})
},
{ immediate: true, deep: true },
);
const handleSave = () => {
formRef.value
@ -132,19 +178,23 @@ const handleSave = () => {
value: item.value.join(','),
};
});
const resp = await saveMetric(instanceStore.current.id || '', props.data.id || '', list).finally(() => {
loading.value = false
})
const resp = await saveMetric(
instanceStore.current.id || '',
props.data.id || '',
list,
).finally(() => {
loading.value = false;
});
if (resp.status === 200) {
message.success('操作成功!');
emit('close')
emit('close');
formRef.value.resetFields();
}
})
.catch((err: any) => {
console.log('error', err);
});
}
};
</script>
<style lang="less" scoped>

View File

@ -1,17 +1,17 @@
<template>
<a-card :hoverable="true" class="card-box">
<!-- <a-spin :spinning="loading"> -->
<j-card :hoverable="true" class="card-box">
<!-- <j-spin :spinning="loading"> -->
<div class="card-container">
<div class="header">
<div class="title">{{ _props.data.name }}</div>
<div class="extra">
<a-space :size="16">
<j-space :size="16">
<template v-for="i in actions" :key="i.key">
<a-tooltip
<j-tooltip
v-bind="i.tooltip"
v-if="i.key !== 'edit'"
>
<a-button
<j-button
style="padding: 0; margin: 0"
type="link"
:disabled="i.disabled"
@ -21,8 +21,8 @@
:type="i.icon"
style="color: #323130; font-size: 12px"
/>
</a-button>
</a-tooltip>
</j-button>
</j-tooltip>
<PermissionButton
:disabled="i.disabled"
v-else
@ -38,7 +38,7 @@
/></template>
</PermissionButton>
</template>
</a-space>
</j-space>
</div>
</div>
<div class="value">
@ -53,8 +53,8 @@
</div>
</div>
</div>
<!-- </a-spin> -->
</a-card>
<!-- </j-spin> -->
</j-card>
</template>
<script lang="ts" setup>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
:maskClosable="false"
:visible="true"
title="编辑"
@ -7,9 +7,9 @@
@cancel="handleCancel"
:confirmLoading="loading"
>
<a-alert message="当数据来源为设备时,填写的值将下发到设备" type="warning" showIcon />
<a-form :rules="rules" layout="vertical" ref="formRef" :model="modelRef" style="margin-top: 20px">
<a-form-item name="propertyValue" :label="data?.name || '自定义属性'">
<j-alert message="当数据来源为设备时,填写的值将下发到设备" type="warning" showIcon />
<j-form :rules="rules" layout="vertical" ref="formRef" :model="modelRef" style="margin-top: 20px">
<j-form-item name="propertyValue" :label="data?.name || '自定义属性'">
<ValueItem
v-model:modelValue="modelRef.propertyValue"
:itemType="data?.valueType?.type || data?.dataType"
@ -24,16 +24,15 @@
: undefined
"
/>
</a-form-item>
</a-form>
</a-modal>
</j-form-item>
</j-form>
</j-modal>
</template>
<script lang="ts" setup>
import { setProperty } from '@/api/device/instance'
const emit = defineEmits(['close']);
import { useInstanceStore } from "@/store/instance"
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const props = defineProps({
data: {
@ -42,6 +41,8 @@ const props = defineProps({
}
})
const emit = defineEmits(['close']);
const loading = ref<boolean>(false)
const instanceStore = useInstanceStore()

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
:maskClosable="false"
width="600px"
:visible="true"
@ -10,7 +10,7 @@
@cancel="handleCancel"
>
<template v-if="['.jpg', '.png'].includes(type)">
<a-image :src="value?.formatValue" />
<j-image :src="value?.formatValue" />
</template>
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
</template>
@ -20,7 +20,7 @@
:value="value?.formatValue"
/>
</template>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -1,59 +1,110 @@
<template>
<div class="value">
<div v-if="value?.formatValue !== 0 && !value?.formatValue" :class="valueClass">--</div>
<div
v-if="value?.formatValue !== 0 && !value?.formatValue"
:class="valueClass"
>
--
</div>
<div v-else-if="_data.data?.valueType?.type === 'file'">
<template v-if="data?.valueType?.fileType === 'base64'">
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
<img :src="imgMap.get(_type)" @error="onError" />
</div>
<div v-else :class="valueClass">
<img :src="imgMap.get('other')" />
</div>
</template>
<div v-else-if="data?.valueType?.fileType === 'Binary(二进制)'" :class="valueClass">
<img :src="imgMap.get('other')" />
</div>
<template v-else>
<template v-if="imgList.some((item) => value?.formatValue.includes(item))">
<div :class="valueClass" @click="getDetail('img')">
<img :src="value?.formatValue" @error="imgError" />
</div>
<template v-if="data?.valueType?.fileType === 'base64'">
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
<img :src="imgMap.get(_type)" @error="onError" />
</div>
<div v-else :class="valueClass">
<img :src="imgMap.get('other')" />
</div>
</template>
<template v-else-if="videoList.some((item) => value?.formatValue.includes(item))">
<div :class="valueClass" @click="getDetail('video')">
<img :src="imgMap.get('video')" />
</div>
</template>
<template v-else-if="fileList.some((item) => value?.formatValue.includes(item))">
<div :class="valueClass">
<img :src="imgMap.get(fileList.find((item) => value?.formatValue.includes(item)).slice(1))" />
</div>
</template>
<template v-else>
<div :class="valueClass">
<div
v-else-if="data?.valueType?.fileType === 'Binary(二进制)'"
:class="valueClass"
>
<img :src="imgMap.get('other')" />
</div>
</div>
<template v-else>
<template
v-if="
imgList.some((item) =>
value?.formatValue.includes(item),
)
"
>
<div :class="valueClass" @click="getDetail('img')">
<img :src="value?.formatValue" @error="imgError" />
</div>
</template>
<template
v-else-if="
videoList.some((item) =>
value?.formatValue.includes(item),
)
"
>
<div :class="valueClass" @click="getDetail('video')">
<img :src="imgMap.get('video')" />
</div>
</template>
<template
v-else-if="
fileList.some((item) =>
value?.formatValue.includes(item),
)
"
>
<div :class="valueClass">
<img
:src="
imgMap.get(
fileList
.find((item) =>
value?.formatValue.includes(item),
)
.slice(1),
)
"
/>
</div>
</template>
<template v-else>
<div :class="valueClass">
<img :src="imgMap.get('other')" />
</div>
</template>
</template>
</template>
</div>
<div v-else-if="_data.data?.valueType?.type === 'object'" @click="getDetail('obj')" :class="valueClass">
<img :src="imgMap.get('obj')" />
<div
v-else-if="_data.data?.valueType?.type === 'object'"
@click="getDetail('obj')"
:class="valueClass"
>
<img :src="imgMap.get('obj')" />
</div>
<div v-else-if="_data.data?.valueType?.type === 'geoPoint' || _data.data?.valueType?.type === 'array'" :class="valueClass">
{{JSON.stringify(value?.formatValue)}}
<div
v-else-if="
_data.data?.valueType?.type === 'geoPoint' ||
_data.data?.valueType?.type === 'array'
"
:class="valueClass"
>
{{ JSON.stringify(value?.formatValue) }}
</div>
<div v-else :class="valueClass">
{{String(value?.formatValue)}}
{{ String(value?.formatValue) }}
</div>
<ValueDetail v-if="visible" :type="_types" :value="value" @close="visible = false" />
<ValueDetail
v-if="visible"
:type="_types"
:value="value"
@close="visible = false"
/>
</div>
</template>
<script lang="ts" setup>
import { getImage } from "@/utils/comm";
import { message } from "ant-design-vue";
import ValueDetail from './ValueDetail.vue'
import {getType, imgMap, imgList, videoList, fileList} from './index'
import { getImage } from '@/utils/comm';
import { message } from 'jetlinks-ui-components';
import ValueDetail from './ValueDetail.vue';
import { getType, imgMap, imgList, videoList, fileList } from './index';
const _data = defineProps({
data: {
@ -62,88 +113,99 @@ const _data = defineProps({
},
value: {
type: Object,
default: () => {}
default: () => {},
},
type: {
type: String,
default: 'card'
}
default: 'card',
},
});
const valueClass = computed(() => {
return _data.type === 'card' ? 'cardValue' : 'otherValue'
})
return _data.type === 'card' ? 'cardValue' : 'otherValue';
});
const isHttps = document.location.protocol === 'https:';
const _types = ref<string>('')
const visible = ref<boolean>(false)
const temp = ref<boolean>(false)
const _types = ref<string>('');
const visible = ref<boolean>(false);
const temp = ref<boolean>(false);
const onError = (e: any) => {
e.target.src = imgMap.get('other')
}
e.target.src = imgMap.get('other');
};
const imgError = (e: any) => {
e.target.src = imgMap.get('error')
temp.value = true
}
e.target.src = imgMap.get('error');
temp.value = true;
};
const getDetail = (_type: string) => {
const value = _data.value
let flag: string = ''
if(_type === 'img'){
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
message.error('域名为https时不支持访问http地址');
} else if (temp.value) {
message.error('该图片无法访问');
} else {
flag = ['.jpg', '.png'].find((item) => value?.formatValue.includes(item)) || '--';
const value = _data.value;
let flag: string = '';
if (_type === 'img') {
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
message.error('域名为https时不支持访问http地址');
} else if (temp.value) {
message.error('该图片无法访问');
} else {
flag =
['.jpg', '.png'].find((item) =>
value?.formatValue.includes(item),
) || '--';
_types.value = flag;
visible.value = true;
}
} else if (_type === 'video') {
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
message.error('域名为https时不支持访问http地址');
} else if (
['.rmvb', '.mvb'].some((item) => value?.formatValue.includes(item))
) {
message.error('当前仅支持播放.mp4,.flv,.m3u8格式的视频');
} else {
flag =
['.m3u8', '.flv', '.mp4'].find((item) =>
value?.formatValue.includes(item),
) || '--';
_types.value = flag;
visible.value = true;
}
} else if (_type === 'obj') {
flag = 'obj';
_types.value = flag;
visible.value = true;
}
} else if(_type === 'video'){
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
message.error('域名为https时不支持访问http地址');
} else if (['.rmvb', '.mvb'].some((item) => value?.formatValue.includes(item))) {
message.error('当前仅支持播放.mp4,.flv,.m3u8格式的视频');
} else {
flag = ['.m3u8', '.flv', '.mp4'].find((item) => value?.formatValue.includes(item)) || '--';
}
}else if(_type === 'obj'){
flag = 'obj'
}
_types.value = flag
visible.value = true
}
};
</script>
<style lang="less" scoped>
.value {
display: flex;
align-items: center;
width: 100%;
.cardValue {
display: flex;
align-items: center;
width: 100%;
height: 60px;
overflow: hidden;
color: #323130;
font-weight: 700;
font-size: 24px;
white-space: nowrap;
text-overflow: ellipsis;
img {
width: 60px;
}
}
.cardValue {
display: flex;
align-items: center;
width: 100%;
height: 60px;
overflow: hidden;
color: #323130;
font-weight: 700;
font-size: 24px;
white-space: nowrap;
text-overflow: ellipsis;
.otherValue {
img {
width: 40px;
img {
width: 60px;
}
}
.otherValue {
img {
width: 40px;
}
}
}
}
</style>

View File

@ -2,7 +2,8 @@
<j-spin :spinning="loading">
<JProTable
:columns="columns"
:dataSource="dataSource"
:request="query"
:params="_params"
:bodyStyle="{ padding: '0 0 0 20px' }"
>
<template #headerTitle>
@ -58,26 +59,6 @@
</template>
</j-space>
</template>
<template #paginationRender>
<j-pagination
size="small"
:total="total"
:showQuickJumper="false"
:showSizeChanger="true"
:current="pageIndex + 1"
:pageSize="pageSize"
:pageSizeOptions="['8', '12', '24', '60', '100']"
:show-total="
(num) =>
`${pageIndex * pageSize + 1} - ${
(pageIndex + 1) * pageSize > num
? num
: (pageIndex + 1) * pageSize
} /总共 ${num} `
"
@change="pageChange"
/>
</template>
</JProTable>
</j-spin>
<Save v-if="editVisible" @close="editVisible = false" :data="currentInfo" />
@ -107,6 +88,7 @@ import { message } from 'ant-design-vue';
import { getWebSocket } from '@/utils/websocket';
import { map } from 'rxjs/operators';
import { queryDashboard } from '@/api/comm';
const columns = [
{
title: '名称',
@ -142,9 +124,6 @@ const _data = defineProps({
const value = ref<string>('');
const dataSource = ref<PropertyData[]>([]);
const _dataSource = ref<PropertyData[]>([]);
const pageIndex = ref<number>(0);
const pageSize = ref<number>(8);
const total = ref<number>(0);
const editVisible = ref<boolean>(false); //
const detailVisible = ref<boolean>(false); //
const currentInfo = ref<Record<string, any>>({});
@ -152,6 +131,9 @@ const instanceStore = useInstanceStore();
const indicatorVisible = ref<boolean>(false); //
const loading = ref<boolean>(false);
const propertyValue = ref<Record<string, any>>({});
const _params = reactive({
name: '',
});
const subRef = ref();
@ -309,39 +291,35 @@ const getDashboard = async () => {
loading.value = false;
};
const query = (page: number, size: number, value: string) => {
pageIndex.value = page || 0;
pageSize.value = size || 8;
const _from = pageIndex.value * pageSize.value;
const _to = (pageIndex.value + 1) * pageSize.value;
const arr = _.cloneDeep(_dataSource.value);
if (unref(value)) {
const li = arr.filter((i: any) => {
return i?.name.indexOf(unref(value)) !== -1;
const query = (params: Record<string, any>) =>
new Promise((resolve) => {
const _from = params.pageIndex * params.pageSize;
const _to = (params.pageIndex + 1) * params.pageSize;
let arr = _.cloneDeep(_dataSource.value);
if (params?.name) {
const li = _dataSource.value.filter((i: any) => {
return i?.name.indexOf(params.name) !== -1;
});
arr = _.cloneDeep(li);
}
resolve({
result: {
data: arr.slice(_from, _to),
pageIndex: params.pageIndex || 0,
pageSize: params.pageSize || 12,
total: arr.length,
},
status: 200,
});
dataSource.value = li.slice(_from, _to);
total.value = li.length;
} else {
dataSource.value = arr.slice(_from, _to);
total.value = arr.length;
}
getDashboard();
};
const pageChange = (page: number, size: number) => {
if (size === pageSize.value) {
query(page - 1, size, value.value);
} else {
query(0, size, value.value);
}
};
getDashboard();
});
watch(
() => _data.data,
(newVal) => {
if (newVal.length) {
_dataSource.value = newVal as PropertyData[];
query(0, 8, value.value);
_params.name = '';
}
},
{
@ -351,7 +329,7 @@ watch(
);
const onSearch = () => {
query(0, 8, value.value);
_params.name = value.value;
};
onUnmounted(() => {

View File

@ -0,0 +1,175 @@
<!-- 绑定设备 -->
<template>
<j-modal
:maskClosable="false"
width="1000px"
:visible="true"
title="绑定父设备"
okText="确定"
cancelText="取消"
@ok="handleOk"
@cancel="handleCancel"
:confirmLoading="btnLoading"
>
<j-advanced-search
:columns="columns"
target="child-device-bind"
@search="handleSearch"
type="simple"
/>
<JProTable
ref="bindDeviceRef"
:columns="columns"
:request="query"
model="TABLE"
:bodyStyle="{ padding: '0 0 0 24px' }"
:defaultParams="defaultParams"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
:params="params"
>
<template #registryTime="slotProps">
{{
slotProps.registryTime
? dayjs(slotProps.registryTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}
</template>
<template #state="slotProps">
<j-badge
:text="slotProps.state.text"
:status="statusMap.get(slotProps.state.value)"
/>
</template>
</JProTable>
</j-modal>
</template>
<script setup lang="ts">
import { query, bindDevice } from '@/api/device/instance';
import dayjs from 'dayjs';
import { message } from 'jetlinks-ui-components';
const emit = defineEmits(['cancel', 'ok']);
const bindDeviceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const _selectedRowKeys = ref<string[]>([]);
const btnLoading = ref<boolean>(false);
const props = defineProps({
data: {
type: Object,
default: () => {}
}
})
const statusMap = new Map();
statusMap.set('online', 'success');
statusMap.set('offline', 'error');
statusMap.set('notActive', 'warning');
const defaultParams = {
terms: [
{
column: 'productId$product-info',
value: [
{
column: 'deviceType',
termType: 'eq',
value: 'gateway',
},
],
},
],
};
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '设备名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '所属产品',
dataIndex: 'productName',
key: 'productName',
search: {
type: 'string',
},
},
{
title: '注册时间',
dataIndex: 'registryTime',
key: 'registryTime',
scopedSlots: true,
search: {
type: 'date',
},
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
scopedSlots: true,
search: {
type: 'select',
options: [
{ label: '禁用', value: 'notActive' },
{ label: '离线', value: 'offline' },
{ label: '在线', value: 'online' },
],
},
},
];
const handleSearch = (e: any) => {
params.value = e;
};
const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
const handleOk = () => {
if (_selectedRowKeys.value.length === 0) {
message.warning('请选择需要绑定的设备');
return;
}
btnLoading.value = true;
bindDevice(_selectedRowKeys.value[0], props.data.id, )
.then((resp) => {
if(resp.status === 200){
emit('ok', _selectedRowKeys.value[0]);
message.success('操作成功');
}
})
.finally(() => {
btnLoading.value = false;
});
};
const handleCancel = () => {
emit('cancel', false);
};
</script>
<style scoped lang="less"></style>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
width="900px"
title="批量映射"
visible
@ -10,49 +10,49 @@
<div class="map-tree-top">
采集器的点位名称与属性名称一致时将自动映射绑定有多个采集器点位名称与属性名称一致时以第1个采集器的点位数据进行绑定
</div>
<a-spin :spinning="loading">
<j-spin :spinning="loading">
<div class="map-tree-content">
<a-card class="map-tree-content-card" title="源数据">
<a-tree
<j-card class="map-tree-content-card" title="源数据">
<j-tree
checkable
:height="300"
:tree-data="dataSource"
:checkedKeys="checkedKeys"
@check="onCheck"
/>
</a-card>
</j-card>
<div style="width: 100px">
<a-button
<j-button
:disabled="rightList.length >= leftList.length"
@click="onRight"
>加入右侧</a-button
>加入右侧</j-button
>
</div>
<a-card class="map-tree-content-card" title="采集器">
<a-list
<j-card class="map-tree-content-card" title="采集器">
<j-list
size="small"
:data-source="rightList"
class="map-tree-content-card-list"
>
<template #renderItem="{ item }">
<a-list-item>
<j-list-item>
{{ item.title }}
<template #actions>
<a-popconfirm
<j-popconfirm
title="确定删除?"
@confirm="_delete(item.key)"
>
<AIcon type="DeleteOutlined" />
</a-popconfirm>
</j-popconfirm>
</template>
</a-list-item>
</j-list-item>
</template>
</a-list>
</a-card>
</j-list>
</j-card>
</div>
</a-spin>
</j-spin>
</div>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -1,46 +1,46 @@
<template>
<a-spin :spinning="loading" v-if="metadata.properties.length">
<a-card>
<j-spin :spinning="loading" v-if="metadata.properties.length">
<j-card>
<template #extra>
<a-space>
<a-button @click="visible = true">批量映射</a-button>
<a-button type="primary" @click="onSave">保存</a-button>
</a-space>
<j-space>
<j-button @click="visible = true">批量映射</j-button>
<j-button type="primary" @click="onSave">保存</j-button>
</j-space>
</template>
<a-form ref="formRef" :model="modelRef">
<a-table :dataSource="modelRef.dataSource" :columns="columns">
<j-form ref="formRef" :model="modelRef">
<j-table :dataSource="modelRef.dataSource" :columns="columns">
<template #headerCell="{ column }">
<template v-if="column.key === 'collectorId'">
采集器
<a-tooltip title="数据采集中配置的真实物理设备">
<j-tooltip title="数据采集中配置的真实物理设备">
<AIcon type="QuestionCircleOutlined" />
</a-tooltip>
</j-tooltip>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.dataIndex === 'channelId'">
<a-form-item
<j-form-item
:name="['dataSource', index, 'channelId']"
>
<a-select
<j-select
style="width: 100%"
v-model:value="record[column.dataIndex]"
placeholder="请选择"
allowClear
:filter-option="filterOption"
>
<a-select-option
<j-select-option
v-for="item in channelList"
:key="item.value"
:value="item.value"
:label="item.label"
>{{ item.label }}</a-select-option
>{{ item.label }}</j-select-option
>
</a-select>
</a-form-item>
</j-select>
</j-form-item>
</template>
<template v-if="column.dataIndex === 'collectorId'">
<a-form-item
<j-form-item
:name="['dataSource', index, 'collectorId']"
:rules="[
{
@ -54,10 +54,10 @@
:id="record.channelId"
type="COLLECTOR"
/>
</a-form-item>
</j-form-item>
</template>
<template v-if="column.dataIndex === 'pointId'">
<a-form-item
<j-form-item
:name="['dataSource', index, 'pointId']"
:rules="[
{
@ -71,33 +71,33 @@
:id="record.collectorId"
type="POINT"
/>
</a-form-item>
</j-form-item>
</template>
<template v-if="column.dataIndex === 'id'">
<a-badge
<j-badge
v-if="record[column.dataIndex]"
status="success"
text="已绑定"
/>
<a-badge v-else status="error" text="未绑定" />
<j-badge v-else status="error" text="未绑定" />
</template>
<template v-if="column.key === 'action'">
<a-tooltip title="解绑">
<a-popconfirm
<j-tooltip title="解绑">
<j-popconfirm
title="确认解绑"
:disabled="!record.id"
@confirm="unbind(record.id)"
>
<a-button type="link" :disabled="!record.id"
<j-button type="link" :disabled="!record.id"
><AIcon type="icon-jiebang"
/></a-button>
</a-popconfirm>
</a-tooltip>
/></j-button>
</j-popconfirm>
</j-tooltip>
</template>
</template>
</a-table>
</a-form>
</a-card>
</j-table>
</j-form>
</j-card>
<PatchMapping
:deviceId="instanceStore.current.id"
v-if="visible"
@ -106,10 +106,10 @@
:type="provider"
:metaData="modelRef.dataSource"
/>
</a-spin>
<a-card v-else>
</j-spin>
<j-card v-else>
<JEmpty description='暂无数据,请配置物模型' style="margin: 10% 0" />
</a-card>
</j-card>
</template>
<script lang="ts" setup>

View File

@ -1,14 +1,14 @@
<template>
<a-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
<a-select-option
<j-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
<j-select-option
v-for="item in list"
:key="item.id"
:value="item.id"
:label="item.name"
:filter-option="filterOption"
>{{ item.name }}</a-select-option
>{{ item.name }}</j-select-option
>
</a-select>
</j-select>
</template>
<script lang="ts" setup>

View File

@ -10,9 +10,9 @@
<div style="display: flex; align-items: center">
<AIcon type="ArrowLeftOutlined" @click="onBack" />
<div style="margin-left: 20px">{{ instanceStore.current.name }}</div>
<a-divider type="vertical" />
<a-space>
<a-badge
<j-divider type="vertical" />
<j-space>
<j-badge
:text="instanceStore.current.state?.text"
:status="
statusMap.get(
@ -49,7 +49,7 @@
>
断开连接
</PermissionButton>
<a-tooltip
<j-tooltip
v-if="
instanceStore.current?.accessProvider ===
'child-device' &&
@ -68,15 +68,15 @@
type="QuestionCircleOutlined"
style="font-size: 14px"
/>
</a-tooltip>
</a-space>
</j-tooltip>
</j-space>
</div>
<div style="padding-top: 10px">
<a-descriptions size="small" :column="4">
<a-descriptions-item label="ID">{{
<j-descriptions size="small" :column="4">
<j-descriptions-item label="ID">{{
instanceStore.current.id
}}</a-descriptions-item>
<a-descriptions-item label="所属产品">
}}</j-descriptions-item>
<j-descriptions-item label="所属产品">
<PermissionButton
type="link"
style="margin-top: -5px; padding: 0"
@ -85,8 +85,8 @@
>
{{ instanceStore.current.productName }}
</PermissionButton>
</a-descriptions-item>
</a-descriptions>
</j-descriptions-item>
</j-descriptions>
</div>
</div>
</template>
@ -119,7 +119,7 @@ import EdgeMap from './EdgeMap/index.vue';
import Parsing from './Parsing/index.vue'
import Log from './Log/index.vue'
import { _deploy, _disconnect } from '@/api/device/instance';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm';
import { getWebSocket } from '@/utils/websocket';
import { useMenuStore } from '@/store/menu';

View File

@ -64,6 +64,6 @@ const handleCancel = () => {
}
const handleSave = () => {
emit('close')
emit('save')
}
</script>

View File

@ -1,12 +1,12 @@
<template>
<a-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleCancel" @cancel="handleCancel">
<j-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleCancel" @cancel="handleCancel">
<div>
<a-badge v-if="flag" status="processing" text="进行中" />
<a-badge v-else status="success" text="已完成" />
<j-badge v-if="flag" status="processing" text="进行中" />
<j-badge v-else status="success" text="已完成" />
</div>
<p>总数量{{count}}</p>
<a style="color: red">{{errMessage}}</a>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>

View File

@ -88,7 +88,7 @@
import { queryNoPagingPost } from '@/api/device/product';
import { isExists, update } from '@/api/device/instance';
import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const emit = defineEmits(['close', 'save']);
const props = defineProps({

View File

@ -156,7 +156,10 @@
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600" @click.stop="handleView(slotProps.id)">
<span
style="font-size: 16px; font-weight: 600"
@click.stop="handleView(slotProps.id)"
>
{{ slotProps.name }}
</span>
</Ellipsis>
@ -205,6 +208,11 @@
:status="statusMap.get(slotProps.state?.value)"
/>
</template>
<template #createTime="slotProps">
<span>{{
dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
}}</span>
</template>
<template #action="slotProps">
<j-space>
<template
@ -229,11 +237,16 @@
</template>
</JProTable>
</page-container>
<Import v-if="importVisible" @close="importVisible = false" @save="onRefresh" />
<Import
v-if="importVisible"
@close="importVisible = false"
@save="onRefresh"
/>
<Export
v-if="exportVisible"
@close="exportVisible = false"
:data="params"
@save="onRefresh"
/>
<Process
v-if="operationVisible"
@ -260,7 +273,7 @@ import {
batchDeleteDevice,
} from '@/api/device/instance';
import { getImage, LocalStore } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import Import from './Import/index.vue';
import Export from './Export/index.vue';
import Process from './Process/index.vue';
@ -274,9 +287,9 @@ import {
} from '@/api/device/product';
import { queryTree } from '@/api/device/category';
import { useMenuStore } from '@/store/menu';
import { ActionsType } from './typings';
import type { ActionsType } from './typings';
import dayjs from 'dayjs';
const router = useRouter();
const instanceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const _selectedRowKeys = ref<string[]>([]);
@ -699,5 +712,5 @@ const handleSearch = (_params: any) => {
const onRefresh = () => {
instanceRef.value?.reload();
}
};
</script>

View File

@ -3903,8 +3903,8 @@ jetlinks-store@^0.0.3:
jetlinks-ui-components@^1.0.4:
version "1.0.4"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#31f6879f83d394ba45bfd6e94ce0b20de817d490"
integrity sha512-5X26+GsqsduCUSmlaYOAw/MDwKRsZFnifzzRqHcm5qbCi0KrrkID9aKatsMRaeTVEEca1/nzPaPGgXAo6bEpfQ==
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#418d9218c2d0076b4a16606eb87ea755db303409"
integrity sha512-7c509Xx4YVamM/R6Xg62iIOxh2gWz8ASKCN2Pa8lU2CP1n+Vn5Xi5Q0fpd0AiFdi1QadssGaGUkpzbqCqWXLsQ==
dependencies:
"@vueuse/core" "^9.12.0"
ant-design-vue "^3.2.15"