Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
JiangQiming 2023-03-02 14:05:25 +08:00
commit 2607f9aace
15 changed files with 6285 additions and 12113 deletions

6489
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
"version": "0.0.0",
"scripts": {
"dev": "vite --mode develop",
"dev:force": "vite --force --mode develop",
"build": "node --max_old_space_size=1024000 ./node_modules/vite/bin/vite.js build",
"preview": "vite preview",
"eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
@ -23,7 +24,7 @@
"event-source-polyfill": "^1.0.31",
"global": "^4.4.0",
"jetlinks-store": "^0.0.3",
"jetlinks-ui-components": "^1.0.0",
"jetlinks-ui-components": "^1.0.1",
"js-cookie": "^3.0.1",
"less": "^4.1.3",
"less-loader": "^11.1.0",

View File

@ -33,4 +33,9 @@ export const detail = (id:string) => server.get(`/alarm/record/${id}`);
/**
*
*/
export const queryHistoryList = (data:any) => server.post('/alarm/history/_query',data)
export const queryHistoryList = (data:any) => server.post('/alarm/history/_query',data);
/**
*
*/
export const queryHandleHistory = (data:any) => server.post('/alarm/record/handle-history/_query',data);

View File

@ -10,6 +10,7 @@
:columns="columns"
:request="queryList"
:gridColumn="3"
:gridColumns="[1,2,3]"
ref="tableRef"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],

View File

@ -0,0 +1,79 @@
<template>
<a-modal
title="告警处理"
okText="确定"
cancelText="取消"
visible
@cancel="handleCancel"
@ok="handleSave"
destroyOnClose
:confirmLoading="loading"
>
<a-form :rules="rules" layout="vertical" ref="formRef" :model="form">
<a-form-item label="处理结果" name="describe">
<a-textarea
:rows="8"
:maxlength="200"
showCount
placeholder="请输入处理结果"
v-model:value="form.describe"
></a-textarea>
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { handleLog } from '@/api/rule-engine/log';
import { onlyMessage } from '@/utils/comm';
const props = defineProps({
data: {
type: Object,
},
});
const loading = ref<boolean>(false);
const formRef = ref();
const rules = {
describe: [
{
required: true,
message: '请输入处理结果',
},
],
};
const form = reactive({
describe: '',
});
let visible = ref(true);
const emit = defineEmits(['closeSolve'])
const handleCancel = () => {
emit('closeSolve');
};
const handleSave = () => {
loading.value = true;
formRef.value
.validate()
.then(async () => {
const res = await handleLog({
describe: form.describe,
type: 'user',
state: 'normal',
alarmRecordId: props.data?.current?.id || '',
alarmConfigId: props.data?.current?.alarmConfigId || '',
alarmTime: props?.data?.current?.alarmTime || '',
});
if (res.status === 200) {
onlyMessage('操作成功!');
} else {
onlyMessage('操作失败!', 'error');
}
loading.value = false;
})
.catch((error) => {
console.log(error);
loading.value = false;
});
};
</script>
<style lang="less" scoped>
</style>

View File

@ -1,78 +1,130 @@
<template>
<a-modal
title="告警处理"
okText="确定"
cancelText="取消"
visible
@cancel="handleCancel"
@ok="handleSave"
destroyOnClose
:confirmLoading="loading"
title="处理记录"
:width="1200"
cancelText="取消"
okText="确定"
@ok="clsoeModal"
@cancel="clsoeModal"
>
<a-form :rules="rules" layout="vertical" ref="formRef" :model="form">
<a-form-item label="处理结果" name="describe">
<a-textarea
:rows="8"
:maxlength="200"
showCount
placeholder="请输入处理结果"
v-model:value="form.describe"
></a-textarea>
</a-form-item>
</a-form>
<Search
:columns="columns"
target="bind-channel"
@search="handleSearch"
></Search>
<JTable
model="TABLE"
:columns="columns"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
terms,
}"
:request="queryHandleHistory"
:params="params"
>
<template #headerTitle>
<h3>记录列表</h3>
</template>
<template #handleTime="slotsProps">
<span>
{{
moment(slotsProps.handleTime).format(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
</template>
<template #handleType="slotProps">
<span>{{ slotProps.handleType.text }}</span>
</template>
<template #alarmTime="slotProps">
<span>
{{
moment(slotProps.alarmTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}
</span>
</template>
</JTable>
</a-modal>
</template>
<script lang="ts" setup>
import { handleLog } from '@/api/rule-engine/log';
import { onlyMessage } from '@/utils/comm';
import { queryHandleHistory } from '@/api/rule-engine/log';
import moment from 'moment';
const props = defineProps({
data: {
type: Object,
},
});
const loading = ref<boolean>(false);
const formRef = ref();
const rules = {
describe: [
{
required: true,
message: '请输入处理结果',
const terms = [
{
column: 'alarmRecordId',
termType: 'eq',
value: props.data.id,
type: 'and',
},
];
const columns = [
{
title: '处理时间',
dataIndex: 'handleTime',
key: 'handleTime',
scopedSlots: true,
search: {
type: 'date',
},
],
},
{
dataIndex: 'handleType',
title: '处理类型',
key: 'handleType',
scopedSlots: true,
search: {
type: 'select',
options: [
{
label: '系统',
value: 'system',
},
{
label: '人工',
value: 'user',
},
],
},
},
{
title: '告警时间',
dataIndex: 'alarmTime',
key: 'alarmTime',
scopedSlots: true,
search: {
type: 'date',
},
},
{
title: '告警处理',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},
},
];
const params = ref();
const emit = defineEmits(['closeLog']);
/**
* 关闭弹窗
*/
const clsoeModal = () => {
emit('closeLog');
};
const form = reactive({
describe: '',
});
let visible = ref(true);
const emit = defineEmits(['closeSolve'])
const handleCancel = () => {
emit('closeSolve');
};
const handleSave = () => {
loading.value = true;
formRef.value
.validate()
.then(async () => {
const res = await handleLog({
describe: form.describe,
type: 'user',
state: 'normal',
alarmRecordId: props.data?.current?.id || '',
alarmConfigId: props.data?.current?.alarmConfigId || '',
alarmTime: props?.data?.current?.alarmTime || '',
});
if (res.status === 200) {
onlyMessage('操作成功!');
} else {
onlyMessage('操作失败!', 'error');
}
loading.value = false;
})
.catch((error) => {
console.log(error);
loading.value = false;
});
const handleSearch = (e: any) => {
params.value = e;
};
</script>
<style lang="less" scoped>

View File

@ -28,6 +28,7 @@
:columns="columns"
:request="handleSearch"
:params="params"
:gridColumns="[1,1,2]"
:gridColumn="2"
model="CARD"
>
@ -115,7 +116,8 @@
</CardBox>
</template>
</JTable>
<SolveLog :data="data" v-if="data.solveVisible" @closeSolve="closeSolve"/>
<SolveComponent :data="data" v-if="data.solveVisible" @closeSolve="closeSolve"/>
<SolveLog :data="data.current" v-if="data.logVisible" @closeLog="closeLog"/>
</div>
</template>
@ -134,6 +136,7 @@ import { storeToRefs } from 'pinia';
import { Store } from 'jetlinks-store';
import moment from 'moment';
import type { ActionsType } from '@/components/Table';
import SolveComponent from '../SolveComponent/index.vue';
import SolveLog from '../SolveLog/index.vue'
import { useMenuStore } from '@/store/menu';
const menuStory = useMenuStore();
@ -390,12 +393,25 @@ const getActions = (
title: '处理记录',
},
icon: 'FileTextOutlined',
onClick:() =>{
data.value.current = currentData;
data.value.logVisible = true;
}
},
];
return actions;
};
/**
* 关闭告警日志
*/
const closeSolve = () =>{
data.value.solveVisible = false
data.value.solveVisible = false;
}
/**
* 关闭处理记录
*/
const closeLog = () =>{
data.value.logVisible = false;
}
</script>
<style lang="less" scoped>

View File

@ -29,6 +29,7 @@
v-model:selectorValues='addModel.selectorValues'
/>
<Type
ref='typeRef'
v-else-if='addModel.stepNumber === 2'
:metadata='addModel.metadata'
/>
@ -46,7 +47,7 @@
<script setup lang='ts' name='AddModel'>
import type { PropType } from 'vue'
import type { metadataType, TriggerDevice } from '@/views/rule-engine/Scene/typings'
import type { metadataType, TriggerDevice, TriggerDeviceOptions } from '@/views/rule-engine/Scene/typings'
import { onlyMessage } from '@/utils/comm'
import { detail as deviceDetail } from '@/api/device/instance'
import Product from './Product.vue'
@ -69,6 +70,7 @@ interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
}
const emit = defineEmits<Emit>()
const typeRef = ref()
const props = defineProps({
value: {
@ -81,9 +83,7 @@ const props = defineProps({
},
options: {
type: Object as PropType<any>,
default: () => ({
})
default: () => ({})
}
})
@ -100,7 +100,7 @@ const addModel = reactive<AddModelType>({
Object.assign(addModel, props.value)
const handleOptions = () => {
const handleOptions = (data: TriggerDeviceOptions) => {
}
@ -138,10 +138,12 @@ const save = async (step?: number) => {
}
addModel.stepNumber = 2
} else {
const typeData = await typeRef.value.vail()
console.log(typeData)
if (typeData) {
const _options = handleOptions(typeData);
}
}
// handleOptions()
// emit('update:value', {})
}
const saveClick = () => save()

View File

@ -1,52 +1,57 @@
<template>
<a-row :gutter='24'>
<a-col :span='10'>
<a-form-item
name='functionId'
:rules="[{ required: true, message: '请选择功能' }]"
>
<a-select
showSearch
allowClear
v-model='functionId'
style='width: 100%'
placeholder='请选择功能'
:filterOption='filterSelectNode'
@select='onSelect'
/>
</a-form-item>
</a-col>
<a-col :span='14'>
<a-form-item>定时调用所选功能</a-form-item>
</a-col>
<a-col :span='24'>
<a-form-item
style='margin-top: 24px'
name='functionParameters'
>
<a-form ref='invokeForm' :model='formModel' layout='vertical' :colon='false'>
<a-row :gutter='24'>
<a-col :span='10'>
<a-form-item
name='functionId'
:rules="[{ required: true, message: '请选择功能' }]"
>
<a-select
showSearch
allowClear
v-model:value='formModel.functionId'
style='width: 100%'
placeholder='请选择功能'
:options='functions'
:filterOption='filterSelectNode'
@select='onSelect'
/>
</a-form-item>
</a-col>
<a-col :span='14'>
<a-form-item>定时调用所选功能</a-form-item>
</a-col>
<a-col :span='24'>
<FunctionCall
v-model:value='_value'
:data='callDataOptions'
:value='_value'
:data='functionData'
@change='callDataChange'
/>
</a-form-item>
</a-col>
</a-row>
</a-col>
</a-row>
</a-form>
</template>
<script setup lang='ts' name='InvokeFunction'>
import { filterSelectNode } from '@/utils/comm'
import { FunctionCall } from '../components'
import type { PropType } from 'vue'
import { defineExpose } from 'vue'
type Emit = {
(e: 'update:value', data: Record<string, any>): void
(e: 'update:functionParameters', data: Array<Record<string, any>>): void
(e: 'update:functionId', data: string): void
(e: 'update:action', data: string): void
}
const props = defineProps({
value: {
type: Object,
default: () => ({})
functionId: {
type: String,
default: undefined
},
functionParameters: {
type: Array as PropType<Record<string, any>[]>,
default: () => []
},
action: {
type: String,
@ -59,46 +64,53 @@ const props = defineProps({
})
const emit = defineEmits<Emit>()
const invokeForm = ref()
const formModel = reactive({
functionId: props.functionId
})
const _value = ref<any[]>(props.functionParameters)
const functionId = ref()
const _value = ref([])
/**
* 获取当前选择功能属性
*/
const functionData = computed(() => {
const functionItem: any = props.functions.find((f: any) => f.id === formModel.functionId)
const arrCache = []
const callDataOptions = computed(() => {
const _valueKeys = Object.keys(props.value)
if (_valueKeys.length) {
return _valueKeys.map(key => {
const item: any = props.functions.find((p: any) => p.id === key)
if (item) {
return {
id: item.id,
name: item.name,
type: item.valueType ? item.valueType.type : '-',
format: item.valueType ? item.valueType.format : undefined,
options: item.valueType ? item.valueType.element : undefined,
value: props.value[key]
}
}
return {
id: key,
name: key,
type: '',
format: undefined,
options: undefined,
value: props.value[key]
}
})
if (functionItem) {
const properties = functionItem.valueType ? functionItem.valueType.properties : functionItem.inputs;
for (const datum of properties) {
arrCache.push({
id: datum.id,
name: datum.name,
type: datum.valueType ? datum.valueType.type : '-',
format: datum.valueType ? datum.valueType.format : undefined,
options: datum.valueType ? datum.valueType.elements : undefined,
value: undefined,
});
}
}
return []
return arrCache
})
const onSelect = (v: string, item: any) => {
emit('update:action', `执行${item.name}`)
emit('update:functionId', v)
}
const callDataChange = () => {
const callDataChange = (v: any[]) => {
_value.value = v
emit('update:functionParameters', v)
}
defineExpose({
validateFields: () => new Promise(async (resolve) => {
const data = await invokeForm.value?.validateFields()
resolve(data)
})
})
</script>
<style scoped>

View File

@ -14,6 +14,7 @@
:params='params'
:request='productQuery'
:gridColumn='2'
:gridColumns='[2,2,2]'
:bodyStyle='{
paddingRight: 0,
paddingLeft: 0

View File

@ -11,15 +11,22 @@
v-model:value='formModel.operator'
/>
</a-form-item>
<Timer v-if='showTimer' v-model:value='formModel.timer' />
<ReadProperties v-if='showReadProperty' v-model:value='formModel.readProperties' v-model:action='optionCache.action' :properties='readProperties' />
<a-form-item
<template v-if='showTimer'>
<Timer ref='timerRef' v-model:value='formModel.timer' />
</template>
<ReadProperties
v-if='showReadProperty'
v-model:value='formModel.readProperties'
v-model:action='optionCache.action'
:properties='readProperties'
/>
<WriteProperty
ref='writeRef'
v-if='showWriteProperty'
name='writeProperties'
:rules="[{ required: true, message: '请输入修改值' }]"
>
<WriteProperty v-model:value='formModel.writeProperties' v-model:action='optionCache.action' :properties='writeProperties' />
</a-form-item>
v-model:value='formModel.writeProperties'
v-model:action='optionCache.action'
:properties='writeProperties'
/>
<a-form-item
v-if='showReportEvent'
name='eventId'
@ -34,6 +41,14 @@
@select='eventSelect'
/>
</a-form-item>
<template v-if='showInvokeFunction'>
<InvokeFunction
ref='invokeRef'
v-model:type='formModel.functionId'
v-model:functionParameters='formModel.functionParameters'
:functions='functionOptions'
/>
</template>
</a-form>
</div>
</template>
@ -47,6 +62,9 @@ import type { PropType } from 'vue'
import { TypeEnum } from '@/views/rule-engine/Scene/Save/Device/util'
import ReadProperties from './ReadProperties.vue'
import WriteProperty from './WriteProperty.vue'
import InvokeFunction from './InvokeFunction.vue'
import { defineExpose } from 'vue'
import { cloneDeep, omit } from 'lodash-es'
const props = defineProps({
metadata: {
@ -72,19 +90,25 @@ const optionCache = reactive({
const readProperties = ref<any[]>([])
const writeProperties = ref<any[]>([])
const eventOptions = ref<any[]>([])
const functionOptions = ref<any[]>([])
const typeForm = ref()
const timerRef = ref()
const writeRef = ref()
const invokeRef = ref()
const topOptions = computed(() => {
const baseOptions = [
{
label: '设备上线',
value: 'online',
img: getImage('/scene/online.png'),
img: getImage('/scene/online.png')
},
{
label: '设备离线',
value: 'offline',
img: getImage('/scene/offline.png'),
},
img: getImage('/scene/offline.png')
}
]
if (props.metadata.events?.length) {
@ -94,9 +118,21 @@ const topOptions = computed(() => {
if (props.metadata.properties?.length) {
const _properties = props.metadata.properties
readProperties.value = _properties.filter((item: any) => item.expands.type?.includes('read')).map(item => ({...item, label: item.name, value: item.id }))
writeProperties.value = _properties.filter((item: any) => item.expands.type?.includes('write')).map(item => ({...item, label: item.name, value: item.id }))
const reportProperties = _properties.filter((item: any) => item.expands.type?.includes('report')).map(item => ({...item, label: item.name, value: item.id }))
readProperties.value = _properties.filter((item: any) => item.expands.type?.includes('read')).map(item => ({
...item,
label: item.name,
value: item.id
}))
writeProperties.value = _properties.filter((item: any) => item.expands.type?.includes('write')).map(item => ({
...item,
label: item.name,
value: item.id
}))
const reportProperties = _properties.filter((item: any) => item.expands.type?.includes('report')).map(item => ({
...item,
label: item.name,
value: item.id
}))
if (readProperties.value.length) {
baseOptions.push(TypeEnum.readProperty)
@ -109,11 +145,11 @@ const topOptions = computed(() => {
if (reportProperties.length) {
baseOptions.push(TypeEnum.reportProperty)
}
}
if (props.metadata.functions?.length) {
baseOptions.push(TypeEnum.invokeFunction)
functionOptions.value = props.metadata.functions.map(item => ({ ...item, label: item.name, value: item.id }))
}
return baseOptions
@ -147,6 +183,50 @@ const eventSelect = (_: string, eventItem: any) => {
optionCache.action = `${eventItem.name}上报`
}
defineExpose({
vail: () => {
return new Promise(async (resolve, reject) => {
const cloneModel = cloneDeep(formModel)
const filterKey: string[] = []
const typeData = await typeForm.value?.validateFields()
if (!typeData) return resolve(false)
if (!showReadProperty.value) {
filterKey.push('readProperties')
}
if (showInvokeFunction.value) {
const invokeData = await invokeRef.value?.validateFields()
if (!invokeData) return resolve(false)
} else {
filterKey.push('functionId')
filterKey.push('functionParameters')
}
if (showTimer.value) {
const timerData = await timerRef.value?.validateFields()
if (!timerData) return resolve(false)
} else {
filterKey.push('timer')
}
if (!showReportEvent.value) {
filterKey.push('eventId')
}
if (showWriteProperty.value) {
const writeData = await writeRef.value?.validateFields()
if (!writeData) return resolve(false)
} else {
filterKey.push('writeProperties')
}
resolve(omit(cloneModel, filterKey))
})
}
})
</script>
<style scoped lang='less'>

View File

@ -1,37 +1,43 @@
<template>
<a-row :futter='[24, 24]'>
<a-col :span='10'>
<a-select
showSearch
style='width: 100%'
placeholder='请选择属性'
v-model:value='reportKey'
:options='properties'
:filter-option='filterSelectNode'
@change='change'
/>
</a-col>
<a-col :span='14'>
<span style='line-height: 32px;padding-left: 24px'>
定时调用所选属性
</span>
</a-col>
<a-col :span='24' v-if='showTable'>
<div style='margin-top: 24px'>
<a-form ref='writeForm' :model='formModel' layout='vertical' :colon='false'>
<a-row :futter='[24, 24]'>
<a-col :span='10'>
<a-form-item
name='reportKey'
:rules="[{ required: true, message: '请输入修改值' }]"
>
<a-select
showSearch
style='width: 100%'
placeholder='请选择属性'
v-model:value='formModel.reportKey'
:options='properties'
:filter-option='filterSelectNode'
@change='change'
/>
</a-form-item>
</a-col>
<a-col :span='14'>
<span style='line-height: 32px;padding-left: 24px'>
定时调用所选属性
</span>
</a-col>
<a-col :span='24' v-if='showTable'>
<FunctionCall
:value='_value'
:data='callDataOptions'
@change='callDataChange'
/>
</div>
</a-col>
</a-row>
</a-col>
</a-row>
</a-form>
</template>
<script setup lang='ts' name='WriteProperties'>
import { filterSelectNode } from '@/utils/comm'
import { FunctionCall } from '../components'
import type { PropType } from 'vue'
import { defineExpose } from 'vue'
type Emit = {
(e: 'update:value', data: Record<string, any>): void
@ -55,8 +61,12 @@ const props = defineProps({
const emit = defineEmits<Emit>()
const reportKey = ref<string>()
const formModel = reactive<{ reportKey: string | undefined }>({
reportKey: undefined
})
const callData = ref<Array<{ id: string, value: string | undefined }>>()
const writeForm = ref()
const _value = ref([])
const callDataOptions = computed(() => {
@ -88,11 +98,10 @@ const callDataOptions = computed(() => {
})
const showTable = computed(() => {
return !!reportKey.value
return !!formModel.reportKey
})
const change = (v: string, option: any) => {
console.log(v, option)
const _data = {
[v]: undefined
}
@ -103,17 +112,24 @@ const change = (v: string, option: any) => {
const callDataChange = (v: any[]) => {
emit('update:value', {
[reportKey.value!]: v[0]?.value
[formModel.reportKey!]: v[0]?.value
})
}
const initRowKey = () => {
if (props.value.length) {
const keys = Object.keys(props.value)
reportKey.value = keys[0]
formModel.reportKey = keys[0]
}
}
defineExpose({
validateFields: () => new Promise(async (resolve) => {
const data = await writeForm.value?.validateFields()
resolve(data)
})
})
</script>
<style scoped>

View File

@ -1,7 +1,10 @@
<template>
<a-table
<j-table
model='TABLE'
:noPagination='true'
:data-source='dataSource.value'
:columns='columns'
:bodyStyle='{ padding: 0}'
>
<template #bodyCell="{ column, record, index }">
<template v-if='column.dataIndex === "name"'>
@ -33,7 +36,7 @@
/>
</template>
</template>
</a-table>
</j-table>
</template>
<script setup lang='ts' name='FunctionCall'>
@ -61,13 +64,6 @@ const dataSource = reactive<{value: any[]}>({
value: []
})
watch(() => props.data, () => {
dataSource.value = props.data.map((item: any) => {
const oldValue = props.value.find((oldItem: any) => oldItem.name === item.id)
return oldValue ? { ...item, value: oldValue.value } : item
})
}, { immediate: true })
const columns = [
{
title: '参数名称',
@ -99,6 +95,13 @@ const handleOptions = (record: any) => {
}
}
watch(() => props.data, () => {
dataSource.value = props.data.map((item: any) => {
const oldValue = props.value.find((oldItem: any) => oldItem.name === item.id)
return oldValue ? { ...item, value: oldValue.value } : item
})
}, { immediate: true })
watch(() => dataSource.value, () => {
const _value = dataSource.value.map(item => ({
name: item.id, value: item.value

View File

@ -108,6 +108,7 @@ import WhenOption from './WhenOption.vue'
import { cloneDeep } from 'lodash-es'
import type { OperationTimer } from '../../../typings'
import { isCron } from '@/utils/regular'
import { defineExpose } from 'vue'
type NameType = string[] | string
@ -143,6 +144,7 @@ const formModel = reactive<OperationTimer>({
unit: 'seconds'
}
})
const timerForm = ref()
Object.assign(formModel, props.value)
@ -174,6 +176,13 @@ watch(() => formModel, () => {
emit('update:value', cloneValue)
}, { deep: true })
defineExpose({
validateFields: () => new Promise(async (resolve) => {
const data = await timerForm.value?.validateFields()
resolve(data)
})
})
</script>
<style scoped lang='less'>

11260
yarn.lock

File diff suppressed because it is too large Load Diff