feat: 运行状态

This commit is contained in:
100011797 2023-02-01 17:46:25 +08:00
parent 1eda294d48
commit 68eaa0ac76
11 changed files with 686 additions and 119 deletions

View File

@ -114,6 +114,14 @@ export const isExists = (id: string) => server.get(`/device-instance/${id}/exist
*/
export const update = (data: Partial<DeviceInstance>) => data.id ? server.patch(`/device-instance`, data) : server.post(`/device-instance`, data)
/**
*
* @param id id
* @param data
* @returns
*/
export const modify = (id: string, data: Partial<DeviceInstance>) => server.put(`/device-instance/${id}`, data)
/**
*
* @param id id

View File

@ -45,6 +45,7 @@ const iconKeys = [
'InfoCircleOutlined',
'SearchOutlined',
'EllipsisOutlined',
'ClockCircleOutlined'
]
const Icon = (props: {type: string}) => {

View File

@ -1,9 +1,9 @@
.jtable-body {
width: 100%;
padding: 0 24px 24px;
padding: 16px 24px 24px;
background-color: white;
.jtable-body-header {
padding: 16px 0;
padding-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;

View File

@ -40,11 +40,11 @@ export interface ActionsType {
children?: ActionsType[];
}
export interface JColumnProps extends ColumnProps{
export interface JColumnProps extends ColumnProps {
scopedSlots?: boolean; // 是否为插槽 true: 是 false: 否
}
export interface JTableProps extends TableProps{
export interface JTableProps extends TableProps {
request?: (params?: Record<string, any>) => Promise<Partial<RequestData>>;
cardBodyClass?: string;
columns: JColumnProps[];
@ -53,8 +53,8 @@ export interface JTableProps extends TableProps{
// actions?: ActionsType[];
noPagination?: boolean;
rowSelection?: TableProps['rowSelection'];
cardProps?: Record<string, any>;
dataSource?: Record<string, any>[];
cardProps?: Record<string, any>;
dataSource?: Record<string, any>[];
gridColumn?: number;
/**
*
@ -62,10 +62,11 @@ export interface JTableProps extends TableProps{
* gridColumns[1] 1440 ~ 1600
* gridColumns[2] > 1600
*/
gridColumns?: number[];
alertRender?: boolean;
type?: keyof typeof TypeEnum;
defaultParams?: Record<string, any>;
gridColumns?: number[];
alertRender?: boolean;
type?: keyof typeof TypeEnum;
defaultParams?: Record<string, any>;
bodyStyle?: Record<string, any>;
}
const JTable = defineComponent<JTableProps>({
@ -88,13 +89,17 @@ const JTable = defineComponent<JTableProps>({
type: String,
default: ''
},
bodyStyle: {
type: Object,
default: {}
},
columns: {
type: Array,
default: () => []
},
params: {
type: Object,
default: () => {}
default: () => { }
},
model: {
type: [String, undefined],
@ -142,7 +147,7 @@ const JTable = defineComponent<JTableProps>({
}
}
} as any,
setup(props: JTableProps ,{ slots, emit, expose }){
setup(props: JTableProps, { slots, emit, expose }) {
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
const _model = ref<keyof typeof ModelEnum>(props.model ? props.model : ModelEnum.CARD); // 模式切换
const column = ref<number>(props.gridColumn || 4);
@ -174,7 +179,7 @@ const JTable = defineComponent<JTableProps>({
*/
const handleSearch = async (_params?: Record<string, any>) => {
loading.value = true
if(props.request) {
if (props.request) {
const resp = await props.request({
pageIndex: 0,
pageSize: 12,
@ -185,14 +190,14 @@ const JTable = defineComponent<JTableProps>({
...(_params?.terms || [])
]
})
if(resp.status === 200){
if(props.type === 'PAGE'){
if (resp.status === 200) {
if (props.type === 'PAGE') {
// 判断如果是最后一页且最后一页为空,就跳转到前一页
if(resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) {
if (resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) {
handleSearch({
..._params,
pageSize: pageSize.value,
pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0,
pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0,
})
} else {
_dataSource.value = resp.result?.data || []
@ -204,10 +209,9 @@ const JTable = defineComponent<JTableProps>({
_dataSource.value = resp?.result || []
}
} else {
_dataSource.value = []
_dataSource.value = []
}
} else {
console.log(props?.dataSource)
_dataSource.value = props?.dataSource || []
}
loading.value = false
@ -218,17 +222,17 @@ const JTable = defineComponent<JTableProps>({
(newValue) => {
handleSearch(newValue)
},
{deep: true, immediate: true}
{ deep: true, immediate: true }
)
watch(
() => props.dataSource,
(newValue) => {
if(props.dataSource){
() => {
if (props.dataSource && !props.request) {
handleSearch(props.params)
}
},
{deep: true, immediate: true}
{ deep: true, immediate: true }
)
onMounted(() => {
@ -241,6 +245,10 @@ const JTable = defineComponent<JTableProps>({
window.onresize = null
})
watchEffect(() => {
// console.log(props.bodyStyle)
})
/**
*
* @param _params
@ -256,10 +264,10 @@ const JTable = defineComponent<JTableProps>({
/**
*
*/
expose({ reload })
expose({ reload })
return () => <Spin spinning={loading.value}>
<div class={styles["jtable-body"]}>
<div class={styles["jtable-body"]} style={{ ...props.bodyStyle }}>
<div class={styles["jtable-body-header"]}>
<div class={styles["jtable-body-header-left"]}>
{/* 顶部左边插槽 */}
@ -278,7 +286,7 @@ const JTable = defineComponent<JTableProps>({
<div class={[styles["jtable-setting-item"], ModelEnum.TABLE === _model.value ? styles['active'] : '']} onClick={() => {
_model.value = ModelEnum.TABLE
}}>
<UnorderedListOutlined />
<UnorderedListOutlined />
</div>
</div>
}
@ -288,58 +296,58 @@ const JTable = defineComponent<JTableProps>({
<div class={styles['jtable-content']}>
{
props.alertRender && props?.rowSelection && props?.rowSelection?.selectedRowKeys && props.rowSelection.selectedRowKeys?.length ?
<div class={styles['jtable-alert']}>
<Alert
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
type="info"
onClose={() => {
emit('cancelSelect')
}}
closeText={<a-button type="link"></a-button>}
/>
</div> : null
<div class={styles['jtable-alert']}>
<Alert
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
type="info"
onClose={() => {
emit('cancelSelect')
}}
closeText={<a-button type="link"></a-button>}
/>
</div> : null
}
{
_model.value === ModelEnum.CARD ?
<div class={styles['jtable-card']}>
{
_dataSource.value.length ?
<div
class={styles['jtable-card-items']}
style={{gridTemplateColumns: `repeat(${column.value}, 1fr)`}}
>
{
_dataSource.value.map(item => slots.card ?
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
{slots.card(item)}
</div> : null
)
}
</div> :
<div><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>
}
</div> :
<div>
<Table
dataSource={_dataSource.value}
columns={_columns.value}
pagination={false}
rowKey="id"
rowSelection={props.rowSelection}
scroll={{x: 1366}}
v-slots={{
bodyCell: (dt: Record<string, any>) => {
const {column, record} = dt;
if((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
const _key = column?.key || column?.dataIndex
return slots?.[_key]!(record)
} else {
return record?.[column?.dataIndex] || ''
<div class={styles['jtable-card']}>
{
_dataSource.value.length ?
<div
class={styles['jtable-card-items']}
style={{ gridTemplateColumns: `repeat(${column.value}, 1fr)` }}
>
{
_dataSource.value.map(item => slots.card ?
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
{slots.card(item)}
</div> : null
)
}
</div> :
<div><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>
}
</div> :
<div>
<Table
dataSource={_dataSource.value}
columns={_columns.value}
pagination={false}
rowKey="id"
rowSelection={props.rowSelection}
scroll={{ x: 1366 }}
v-slots={{
bodyCell: (dt: Record<string, any>) => {
const { column, record } = dt;
if ((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
const _key = column?.key || column?.dataIndex
return slots?.[_key]!(record)
} else {
return record?.[column?.dataIndex] || ''
}
}
}
}}
/>
</div>
}}
/>
</div>
}
</div>
{/* 分页 */}

View File

@ -0,0 +1,105 @@
<template>
<a-drawer placement="right" :closable="false" :visible="true">
<template #title>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
"
>
<span
><AIcon
type="CloseOutlined"
style="margin-right: 5px"
@click="onClose"
/></span
>
<a-button type="primary" @click="saveBtn">保存</a-button>
</div>
</template>
<a-form layout="vertical" ref="formRef" :model="modelRef">
<template v-for="(item, index) in props.config" :key="index">
<a-form-item
:name="item.property"
v-for="i in item.properties"
:key="i.property"
>
<template #label>
<span style="margin-right: 5px">{{ i.name }}</span>
<a-tooltip v-if="i.description" :title="i.description"
><AIcon type="QuestionCircleOutlined"
/></a-tooltip>
</template>
<ValueItem
v-model:modelValue="modelRef[i.property]"
:itemType="i.type.type"
:options="
i.type.type === 'enum'
? (i.type?.elements || []).map((item) => {
return {
label: item?.text,
value: item?.value,
};
})
: undefined
"
/>
</a-form-item>
</template>
</a-form>
</a-drawer>
</template>
<script lang="ts" setup>
import { modify } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import { message } from 'ant-design-vue';
const emit = defineEmits(['close', 'save']);
const formRef = ref();
const modelRef = reactive({});
const instanceStore = useInstanceStore();
const props = defineProps({
config: {
type: Array,
default: []
}
})
watchEffect(() => {
const obj = instanceStore.current?.configuration
if(obj && Object.keys(obj).length) {
(props?.config || []).map((item: any) => {
if(Array.isArray(item.properties) && item?.properties.length){
item.properties.map((i: any) => {
modelRef[i.property] = obj[i.property]
})
}
})
}
})
const onClose = () => {
emit('close');
formRef.value.resetFields();
};
const saveBtn = () => {
formRef.value.validate().then(async () => {
const values = toRaw(modelRef);
const resp = await modify(instanceStore.current?.id || '', {
id: instanceStore.current?.id,
configuration: { ...values }
})
if(resp.status === 200){
message.success('操作成功!')
emit('save');
}
});
};
</script>

View File

@ -1,6 +1,6 @@
<template>
<div style="margin-top: 20px" v-if="config.length">
<div style="display: flex;">
<div style="display: flex; margin-bottom: 20px; align-items: center;">
<div style="font-size: 16px; font-weight: 700">配置</div>
<a-space>
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
@ -13,11 +13,11 @@
</a-space>
</div>
<a-descriptions bordered size="small" v-for="i in config" :key="i.name">
<template #title><h4>{{i.name}}</h4></template>
<template #title><h4 style="font-size: 15px">{{i.name}}</h4></template>
<a-descriptions-item v-for="item in i.properties" :key="item.property">
<template #label>
<span style="margin-right: 5px">{{item.name}}</span>
<a-tooltip v-if="item.description" :title="item.description"><AIcon type="QuestionCircleOutlined" /></a-tooltip>
<span>{{item.name}}</span>
</template>
<span v-if="item.type.type === 'password' && instanceStore.current?.configuration?.[item.property]?.length > 0">******</span>
<span v-else>
@ -26,6 +26,7 @@
</span>
</a-descriptions-item>
</a-descriptions>
<Save v-if="visible" @save="saveBtn" @close="visible = false" :config="config" />
</div>
</template>
@ -34,6 +35,7 @@ import { useInstanceStore } from "@/store/instance"
import { ConfigMetadata } from "@/views/device/Product/typings"
import { getConfigMetadata, _deploy, configurationReset } from '@/api/device/instance'
import { message } from "ant-design-vue"
import Save from './Save.vue'
const instanceStore = useInstanceStore()
const visible = ref<boolean>(false)
@ -78,4 +80,11 @@ const resetBtn = async () => {
}
}
}
const saveBtn = () => {
visible.value = false
if(instanceStore.current.id){
instanceStore.refresh(instanceStore.current.id)
}
}
</script>

View File

@ -0,0 +1,68 @@
<template>
<Search :columns="columns" target="device-instance-running-events" />
<JTable
ref="eventsRef"
:columns="columns"
:dataSource="dataSource"
model="TABLE"
:bodyStyle="{padding: '0 24px'}"
>
<template #timestamp="slotProps">
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template #action="slotProps">
<a-button type="link" @click="detail(slotProps)">
<AIcon type="SearchOutlined" />
</a-button>
</template>
</JTable>
</template>
<script lang="ts" setup>
import moment from 'moment'
const events = defineProps({
data: {
type: Object,
default: () => {}
}
})
const columns = ref<Record<string, any>>([
{
title: '时间',
dataIndex: 'timestamp',
key: 'timestamp',
scopedSlots: true,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
}
])
const dataSource = ref<Record<string, any>[]>([])
watchEffect(() => {
if(events.data?.valueType?.type === 'object'){
(events.data.valueType?.properties || []).map((i: any) => {
columns.value.splice(0, 0, {
key: i.id,
title: i.name,
dataIndex: `${i.id}_format`,
// renderText: (text) => (typeof text === 'object' ? JSON.stringify(text) : text),
})
})
} else {
columns.value.splice(0, 0, {
title: '数据',
dataIndex: 'value',
})
}
})
const detail = () => {
}
</script>

View File

@ -0,0 +1,114 @@
<template>
<a-card :hoverable="true" class="card-box">
<a-spin :spinning="loading">
<div class="card-container">
<div class="header">
<div class="title">{{ _props.data.name }}</div>
<div class="extra">
<a-space>
<a-tooltip title="设置属性至设备" v-if="data.expands?.type?.includes('write')">
<AIcon
type="EditOutlined"
style="font-size: 12px"
/>
</a-tooltip>
<a-tooltip title="指标" v-if="(data.expands?.metrics || []).length > 0 &&
['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
data.valueType?.type || '',
)">
<AIcon
type="ClockCircleOutlined"
style="font-size: 12px"
/>
</a-tooltip>
<a-tooltip title="获取最新属性值" v-if="data.expands?.type?.includes('read')">
<AIcon
type="SyncOutlined"
style="font-size: 12px"
/>
</a-tooltip>
<a-tooltip title="详情">
<AIcon
type="BarsOutlined"
style="font-size: 12px"
/>
</a-tooltip>
</a-space>
</div>
</div>
<div class="value">
<ValueRender :data="data" />
</div>
<div class="bottom">
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
<div class="time-value">{{data?.time || '--'}}</div>
</div>
</div>
</a-spin>
</a-card>
</template>
<script lang="ts" setup>
import ValueRender from './ValueRender.vue'
const _props = defineProps({
data: {
type: Object,
default: () => {},
},
});
const loading = ref<boolean>(true);
watchEffect(() => {
if (_props.data.name) {
loading.value = false;
}
});
</script>
<style lang="less" scoped>
.card-box {
background-color: rgba(0, 0, 0, 0.02);
width: 100%;
.card-container {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 154px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.title {
width: 60%;
margin-right: 10px;
overflow: hidden;
color: rgba(0, 0, 0, 0.65);
font-weight: 400;
font-size: 12px;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.value {
height: 60px;
line-height: 60px;
color: #323130;
font-weight: 700;
font-size: 24px;
margin: 10px 0;
}
.bottom {
.time-value {
margin-top: 5px;
font-size: 16px;
min-height: 25px;
color: #000;
}
}
}
}
</style>

View File

@ -0,0 +1,14 @@
<template>
<div>
{{data.value || '--'}}
</div>
</template>
<script lang="ts" setup>
const _data = defineProps({
data: {
type: Object,
default: () => {},
},
});
</script>

View File

@ -0,0 +1,166 @@
<template>
<JTable
ref="metadataRef"
:columns="columns"
:dataSource="dataSource"
:bodyStyle="{padding: 0}"
>
<template #headerTitle>
<a-input-search
placeholder="请输入名称"
style="width: 300px; margin-bottom: 10px"
@search="onSearch"
/>
</template>
<template #card="slotProps">
<PropertyCard :data="slotProps" />
</template>
<template #value="slotProps">
<ValueRender :data="slotProps" />
</template>
<template #time="slotProps">
{{slotProps.time || '--'}}
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps)"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-popconfirm>
<a-button
style="padding: 0"
type="link"
v-else
@click="i.onClick && i.onClick(slotProps)"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
</template>
</JTable>
</template>
<script lang="ts" setup>
import { PropertyData } from "../../../typings"
import PropertyCard from './PropertyCard.vue'
import ValueRender from './ValueRender.vue'
const columns = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
},
{
title: '值',
dataIndex: 'value',
key: 'value',
scopedSlots: true
},
{
title: '更新时间',
dataIndex: 'time',
key: 'time',
scopedSlots: true,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
},
]
const _data = defineProps({
data: {
type: Array,
default: () => []
}
})
const dataSource = ref<PropertyData[]>([])
const getActions = (data: Partial<Record<string, any>>) => {
const arr = []
if(data.expands?.type?.includes('write')){
arr.push({
key: 'edit',
tooltip: {
title: '设置属性至设备',
},
icon: 'EditOutlined',
onClick: () => {
},
})
}
if((data.expands?.metrics || []).length && ['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
data.valueType?.type || '',
)){
arr.push({
key: 'metrics',
tooltip: {
title: '指标',
},
icon: 'ClockCircleOutlined',
onClick: () => {
},
})
}
if(data.expands?.type?.includes('read')){
arr.push({
key: 'read',
tooltip: {
title: '获取最新属性值',
},
icon: 'SyncOutlined',
onClick: () => {
},
})
}
arr.push({
key: 'detail',
text: '详情',
tooltip: {
title: '详情',
},
icon: 'BarsOutlined',
onClick: () => {
},
})
return arr
}
watchEffect(() => {
dataSource.value = _data.data as PropertyData[]
})
const onSearch = () => {};
</script>
<style scoped lang="less">
</style>

View File

@ -1,46 +1,120 @@
<template>
<a-card>
<a-row type="flex">
<a-col flex="200px">
<div>
<a-input-search
v-model:value="value"
placeholder="请输入事件名称"
style="width: 200px; margin-bottom: 10px"
@search="onSearch"
<div class="property-box">
<div class="property-box-left">
<a-input-search
v-model:value="value"
placeholder="请输入事件名称"
style="width: 200px; margin-bottom: 10px"
@search="onSearch"
:allowClear="true"
/>
<a-tabs
tab-position="left"
style="height: 600px"
v-model:activeKey="activeKey"
:tabBarStyle="{ width: '200px' }"
@change="tabChange"
>
<a-tab-pane
v-for="i in tabList"
:key="i.key"
:tab="i.tab"
/>
<a-tabs
tab-position="left"
:style="{ height: '600px' }"
v-model:activeKey="activeKey"
tabBarStyle="width: 200px"
>
<a-tab-pane v-for="i in tabList" :key="i.key" :tab="i.tab" />
</a-tabs>
</div>
</a-col>
<a-col flex="auto">
<!-- <component :is="tabs[activeKey]" /> -->
123
</a-col>
</a-row>
</a-tabs>
</div>
<div class="property-box-right">
<Event v-if="type === 'event'" :data="data" />
<Property v-else :data="properties" />
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
const activeKey = ref<string>('property')
const tabList = ref<{key: string, tab: string}[]>([
{
key: 'property',
tab: '属性'
},
{
key: 'event1',
tab: '事件1'
import { useInstanceStore } from '@/store/instance';
import _ from 'lodash';
import Event from './Event/index.vue';
import Property from './Property/index.vue';
const activeKey = ref<string>('property');
const tabList = ref<{ key: string; tab: string; type: 'property' | 'event' }[]>(
[
{
key: 'property',
tab: '属性',
type: 'property',
},
],
);
const type = ref<string>('property');
const data = ref<Record<string, any>>({})
const value = ref<string>('');
const instanceStore = useInstanceStore()
const metadata = JSON.parse(instanceStore.current?.metadata || '{}')
const properties = metadata.properties
const events = metadata.events
watch(() => events, (newVal) => {
if(events && newVal.length){
newVal.map((item: any) => {
tabList.value.push({
...item,
key: item.id,
tab: item.name,
type: 'event',
})
})
}
])
}, {
deep: true,
immediate: true
})
const onSearch = () => {
const arr = [
{
key: 'property',
tab: '属性',
type: 'property',
},
...events.map((item: any) => {
return {
...item,
key: item.id,
tab: item.name,
type: 'event',
}
})
]
if(value.value){
const li = arr.filter((i: any) => {
return i?.tab.indexOf(value.value) !== -1;
})
tabList.value = _.cloneDeep(li)
} else {
tabList.value = _.cloneDeep(arr)
}
};
const tabChange = (key: string) => {
const dt = tabList.value.find((i) => i.key === key);
if (dt) {
data.value = dt
type.value = dt.type;
}
};
}
</script>
<style lang="less" scoped>
.property-box {
display: flex;
.property-box-left {
width: 200px;
margin-right: 20px;
}
.property-box-right {
flex: 1;
}
}
</style>