feat: 新增场景联动-触发规则
This commit is contained in:
parent
7d0fbc0e1c
commit
be501e77fe
|
@ -98,7 +98,11 @@ const props = defineProps({
|
|||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
// defaultTerms: {
|
||||
// type: Object,
|
||||
// default: () => ({})
|
||||
// }
|
||||
})
|
||||
|
||||
const searchRef = ref(null)
|
||||
|
@ -223,6 +227,7 @@ const handleParamsFormat = () => {
|
|||
*/
|
||||
const searchSubmit = () => {
|
||||
emit('search', handleParamsFormat())
|
||||
console.log('searchSubmit')
|
||||
if (props.type === 'advanced') {
|
||||
addUrlParams()
|
||||
}
|
||||
|
|
|
@ -144,6 +144,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
pageSize: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll: {
|
||||
type: Object,
|
||||
default: () => { x: 1366 }
|
||||
}
|
||||
} as any,
|
||||
setup(props: JTableProps, { slots, emit, expose }) {
|
||||
|
@ -331,7 +335,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{ x: 1366 }}
|
||||
scroll={props.scroll}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const { column, record } = dt;
|
||||
|
|
|
@ -68,7 +68,7 @@ const defaultOptions = {
|
|||
};
|
||||
|
||||
export const useSceneStore = defineStore('scene', () => {
|
||||
const data = reactive<FormModelType | any>({
|
||||
const data = reactive<FormModelType>({
|
||||
trigger: { type: ''},
|
||||
options: defaultOptions,
|
||||
branches: defaultBranches,
|
||||
|
@ -116,67 +116,3 @@ export const useSceneStore = defineStore('scene', () => {
|
|||
getDetail
|
||||
}
|
||||
})
|
||||
//
|
||||
// export const useSceneStore = defineStore({
|
||||
// id: 'scene',
|
||||
// state: (): DataType => {
|
||||
// return {
|
||||
// data: {
|
||||
// trigger: { type: ''},
|
||||
// options: defaultOptions,
|
||||
// branches: defaultBranches,
|
||||
// description: ''
|
||||
// },
|
||||
// productCache: {}
|
||||
// }
|
||||
// },
|
||||
// actions: {
|
||||
// /**
|
||||
// * 初始化数据
|
||||
// */
|
||||
// initData() {
|
||||
//
|
||||
// },
|
||||
// /**
|
||||
// * 获取详情
|
||||
// * @param id
|
||||
// */
|
||||
// async getDetail(id: string) {
|
||||
// const resp = await detail(id)
|
||||
// if (resp.success) {
|
||||
// const result = resp.result as SceneItem
|
||||
// const triggerType = result.triggerType
|
||||
// let branches: any[] = result.branches
|
||||
//
|
||||
// if (!branches) {
|
||||
// branches = cloneDeep(defaultBranches)
|
||||
// if (triggerType === 'device') {
|
||||
// branches.push(null)
|
||||
// }
|
||||
// } else {
|
||||
// const branchesLength = branches.length;
|
||||
// if (
|
||||
// triggerType === 'device' &&
|
||||
// ((branchesLength === 1 && !!branches[0]?.when?.length) || // 有一组数据并且when有值
|
||||
// (branchesLength > 1 && !branches[branchesLength - 1]?.when?.length)) // 有多组否则数据,并且最后一组when有值
|
||||
// ) {
|
||||
// branches.push(null);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// this.data = {
|
||||
// ...result,
|
||||
// trigger: result.trigger || {},
|
||||
// branches: cloneDeep(assignmentKey(branches)),
|
||||
// options: {...defaultOptions, ...result.options },
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// getProduct() {
|
||||
//
|
||||
// }
|
||||
// },
|
||||
// getters: {
|
||||
//
|
||||
// }
|
||||
// })
|
|
@ -7,4 +7,12 @@ export const isUrl = (path: string): boolean => urlReg.test(path)
|
|||
|
||||
export const inputReg = /^[a-zA-Z0-9_\-]+$/
|
||||
|
||||
export const isInput = (value: string) => inputReg.test(value)
|
||||
export const isInput = (value: string) => inputReg.test(value)
|
||||
|
||||
// cron 表达式
|
||||
|
||||
export const CronRegEx = new RegExp(
|
||||
'^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?)*))$',
|
||||
);
|
||||
|
||||
export const isCron = (value: string) => CronRegEx.test(value)
|
|
@ -6,7 +6,7 @@
|
|||
@click='save'
|
||||
@cancel='cancel'
|
||||
>
|
||||
<a-steps :current='addModel.stepNumber'>
|
||||
<a-steps :current='addModel.stepNumber' @change='stepChange'>
|
||||
<a-step>
|
||||
<template #title>选择产品</template>
|
||||
</a-step>
|
||||
|
@ -17,19 +17,28 @@
|
|||
<template #title>触发类型</template>
|
||||
</a-step>
|
||||
</a-steps>
|
||||
<a-divider style='margin-bottom: 0px' />
|
||||
<div class='steps-content'>
|
||||
<Product :rowKey='addModel.productId' />
|
||||
<Product v-if='addModel.stepNumber === 0' v-model:rowKey='addModel.productId' v-model:detail='addModel.productDetail' />
|
||||
<DeviceSelect
|
||||
v-else-if='addModel.stepNumber === 1'
|
||||
:productId='addModel.productId'
|
||||
v-model:deviceKeys='addModel.deviceKeys'
|
||||
v-model:orgId='addModel.orgId'
|
||||
v-model:selector='addModel.selector'
|
||||
v-model:selectorValues='addModel.selectorValues'
|
||||
/>
|
||||
<Type
|
||||
v-else-if='addModel.stepNumber === 2'
|
||||
:metadata='addModel.metadata'
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class='steps-action'>
|
||||
<template>
|
||||
<a-button v-if='addModel.stepNumber === 0' @click='cancel'>取消</a-button>
|
||||
<a-button v-else>上一步</a-button>
|
||||
</template>
|
||||
<template>
|
||||
<a-button type='primary' v-if='addModel.stepNumber < 2'>下一步</a-button>
|
||||
<a-button type='primary' v-else>确定</a-button>
|
||||
</template>
|
||||
<a-button v-if='addModel.stepNumber === 0' @click='cancel'>取消</a-button>
|
||||
<a-button v-else @click='prev'>上一步</a-button>
|
||||
<a-button type='primary' v-if='addModel.stepNumber < 2' @click='saveClick'>下一步</a-button>
|
||||
<a-button type='primary' v-else @click='saveClick'>确定</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
@ -37,10 +46,12 @@
|
|||
|
||||
<script setup lang='ts' name='AddModel'>
|
||||
import type { PropType } from 'vue'
|
||||
import { TriggerDevice } from '@/views/rule-engine/Scene/typings'
|
||||
import type { metadataType, TriggerDevice } from '@/views/rule-engine/Scene/typings'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
import { detail as deviceDetail } from '@/api/device/instance'
|
||||
import Product from './Product.vue'
|
||||
import DeviceSelect from './DeviceSelect.vue'
|
||||
import Type from './Type.vue'
|
||||
|
||||
type Emit = {
|
||||
(e: 'cancel'): void
|
||||
|
@ -54,11 +65,7 @@ interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
|
|||
orgId: Array<{ label: string, value: string }>
|
||||
productDetail: any
|
||||
selectorValues: Array<{ label: string, value: string }>
|
||||
metadata: {
|
||||
properties?: any[]
|
||||
functions?: any[]
|
||||
events?: any[]
|
||||
}
|
||||
metadata: metadataType
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
@ -97,39 +104,56 @@ const handleOptions = () => {
|
|||
|
||||
}
|
||||
|
||||
const prev = () => {
|
||||
addModel.stepNumber = addModel.stepNumber - 1
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit("cancel")
|
||||
}
|
||||
|
||||
const handleMetadata = (metadata: string) => {
|
||||
const handleMetadata = (metadata?: string) => {
|
||||
try {
|
||||
addModel.metadata = JSON.parse(metadata)
|
||||
addModel.metadata = JSON.parse(metadata || "{}")
|
||||
} catch (e) {
|
||||
console.warn('handleMetadata: ' + e)
|
||||
}
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
if (addModel.stepNumber === 0) {
|
||||
const save = async (step?: number) => {
|
||||
let _step = step !== undefined ? step : addModel.stepNumber
|
||||
if (_step === 0) {
|
||||
addModel.productId ? addModel.stepNumber = 1 : onlyMessage('请选择产品', 'error')
|
||||
} else if (addModel.stepNumber === 1) {
|
||||
} else if (_step === 1) {
|
||||
const isFixed = addModel.selector === 'fixed' // 是否选择方式为设备
|
||||
if ((['fixed', 'org'].includes(addModel.selector) ) && addModel.selectorValues?.length) {
|
||||
if ((['fixed', 'org'].includes(addModel.selector) ) && !addModel.selectorValues?.length) {
|
||||
return onlyMessage(isFixed ? '请选择设备' : '请选择部门', 'error')
|
||||
}
|
||||
// 选择方式为设备且仅选中一个设备时,物模型取该设备
|
||||
if (isFixed && addModel.selectorValues?.length === 1) {
|
||||
const resp = await deviceDetail(addModel.selectorValues[0].value)
|
||||
addModel.metadata
|
||||
handleMetadata(resp.result.metadata)
|
||||
} else {
|
||||
|
||||
handleMetadata(addModel.productDetail?.metadata)
|
||||
}
|
||||
//
|
||||
addModel.stepNumber = 2
|
||||
} else {
|
||||
|
||||
}
|
||||
// handleOptions()
|
||||
// emit('update:value', {})
|
||||
}
|
||||
|
||||
const saveClick = () => save()
|
||||
|
||||
const stepChange = (step: number) => {
|
||||
if (step !== 0) {
|
||||
save(step - 1)
|
||||
} else {
|
||||
addModel.stepNumber = 0
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<Search
|
||||
:columns="columns"
|
||||
type='simple'
|
||||
@search="handleSearch"
|
||||
class='search'
|
||||
target="scene-triggrt-device-device"
|
||||
/>
|
||||
<a-divider style='margin: 0' />
|
||||
<j-table
|
||||
ref='actionRef'
|
||||
model='CARD'
|
||||
:columns='columns'
|
||||
:request='deviceQuery'
|
||||
:gridColumn='2'
|
||||
:params='params'
|
||||
:bodyStyle='{
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0
|
||||
}'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="deviceRowKeys.includes(slotProps.id)"
|
||||
:status="slotProps.state?.value"
|
||||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
online: 'success',
|
||||
offline: 'error',
|
||||
notActive: 'warning',
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img width='88' height='88' :src="slotProps.photoUrl || getImage('/device/instance/device-card.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<Ellipsis style='width: calc(100% - 100px)'>
|
||||
<span style="font-size: 16px;font-weight: 600" >
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
</div>
|
||||
<div>{{ slotProps.deviceType?.text }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
产品名称
|
||||
</div>
|
||||
<div>{{ slotProps.productName }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</j-table>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='DeviceSelectList'>
|
||||
import type { PropType } from 'vue'
|
||||
import { getImage } from '@/utils/comm'
|
||||
import { query } from '@/api/device/instance'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update', data: Array<{ name: string, value: string}>): void
|
||||
}
|
||||
|
||||
const actionRef = ref()
|
||||
const params = ref({})
|
||||
const context = inject('SceneDeviceAddModel')
|
||||
const props = defineProps({
|
||||
rowKeys: {
|
||||
type: Array as PropType<Array<{ name: string, value: string}>>,
|
||||
default: () => ([])
|
||||
},
|
||||
productId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const deviceRowKeys = computed(() => {
|
||||
return props.rowKeys.map(item => item.value)
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 200,
|
||||
search: {
|
||||
type: 'date'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
width: 90,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
}
|
||||
|
||||
const deviceQuery = (p: any) => {
|
||||
const sorts: any = [];
|
||||
|
||||
if (props.rowKeys) {
|
||||
props.rowKeys.forEach(rowKey => {
|
||||
sorts.push({
|
||||
name: 'id',
|
||||
value: rowKey,
|
||||
});
|
||||
})
|
||||
}
|
||||
sorts.push({ name: 'createTime', order: 'desc' });
|
||||
const terms = [
|
||||
...p.terms,
|
||||
{ terms: [{ column: "productId", value: props.productId }]}
|
||||
]
|
||||
return query({ ...p, terms, sorts })
|
||||
}
|
||||
|
||||
const handleClick = (detail: any) => {
|
||||
const cloneRowKeys = cloneDeep(props.rowKeys)
|
||||
const indexOf = cloneRowKeys.findIndex(item => item.value === detail.id)
|
||||
if (indexOf !== -1) {
|
||||
cloneRowKeys.splice(indexOf, 1)
|
||||
} else {
|
||||
cloneRowKeys.push({
|
||||
name: detail.name,
|
||||
value: detail.id
|
||||
})
|
||||
}
|
||||
console.log('cloneRowKeys', cloneRowKeys)
|
||||
emit('update', cloneRowKeys)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div class='device-select'>
|
||||
<TopCard :options='typeList' v-model:value='selectorModel' @select='select' />
|
||||
<DeviceList v-if='selectorModel === "fixed"' :productId='productId' :row-keys='devices' @update='updateDevice' />
|
||||
<OrgList v-else-if='selectorModel === "org"' :productId='productId' :row-keys='orgIds' @update='updateOrg' />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import TopCard from '@/views/rule-engine/Scene/Save/components/TopCard.vue'
|
||||
import DeviceList from './DeviceList.vue'
|
||||
import OrgList from './OrgList.vue'
|
||||
import { getImage } from '@/utils/comm'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type ItemType = {
|
||||
name: string,
|
||||
value: string
|
||||
}
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:selector', data: string): void
|
||||
(e: 'update:selectorValues', data: ItemType[]): void
|
||||
(e: 'update:deviceKeys', data: ItemType[]): void
|
||||
(e: 'update:orgId', data: ItemType[]): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const props = defineProps({
|
||||
productId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selector: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
device: {
|
||||
type: Array as PropType<ItemType[]>,
|
||||
default: () => []
|
||||
},
|
||||
orgId: {
|
||||
type: Array as PropType<ItemType[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const selectorModel = ref(props.selector)
|
||||
const devices = ref(props.device)
|
||||
const orgIds = ref(props.orgId)
|
||||
|
||||
const typeList = [
|
||||
{ label: '自定义', value: 'fixed', tip: '自定义选择当前产品下的任意设备', img: getImage('/scene/device-custom.png')},
|
||||
{ label: '全部', value: 'all', tip: '产品下的所有设备', img: getImage('/scene/trigger-device-all.png')},
|
||||
{ label: '按组织', value: 'org', tip: '选择产品下归属于具体组织的设备', img: getImage('/scene/trigger-device-org.png')},
|
||||
]
|
||||
|
||||
const select = (s: string) => {
|
||||
selectorModel.value = s
|
||||
emit('update:selector', s)
|
||||
emit('update:selectorValues', [])
|
||||
}
|
||||
|
||||
const updateDevice = (d: any[]) => {
|
||||
devices.value = d
|
||||
emit('update:deviceKeys', d)
|
||||
emit('update:selectorValues', d)
|
||||
}
|
||||
|
||||
const updateOrg = (d: any[]) => {
|
||||
orgIds.value = d
|
||||
emit('update:orgId', d)
|
||||
emit('update:selectorValues', d)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.device-select{
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,130 @@
|
|||
<template>
|
||||
<Search
|
||||
:columns="columns"
|
||||
type='simple'
|
||||
@search="handleSearch"
|
||||
class='search'
|
||||
target="scene-triggrt-device-category"
|
||||
/>
|
||||
<a-divider style='margin: 0' />
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
model='TABLE'
|
||||
type='TREE'
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:scroll="{
|
||||
y: 350
|
||||
}"
|
||||
:expandable='{
|
||||
expandedRowKeys: openKeys,
|
||||
onExpandedRowsChange: expandedRowChange,
|
||||
}'
|
||||
:rowSelection='{
|
||||
type: "radio",
|
||||
selectedRowKeys: orgRowKeys,
|
||||
onChange: selectedRowChange
|
||||
}'
|
||||
:onChange='tableChange'
|
||||
>
|
||||
|
||||
</JTable>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='OrgList'>
|
||||
import type { PropType } from 'vue'
|
||||
import { getExpandedRowById } from './util'
|
||||
import { getTreeData_api } from '@/api/system/department'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update', data: Array<{ name: string, value: string}>): void
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
rowKeys: {
|
||||
type: Array as PropType<Array<{ name: string, value: string}>>,
|
||||
default: () => ([])
|
||||
},
|
||||
productId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const params = ref()
|
||||
const openKeys = ref<string[]>([])
|
||||
const selectedRowKeys = ref(props.rowKeys.map(item => item.value))
|
||||
const sortParam = ref<{ name:string, order: string }>({ name: 'sortIndex', order: 'asc' })
|
||||
const iniPage = ref(true)
|
||||
|
||||
const orgRowKeys = computed(() => {
|
||||
return props.rowKeys.map(item => item.value)
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortIndex',
|
||||
sorter: true,
|
||||
},
|
||||
]
|
||||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
}
|
||||
|
||||
const tableChange = (_: any, f: any, sorter: any) => {
|
||||
if (sorter.order) {
|
||||
sortParam.value = { name: sorter.columnKey, order: (sorter.order as string).replace('end', ''), }
|
||||
} else {
|
||||
sortParam.value = { name: 'sortIndex', order: 'asc' }
|
||||
}
|
||||
}
|
||||
|
||||
const query = async (p: any) => {
|
||||
const _params: any = {
|
||||
paging: false,
|
||||
sorts: [sortParam.value],
|
||||
}
|
||||
|
||||
if (p.terms && p.terms.length) {
|
||||
_params.terms = p.terms
|
||||
}
|
||||
|
||||
const resp = await getTreeData_api(_params)
|
||||
|
||||
if (iniPage.value && props.rowKeys.length) {
|
||||
iniPage.value = false
|
||||
openKeys.value = getExpandedRowById(props.rowKeys[0]?.value, resp.result as any[])
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
const selectedRowChange = (_: any, selectedRows: any[]) => {
|
||||
const item = selectedRows[0]
|
||||
console.log(selectedRows)
|
||||
emit('update', item ? [{ name: item.name, value: item.id }] : [])
|
||||
}
|
||||
|
||||
const expandedRowChange = (keys: string[]) => {
|
||||
openKeys.value = keys
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
</style>
|
|
@ -4,18 +4,25 @@
|
|||
type='simple'
|
||||
@search="handleSearch"
|
||||
class='search'
|
||||
target="scene-triggrt-device-device"
|
||||
/>
|
||||
<a-divider style='margin: 0' />
|
||||
<j-table
|
||||
:columns='columns'
|
||||
ref='actionRef'
|
||||
model='CARD'
|
||||
:columns='columns'
|
||||
:params='params'
|
||||
:request='productQuery'
|
||||
:gridColumn='2'
|
||||
model='CARD'
|
||||
:bodyStyle='{
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0
|
||||
}'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="selectedRowKeys.includes(slotProps.id)"
|
||||
:active="rowKey === slotProps.id"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{ 1: 'success', 0: 'error', }"
|
||||
|
@ -23,13 +30,17 @@
|
|||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/device-product.png')" />
|
||||
<img width='88' height='88' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 style="font-weight: 600" >
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<div style='width: calc(100% - 100px)'>
|
||||
<Ellipsis>
|
||||
<span style="font-size: 16px;font-weight: 600" >
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
</div>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
|
@ -51,16 +62,25 @@ import { getTreeData_api } from '@/api/system/department'
|
|||
import { isNoCommunity } from '@/utils/utils'
|
||||
import { getImage } from '@/utils/comm'
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:rowKey', data: string): void
|
||||
(e: 'update:detail', data: string): void
|
||||
}
|
||||
|
||||
const actionRef = ref()
|
||||
const params = ref({})
|
||||
const props = defineProps({
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
detail: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const selectedRowKeys = ref(props.rowKey)
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
@ -69,12 +89,19 @@ const columns = [
|
|||
width: 300,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '网关类型',
|
||||
|
@ -199,7 +226,6 @@ const columns = [
|
|||
|
||||
const handleSearch = (p: any) => {
|
||||
params.value = p
|
||||
actionRef.value.required()
|
||||
}
|
||||
|
||||
const productQuery = (p: any) => {
|
||||
|
@ -217,12 +243,8 @@ const productQuery = (p: any) => {
|
|||
}
|
||||
|
||||
const handleClick = (detail: any) => {
|
||||
const _selected = new Set(selectedRowKeys.value)
|
||||
if (_selected.has(detail.id)) {
|
||||
_selected.delete(detail.id)
|
||||
} else {
|
||||
_selected.add(detail.id)
|
||||
}
|
||||
emit('update:rowKey', detail.id)
|
||||
emit('update:detail', detail)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -230,5 +252,7 @@ const handleClick = (detail: any) => {
|
|||
<style scoped lang='less'>
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div class='type'>
|
||||
<a-form ref='typeForm' :model='formModel' layout='vertical' :colon='false'>
|
||||
<a-form-item
|
||||
required
|
||||
label='触发类型'
|
||||
>
|
||||
<TopCard
|
||||
:label-bottom='true'
|
||||
:options='options'
|
||||
v-model:value='formModel.operator'
|
||||
/>
|
||||
</a-form-item>
|
||||
<Timer v-if='showTimer' />
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
|
||||
import TopCard from '@/views/rule-engine/Scene/Save/components/TopCard.vue'
|
||||
import { getImage } from '@/utils/comm'
|
||||
import { metadataType } from '@/views/rule-engine/Scene/typings'
|
||||
import type { PropType } from 'vue'
|
||||
import { TypeEnum } from '@/views/rule-engine/Scene/Save/Device/util'
|
||||
import Timer from '../components/Timer.vue'
|
||||
|
||||
const props = defineProps({
|
||||
metadata: {
|
||||
type: Object as PropType<metadataType>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formModel = reactive({
|
||||
operator: 'online',
|
||||
})
|
||||
|
||||
const readProperties = ref<any[]>([])
|
||||
const writeProperties = ref<any[]>([])
|
||||
|
||||
const options = computed(() => {
|
||||
const baseOptions = [
|
||||
{
|
||||
label: '设备上线',
|
||||
value: 'online',
|
||||
img: getImage('/scene/online.png'),
|
||||
},
|
||||
{
|
||||
label: '设备离线',
|
||||
value: 'offline',
|
||||
img: getImage('/scene/offline.png'),
|
||||
},
|
||||
]
|
||||
|
||||
if (props.metadata.events?.length) {
|
||||
baseOptions.push(TypeEnum.reportEvent)
|
||||
}
|
||||
|
||||
if (props.metadata.properties?.length) {
|
||||
const _properties = props.metadata.properties
|
||||
readProperties.value = _properties.filter((item: any) => item.expands.type?.includes('read'))
|
||||
writeProperties.value = _properties.filter((item: any) => item.expands.type?.includes('write'))
|
||||
const reportProperties = _properties.filter((item: any) => item.expands.type?.includes('report'))
|
||||
|
||||
if (readProperties.value.length) {
|
||||
baseOptions.push(TypeEnum.readProperty)
|
||||
}
|
||||
|
||||
if (writeProperties.value.length) {
|
||||
baseOptions.push(TypeEnum.writeProperty)
|
||||
}
|
||||
|
||||
if (reportProperties.length) {
|
||||
baseOptions.push(TypeEnum.reportProperty)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (props.metadata.functions?.length) {
|
||||
baseOptions.push(TypeEnum.invokeFunction)
|
||||
}
|
||||
|
||||
return baseOptions
|
||||
})
|
||||
|
||||
const showTimer = computed(() => {
|
||||
return ['readProperty', 'writeProperty', 'invokeFunction'].includes(formModel.operator)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.type {
|
||||
max-height: calc(100vh - 350px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
|
@ -26,7 +26,8 @@ import AddButton from '../components/AddButton.vue'
|
|||
import Title from '../components/Title.vue'
|
||||
|
||||
const sceneStore = useSceneStore()
|
||||
const { data } = storeToRefs(sceneStore)
|
||||
const { data } = storeToRefs<any>(sceneStore)
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const rules = [{
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { getImage } from '@/utils/comm'
|
||||
|
||||
export const TypeName = {
|
||||
online: '设备上线',
|
||||
offline: '设备离线',
|
||||
reportEvent: '事件上报',
|
||||
reportProperty: '属性上报',
|
||||
readProperty: '读取属性',
|
||||
writeProperty: '修改属性',
|
||||
invokeFunction: '功能调用',
|
||||
};
|
||||
|
||||
export const TypeEnum = {
|
||||
reportProperty: {
|
||||
label: '属性上报',
|
||||
value: 'reportProperty',
|
||||
img: getImage('/scene/reportProperty.png'),
|
||||
},
|
||||
reportEvent: {
|
||||
label: '事件上报',
|
||||
value: 'reportEvent',
|
||||
img: getImage('/scene/reportProperty.png'),
|
||||
},
|
||||
readProperty: {
|
||||
label: '读取属性',
|
||||
value: 'readProperty',
|
||||
img: getImage('/scene/readProperty.png'),
|
||||
},
|
||||
writeProperty: {
|
||||
label: '修改属性',
|
||||
value: 'writeProperty',
|
||||
img: getImage('/scene/writeProperty.png'),
|
||||
},
|
||||
invokeFunction: {
|
||||
label: '功能调用',
|
||||
value: 'invokeFunction',
|
||||
img: getImage('/scene/invokeFunction.png'),
|
||||
},
|
||||
};
|
||||
|
||||
export const getExpandedRowById = (id: string, data: any[]): string[] => {
|
||||
const expandedKeys:string[] = []
|
||||
const dataMap = new Map()
|
||||
|
||||
const flatMapData = (flatData: any[]) => {
|
||||
flatData.forEach(item => {
|
||||
dataMap.set(item.id, { pid: item.parentId })
|
||||
if (item.children && item.children.length) {
|
||||
flatMapData(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getExp = (_id: string) => {
|
||||
const item = dataMap.get(_id)
|
||||
if (item) {
|
||||
expandedKeys.push(_id)
|
||||
if (dataMap.has(dataMap)) {
|
||||
getExp(item.pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flatMapData(data)
|
||||
|
||||
getExp(id)
|
||||
|
||||
return expandedKeys
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<a-form
|
||||
ref='timerForm'
|
||||
:model='formModel'
|
||||
layout='vertical'
|
||||
:colon='false'
|
||||
>
|
||||
<a-form-item name='trigger'>
|
||||
<a-radio-group
|
||||
v-model:value='formModel.trigger'
|
||||
:options='[
|
||||
{ label: "按周", value: "week" },
|
||||
{ label: "按月", value: "month" },
|
||||
{ label: "cron表达式", value: "cron" },
|
||||
]'
|
||||
option-type='button'
|
||||
button-style='solid'
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if='showCron' name='cron'>
|
||||
<a-input placeholder='corn表达式' v-model='formModel.cron' />
|
||||
</a-form-item>
|
||||
<template v-else>
|
||||
<a-form-item name='when'>
|
||||
|
||||
</a-form-item>
|
||||
<a-form-item name='mod'>
|
||||
<a-radio-group
|
||||
v-model:value='formModel.mod'
|
||||
:options='[
|
||||
{ label: "周期执行", value: "period" },
|
||||
{ label: "执行一次", value: "once" },
|
||||
]'
|
||||
option-type='button'
|
||||
button-style='solid'
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-space v-if='showOnce' style='display: flex;gap: 24px'>
|
||||
<a-form-item :name="['once', 'time']">
|
||||
<a-time-picker valueFormat='HH:mm:ss' v-model:value='formModel.once.time' style='width: 100%' format='HH:mm:ss' />
|
||||
</a-form-item>
|
||||
<a-form-item> 执行一次 </a-form-item>
|
||||
</a-space>
|
||||
<a-space v-if='showPeriod' style='display: flex;gap: 24px'>
|
||||
<a-form-item>
|
||||
<a-time-range-picker
|
||||
valueFormat='HH:mm:ss'
|
||||
:value='[
|
||||
formModel.period.from,
|
||||
formModel.period.to,
|
||||
]'
|
||||
@change='(v) => {
|
||||
formModel.period.from = v[0]
|
||||
formModel.period.to = v[1]
|
||||
}'
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>每</a-form-item>
|
||||
<a-form-item
|
||||
:name='["period", "every"]'
|
||||
:rules='[{ required: true, message: "请输入时间" }]'
|
||||
>
|
||||
<a-input-number
|
||||
placeholder='请输入时间'
|
||||
style='max-width: 170px'
|
||||
:precision='0'
|
||||
:min='1'
|
||||
:max='59'
|
||||
v-model:value='formModel.period.every'
|
||||
>
|
||||
<template #addonAfter>
|
||||
<a-select
|
||||
v-model:value='formModel.period.unit'
|
||||
:options='[
|
||||
{ label: "秒", value: "seconds" },
|
||||
{ label: "分", value: "minutes" },
|
||||
{ label: "小时", value: "hours" },
|
||||
]'
|
||||
/>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>执行一次</a-form-item>
|
||||
</a-space>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='Timer'>
|
||||
import type { PropType } from 'vue'
|
||||
import moment from 'moment'
|
||||
|
||||
type NameType = string[] | string
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: [String, Array] as PropType<NameType>,
|
||||
default: ''
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const formModel = reactive({
|
||||
trigger: 'week',
|
||||
when: [],
|
||||
mod: 'period',
|
||||
cron: undefined,
|
||||
once: {
|
||||
time: ''
|
||||
},
|
||||
period: {
|
||||
from: moment(new Date()).startOf('day').format('HH:mm:ss'),
|
||||
to: moment(new Date()).endOf('day').format('HH:mm:ss'),
|
||||
every: 1,
|
||||
unit: 'seconds'
|
||||
}
|
||||
})
|
||||
|
||||
const showCron = computed(() => {
|
||||
return formModel.trigger === 'cron'
|
||||
})
|
||||
|
||||
const showOnce = computed(() => {
|
||||
return formModel.trigger !== 'cron' && formModel.mod === 'once'
|
||||
})
|
||||
|
||||
const showPeriod = computed(() => {
|
||||
return formModel.trigger !== 'cron' && formModel.mod === 'period'
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,167 @@
|
|||
<template>
|
||||
<div :class='classNames'>
|
||||
<div
|
||||
v-for='item in options'
|
||||
:key='item.value'
|
||||
:class='[
|
||||
"trigger-way-item",
|
||||
value === item.value ? "active" : "",
|
||||
labelBottom ? "label-bottom" : ""
|
||||
]'
|
||||
@click='() => click(item.value)'
|
||||
>
|
||||
<div class='way-item-title'>
|
||||
<span class='label'>{{ item.label }}</span>
|
||||
<a-popover v-if='item.tip' :content='item.tip'>
|
||||
<AIcon type='QuestionCircleOutlined' class='icon' />
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class='way-item-image'>
|
||||
<img
|
||||
width='48'
|
||||
v-bind='item.imgProps'
|
||||
:src='item.img'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='TopCard'>
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type optionsType = {
|
||||
label: string
|
||||
value: string
|
||||
img?: string
|
||||
tip?: string
|
||||
imgProps: Record<string, any>
|
||||
}
|
||||
|
||||
type Emit = {
|
||||
(e: 'update:value', data: string): void
|
||||
(e: 'select', data: string): void
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array as PropType<optionsType[]>,
|
||||
default: () => ([])
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
labelBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const classNames = computed(() => {
|
||||
return [
|
||||
props.class,
|
||||
'trigger-way-warp',
|
||||
props.disabled ? 'disabled' : ''
|
||||
]
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const click = (value: string) => {
|
||||
emit('update:value', value)
|
||||
emit('select', value)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.trigger-way-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.trigger-way-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 237px;
|
||||
//width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.way-item-title {
|
||||
span {
|
||||
font-size: 16px;
|
||||
}
|
||||
.label {
|
||||
padding-right: 6px;
|
||||
color: rgba(#000, 0.64);
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: rgba(#000, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.way-item-image {
|
||||
margin: 0 !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
//color: @primary-color-hover;
|
||||
.way-item-image {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: @primary-color-active;
|
||||
.way-item-image {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.label-bottom {
|
||||
flex-direction: column-reverse;
|
||||
grid-gap: 16px;
|
||||
gap: 0;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
padding: 8px 16px;
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.trigger-way-item {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -9,6 +9,11 @@ type State = {
|
|||
text: string;
|
||||
};
|
||||
|
||||
export type optionItem = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type Action = {
|
||||
executor: string;
|
||||
configuration: Record<string, unknown>;
|
||||
|
@ -311,3 +316,9 @@ export interface FormModelType {
|
|||
options?: Record<string, any>;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export type metadataType = {
|
||||
properties?: any[]
|
||||
functions?: any[]
|
||||
events?: any[]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue