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

This commit is contained in:
wangshuaiswim 2023-03-09 16:05:07 +08:00
commit e7630bc7e2
34 changed files with 1790 additions and 337 deletions

View File

@ -100,7 +100,7 @@ const matchComponents: IMatcher[] = [
},
{
pattern: /^TimePicker|^TimeRangePicker/,
styleDir: 'TimeTicker'
styleDir: 'TimePicker'
},
{
pattern: /^Radio/,

View File

@ -85,7 +85,7 @@ export const batchDeleteDevice = (data: string[]) => server.put(`/device-instanc
*/
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
export const templateDownload = (productId: string, type: string) => server.get(`/device-instance/${productId}/template.${type}`,{},{responseType: 'blob'})
export const templateDownload = (productId: string, type: string) => server.get(`/device-instance/${productId}/template.${type}`, {}, { responseType: 'blob' })
/**
*
* @param productId id
@ -245,6 +245,22 @@ export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) =
*/
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
/**
*
* @param data
*/
export const getProductListNoPage = (data: any) => server.post('/device/product/_query/no-paging?paging=false', data)
/**
*
*/
export const editDevice = (parmas: any) => server.patch('/device-instance', parmas)
/**
*
*/
export const addDevice = (params: any) => server.post("/device-instance", params)
/**
*
* @param id id
@ -504,14 +520,14 @@ export const productCode = (productId: string) => server.get(`/device/transparen
* @param productId
* @returns
*/
export const saveProductCode = (productId: string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}`,data)
export const saveProductCode = (productId: string, data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}`, data)
/**
*
* @param productId
* @param deviceId
* @returns
*/
export const deviceCode = (productId: string,deviceId:string) => server.get(`device/transparent-codec/${productId}/${deviceId}`)
export const deviceCode = (productId: string, deviceId: string) => server.get(`device/transparent-codec/${productId}/${deviceId}`)
/**
*
* @param productId
@ -520,13 +536,13 @@ export const deviceCode = (productId: string,deviceId:string) => server.get(`dev
* @param data
* @returns
*/
export const saveDeviceCode = (productId: string,deviceId:string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}/${deviceId}`,data)
export const saveDeviceCode = (productId: string, deviceId: string, data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}/${deviceId}`, data)
/**
*
* @param data
* @returns
*/
export const testCode = (data: Record<string, unknown>) => server.post(`/device/transparent-codec/decode-test`,data)
export const testCode = (data: Record<string, unknown>) => server.post(`/device/transparent-codec/decode-test`, data)
/**
*
* @param productId

View File

@ -16,10 +16,11 @@
disabled && myValue === item.value
? 'active-checked-disabled'
: '',
item.disabled ? 'disabled' : '',
]"
v-for="(item, index) in options"
:key="index"
@click="myValue = item.value"
@click="handleRadio(item)"
>
<img v-if="item.logo" class="img" :src="item.logo" alt="" />
<span>{{ item.label }}</span>
@ -86,6 +87,11 @@ const myValue = computed({
}
},
});
const handleRadio = (item: any) => {
if (item.disabled) return;
myValue.value = item.value;
};
</script>
<style lang="less" scoped>
@ -93,6 +99,11 @@ const myValue = computed({
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.disabled {
color: rgba(0, 0, 0, 0.25);
border-color: #f5f5f5;
cursor: not-allowed;
}
&-item {
width: 49%;
height: 70px;

View File

@ -7,6 +7,15 @@
:options="options"
allowClear
style="width: 100%"
@change='selectChange'
/>
<j-time-picker
v-else-if="typeMap.get(itemType) === 'time'"
v-model:value="myValue"
allowClear
format="HH:mm:ss"
style="width: 100%"
@change='timeChange'
/>
<j-date-picker
v-else-if="typeMap.get(itemType) === 'date'"
@ -16,17 +25,20 @@
lang="cn"
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
@change='dateChange'
/>
<j-input-number
v-else-if="typeMap.get(itemType) === 'inputNumber'"
v-model:value="myValue"
allowClear
style="width: 100%"
@change='inputChange'
/>
<j-input
allowClear
v-else-if="typeMap.get(itemType) === 'object'"
v-model:value="myValue"
@change='inputChange'
>
<template #addonAfter>
<AIcon type="FormOutlined" @click="modalVis = true" />
@ -60,6 +72,7 @@
type="text"
v-model:value="myValue"
style="width: 100%"
@change='inputChange'
/>
<!-- 代码编辑器弹窗 -->
@ -92,6 +105,7 @@ import { FILE_UPLOAD } from '@/api/comm';
type Emits = {
(e: 'update:modelValue', data: string | number | boolean): void;
(e: 'change', data: any, item?: any): void;
};
const emit = defineEmits<Emits>();
@ -169,6 +183,23 @@ const handleFileChange = (info: UploadChangeParam<UploadFile<any>>) => {
emit('update:modelValue', url);
}
};
const selectChange = (e: string, option: any) => {
emit('change', e, option)
}
const timeChange = (e: any) => {
emit('change', e)
}
const inputChange = (e: any) => {
emit('change', e.target.value)
}
const dateChange = (e: any) => {
emit('change', e)
}
</script>
<style lang="less" scoped></style>

View File

@ -1,5 +1,5 @@
import type { App } from 'vue'
import AIcon from './AIcon'
// import AIcon from './AIcon'
import PermissionButton from './PermissionButton/index.vue'
import JTable from './Table/index'
import TitleComponent from "./TitleComponent/index.vue";
@ -10,7 +10,7 @@ import NormalUpload from './NormalUpload/index.vue'
import FileFormat from './FileFormat/index.vue'
import JProUpload from './JUpload/index.vue'
import { BasicLayoutPage, BlankLayoutPage } from './Layout'
import { PageContainer } from 'jetlinks-ui-components'
import { PageContainer, AIcon } from 'jetlinks-ui-components'
import Ellipsis from './Ellipsis/index.vue'
import JEmpty from './Empty/index.vue'
import AMapComponent from './AMapComponent/index.vue'

View File

@ -18,7 +18,7 @@
@search="handleSearch"
type="simple"
/>
<JTable
<JProTable
ref="bindDeviceRef"
:columns="columns"
:request="query"
@ -78,7 +78,7 @@
:status="statusMap.get(slotProps.state.value)"
/>
</template>
</JTable>
</JProTable>
</div>
</a-modal>
</template>

View File

@ -0,0 +1,117 @@
<template>
<a-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
<a-select-option
v-for="item in list"
:key="item.id"
:value="item.id"
:label="item.name"
:filter-option="filterOption"
>{{ item.name }}</a-select-option
>
</a-select>
</template>
<script lang="ts" setup>
import {
edgeCollector,
edgePoint,
} from '@/api/device/instance';
const _props = defineProps({
modelValue: {
type: String,
default: undefined,
},
type: {
type: String,
default: 'POINT',
},
id: {
type: String,
default: '',
},
edgeId: {
type: String,
default: '',
}
});
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
type Emits = {
(e: 'update:modelValue', data: string | undefined): void;
};
const emit = defineEmits<Emits>();
const list = ref<any[]>([]);
const _value = ref<string | undefined>(undefined);
watchEffect(() => {
_value.value = _props.modelValue;
});
const onChange = (_val: string) => {
emit('update:modelValue', _val);
};
const getCollector = async (_val: string) => {
if (!_val) {
return [];
} else {
const resp = await edgeCollector(_props.edgeId, {
terms: [
{
terms: [
{
column: 'channelId',
value: _val,
},
],
},
],
});
if (resp.status === 200) {
list.value = (resp.result as any[])?.[0] || []
}
}
};
const getPoint = async (_val: string) => {
if (!_val) {
return [];
} else {
const resp = await edgePoint(_props.edgeId, {
terms: [
{
terms: [
{
column: 'collectorId',
value: _val,
},
],
},
],
});
if (resp.status === 200) {
list.value = (resp.result as any[])?.[0] || []
}
}
};
watchEffect(() => {
if (_props.id) {
if (_props.type === 'POINT') {
getPoint(_props.id);
} else {
getCollector(_props.id);
}
} else {
list.value = [];
}
});
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,212 @@
<template>
<a-modal
width="900px"
title="批量映射"
visible
@ok="handleClick"
@cancel="handleClose"
>
<div class="map-tree">
<div class="map-tree-top">
采集器的点位名称与属性名称一致时将自动映射绑定有多个采集器点位名称与属性名称一致时以第1个采集器的点位数据进行绑定
</div>
<a-spin :spinning="loading">
<div class="map-tree-content">
<a-card class="map-tree-content-card" title="源数据">
<a-tree
checkable
:height="300"
:tree-data="dataSource"
:checkedKeys="checkedKeys"
@check="onCheck"
/>
</a-card>
<div style="width: 100px">
<a-button
:disabled="rightList.length >= leftList.length"
@click="onRight"
>加入右侧</a-button
>
</div>
<a-card class="map-tree-content-card" title="采集器">
<a-list
size="small"
:data-source="rightList"
class="map-tree-content-card-list"
>
<template #renderItem="{ item }">
<a-list-item>
{{ item.title }}
<template #actions>
<a-popconfirm
title="确定删除?"
@confirm="_delete(item.key)"
>
<AIcon type="DeleteOutlined" />
</a-popconfirm>
</template>
</a-list-item>
</template>
</a-list>
</a-card>
</div>
</a-spin>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { treeEdgeMap, saveEdgeMap, addDevice } from '@/api/device/instance';
import { message } from 'ant-design-vue/es';
const _props = defineProps({
metaData: {
type: Array,
default: () => [],
},
deviceId: {
type: String,
default: '',
},
edgeId: {
type: String,
default: '',
},
deviceData: {
type: Object,
},
});
const _emits = defineEmits(['close', 'save']);
const checkedKeys = ref<string[]>([]);
const leftList = ref<any[]>([]);
const rightList = ref<any[]>([]);
const dataSource = ref<any[]>([]);
const loading = ref<boolean>(false);
const handleData = (data: any[], type: string) => {
data.forEach((item) => {
item.key = item.id;
item.title = item.name;
item.checkable = type === 'collectors';
if (
item.collectors &&
Array.isArray(item.collectors) &&
item.collectors.length
) {
item.children = handleData(item.collectors, 'collectors');
}
if (item.points && Array.isArray(item.points) && item.points.length) {
item.children = handleData(item.points, 'points');
}
});
return data as any[];
};
const handleSearch = async () => {
loading.value = true;
const resp = await treeEdgeMap(_props.edgeId);
loading.value = false;
if (resp.status === 200) {
dataSource.value = handleData((resp.result as any[])?.[0], 'channel');
}
};
const onCheck = (keys: string[], e: any) => {
checkedKeys.value = [...keys];
leftList.value = e?.checkedNodes || [];
};
const onRight = () => {
rightList.value = leftList.value;
};
const _delete = (_key: string) => {
const _index = rightList.value.findIndex((i) => i.key === _key);
rightList.value.splice(_index, 1);
checkedKeys.value = rightList.value.map((i) => i.key);
leftList.value = rightList.value;
};
const handleClick = async () => {
if (!rightList.value.length) {
message.warning('请选择采集器');
} else {
const params: any[] = [];
rightList.value.map((item: any) => {
const array = (item.children || []).map((element: any) => ({
channelId: item.parentId,
collectorId: element.collectorId,
pointId: element.id,
metadataType: 'property',
metadataId: (_props.metaData as any[]).find(
(i: any) => i.name === element.name,
)?.metadataId,
provider: dataSource.value.find(
(it: any) => it.id === item.parentId,
).provider,
}));
params.push(...array);
});
const filterParms = params.filter((item) => !!item.metadataId);
if (_props.deviceId) {
if (filterParms && filterParms.length !== 0) {
const res = await saveEdgeMap(_props.edgeId, {
deviceId: _props.deviceId,
provider: filterParms[0]?.provider,
requestList: filterParms,
});
if (res.status === 200) {
message.success('操作成功');
_emits('save');
}
} else {
message.error('暂无对应属性的映射');
}
} else {
if (filterParms && filterParms.length !== 0) {
const res = await addDevice(_props.deviceData);
if (res.status === 200) {
const resq = await saveEdgeMap(_props.edgeId, {
deviceId: res.result?.id,
provider: filterParms[0]?.provider,
requestList: filterParms,
});
if (res.status === 200) {
message.success('操作成功');
_emits('save');
}
}
}
}
}
};
const handleClose = () => {
_emits('close');
};
onMounted(() => {
if (_props.edgeId) {
handleSearch();
}
});
</script>
<style lang="less" scoped>
.map-tree-content {
margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;
.map-tree-content-card {
width: 350px;
height: 400px;
.map-tree-content-card-list {
overflow-y: auto;
height: 300px;
}
}
}
</style>

View File

@ -0,0 +1,337 @@
<template>
<a-spin :spinning="loading" v-if="_metadata">
<a-card :bordered="false">
<template #title>
<TitleComponent data="点位映射"></TitleComponent>
</template>
<template #extra>
<a-space>
<a-button @click="showModal">批量映射</a-button>
<a-button type="primary" @click="onSave">保存</a-button>
</a-space>
</template>
<a-form ref="formRef" :model="modelRef">
<a-table :dataSource="modelRef.dataSource" :columns="columns">
<template #headerCell="{ column }">
<template v-if="column.key === 'collectorId'">
采集器
<a-tooltip title="边缘网关代理的真实物理设备">
<AIcon type="QuestionCircleOutlined" />
</a-tooltip>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.dataIndex === 'channelId'">
<a-form-item
:name="['dataSource', index, 'channelId']"
>
<a-select
style="width: 100%"
v-model:value="record[column.dataIndex]"
placeholder="请选择"
allowClear
:filter-option="filterOption"
>
<a-select-option
v-for="item in channelList"
:key="item.value"
:value="item.value"
:label="item.label"
>{{ item.label }}</a-select-option
>
</a-select>
</a-form-item>
</template>
<template v-if="column.dataIndex === 'collectorId'">
<a-form-item
:name="['dataSource', index, 'collectorId']"
:rules="[
{
required: !!record.channelId,
message: '请选择采集器',
},
]"
>
<MSelect
v-model="record[column.dataIndex]"
:id="record.channelId"
type="COLLECTOR"
:edgeId="instanceStore.current.id"
/>
</a-form-item>
</template>
<template v-if="column.dataIndex === 'pointId'">
<a-form-item
:name="['dataSource', index, 'pointId']"
:rules="[
{
required: !!record.channelId,
message: '请选择点位',
},
]"
>
<MSelect
v-model="record[column.dataIndex]"
:id="record.collectorId"
type="POINT"
:edgeId="instanceStore.current.id"
/>
</a-form-item>
</template>
<template v-if="column.dataIndex === 'id'">
<a-badge
v-if="record[column.dataIndex]"
status="success"
text="已绑定"
/>
<a-badge v-else status="error" text="未绑定" />
</template>
<template v-if="column.key === 'action'">
<a-tooltip title="解绑">
<a-popconfirm
title="确认解绑"
:disabled="!record.id"
@confirm="unbind(record.id)"
>
<a-button type="link" :disabled="!record.id"
><AIcon type="icon-jiebang"
/></a-button>
</a-popconfirm>
</a-tooltip>
</template>
</template>
</a-table>
</a-form>
</a-card>
<PatchMapping
:deviceId="instanceStore.current.parentId"
v-if="visible"
@close="visible = false"
@save="onPatchBind"
:metaData="modelRef.dataSource"
:edgeId="instanceStore.current.id"
:deviceData="deviceData"
/>
</a-spin>
<a-card v-else>
<JEmpty description="暂无数据,请配置物模型" style="margin: 10% 0" />
</a-card>
</template>
<script lang="ts" setup>
import { useInstanceStore } from '@/store/instance';
import {
getEdgeMap,
saveEdgeMap,
removeEdgeMap,
edgeChannel,
addDevice,
editDevice,
} from '@/api/device/instance';
import MSelect from './MSelect.vue';
import PatchMapping from './PatchMapping.vue';
import { message } from 'ant-design-vue/es';
import { inject } from 'vue';
const columns = [
{
title: '名称',
dataIndex: 'metadataName',
key: 'metadataName',
width: '20%',
},
{
title: '通道',
dataIndex: 'channelId',
key: 'channelId',
width: '20%',
},
{
title: '采集器',
dataIndex: 'collectorId',
key: 'collectorId',
width: '20%',
},
{
title: '点位',
key: 'pointId',
dataIndex: 'pointId',
width: '20%',
},
{
title: '状态',
key: 'id',
dataIndex: 'id',
width: '10%',
},
{
title: '操作',
key: 'action',
width: '10%',
},
];
const validate = inject('validate');
const form = ref();
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const { productList } = defineProps(['productList']);
const _emit = defineEmits(['close']);
const instanceStore = useInstanceStore();
let _metadata = ref();
const loading = ref<boolean>(false);
const channelList = ref([]);
const modelRef = reactive({
dataSource: [],
});
const deviceData = ref();
const formRef = ref();
const visible = ref<boolean>(false);
const getChannel = async () => {
if (instanceStore.current?.id) {
const resp: any = await edgeChannel(instanceStore.current.id);
if (resp.status === 200) {
channelList.value = resp.result?.[0]?.map((item: any) => ({
label: item.name,
value: item.id,
provider: item.provider,
}));
}
}
};
const handleSearch = async () => {
loading.value = true;
modelRef.dataSource = _metadata;
getChannel();
if (_metadata && _metadata.length) {
const resp: any = await getEdgeMap(instanceStore.current?.orgId || '', {
deviceId: instanceStore.current.id,
query: {},
}).catch(() => {
modelRef.dataSource = _metadata;
loading.value = false;
});
if (resp.status === 200) {
const array = resp.result?.[0].reduce((x: any, y: any) => {
const metadataId = _metadata.find(
(item: any) => item.metadataId === y.metadataId,
);
if (metadataId) {
Object.assign(metadataId, y);
} else {
x.push(y);
}
return x;
}, _metadata);
modelRef.dataSource = array;
}
}
loading.value = false;
};
const unbind = async (id: string) => {
if (id) {
const resp = await removeEdgeMap(
instanceStore.current?.parentId || '',
{
deviceId: instanceStore.current.id,
idList: [id],
},
);
if (resp.status === 200) {
message.success('操作成功!');
handleSearch();
}
}
};
const onPatchBind = () => {
visible.value = false;
_emit('close');
};
onMounted(() => {
handleSearch();
});
watchEffect(() => {
if (instanceStore.current?.metadata) {
_metadata.value = instanceStore.current?.metadata;
} else {
_metadata.value = {};
}
});
const onSave = async () => {
form.value = await validate();
if (form.value) {
formRef.value.validateFields().then(async () => {
if (modelRef.dataSource.length === 0) {
message.error('请配置物模型');
} else {
channelList.value.forEach((item: any) => {
modelRef.dataSource.forEach((i: any) => {
if (item.value === i.channelId) {
i.provider = item.provider;
}
});
});
const formData = {
...form.value,
productName: productList.find(
(item: any) => item.id === form.value?.productId,
).name,
parentId: instanceStore.current.id,
id: instanceStore.current.parentId
? instanceStore.current.parentId
: undefined,
};
const resq = instanceStore.current.parentId
? await editDevice(formData)
: await addDevice(formData);
if (resq.status === 200) {
const array = modelRef.dataSource.filter(
(item: any) => item.channelId,
);
const submitData = {
deviceId: instanceStore.current.parentId
? instanceStore.current.parentId
: resq.result?.id,
provider: array?.[0]?.provider,
requestList: array,
};
save(submitData);
}
}
});
}
};
const save = async (item: any) => {
const res = await saveEdgeMap(instanceStore.current.id, item);
if (res.status === 200) {
message.success('保存成功');
_emit('close');
}
};
const showModal = async () => {
form.value = await validate();
if (form.value) {
const formData = {
...form.value,
productName: productList.find(
(item: any) => item.id === form.value?.productId,
).name,
parentId: instanceStore.current.id,
};
deviceData.value = formData;
}
visible.value = true;
};
</script>
<style lang="less" scoped>
:deep(.ant-form-item) {
margin: 0 !important;
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<div>
<TitleComponent data="基本信息">
<template #extra>
<j-button @click="comeBack">返回</j-button>
</template>
</TitleComponent>
<j-form layout="vertical" :model="form" ref="formRef">
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
label="设备名称"
name="name"
:rules="{ required: true, message: '请输入设备名称' }"
>
<j-input v-model:value="form.name"></j-input>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="产品名称"
name="productId"
:rules="{ required: true, message: '请选择产品名称' }"
>
<j-select
:disabled="props.childData?.id"
@change="selectChange"
v-model:value="form.productId"
>
<j-select-option
v-for="i in productList"
:key="i.id"
:value="i.id"
>{{ i.name }}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
</j-row>
<j-row :gutter="24" v-if="visible">
<j-col :span="24"
><EdgeMap :productList="productList" @close="comeBack"
/></j-col>
</j-row>
</j-form>
</div>
</template>
<script lang="ts" setup>
import { getProductListNoPage } from '@/api/device/instance';
import EdgeMap from '../EdgeMap/index.vue';
import { useInstanceStore } from '@/store/instance';
import { storeToRefs } from 'pinia';
import { provide } from 'vue';
const instanceStore = useInstanceStore();
const { current } = storeToRefs(instanceStore);
const props = defineProps(['childData']);
const form = reactive({
name: '',
productId: '',
});
const formRef = ref();
const emit = defineEmits(['closeChildSave']);
const productList = ref();
const visible = ref(false);
const getProductList = async () => {
const res = await getProductListNoPage({
terms: [{ column: 'accessProvider', value: 'edge-child-device' }],
});
if (res.status === 200) {
productList.value = res.result;
}
};
getProductList();
const selectChange = (e: any) => {
if (e) {
visible.value = true;
}
const item = productList.value.filter((i: any) => i.id === e)[0];
const array = JSON.parse(item.metadata || [])?.properties?.map(
(i: any) => ({
metadataType: 'property',
metadataName: `${i.name}(${i.id})`,
metadataId: i.id,
name: i.name,
}),
);
current.value.metadata = array;
};
watchEffect(() => {
if (props.childData?.id) {
visible.value = true;
}
});
watchEffect(() => {});
const validate = async () => {
return formRef.value.validateFields();
};
provide('validate',validate);
const comeBack = () =>{
emit('closeChildSave');
}
</script>
<style lang="less" scoped>
</style>

View File

@ -1,12 +1,18 @@
<template>
<a-card>
<SaveChild
v-if="childVisible"
@close-child-save="closeChildSave"
:childData="current"
/>
<div v-else>
<Search
:columns="columns"
target="child-device"
@search="handleSearch"
class="child-device-search"
/>
<JTable
<JProTable
ref="childDeviceRef"
:columns="columns"
:request="query"
@ -28,15 +34,37 @@
:model="'TABLE'"
>
<template #headerTitle>
<a-space>
<a-button type="primary"> 新增并绑定 </a-button>
<a-button type="primary" @click="visible = true">
绑定
</a-button>
<a-popconfirm title="确认解绑吗?" @confirm="handleUnBind">
<a-button type="primary"> 批量解绑 </a-button>
</a-popconfirm>
</a-space>
<j-space>
<PermissionButton
type="primary"
v-if="
detail?.accessProvider ===
'official-edge-gateway'
"
hasPermission="device/Instance:update"
@click="
current = {};
childVisible = true;
"
>新增并绑定</PermissionButton
>
<PermissionButton
type="primary"
@click="visible = true"
hasPermission="device/Instance:update"
>
绑定</PermissionButton
>
<PermissionButton
type="primary"
hasPermission="device/Instance:update"
:popConfirm="{
title: '确定解绑吗?',
onConfirm: handleUnBind,
}"
>批量解除</PermissionButton
>
</j-space>
</template>
<template #registryTime="slotProps">
{{
@ -54,38 +82,32 @@
/>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
v-for="i in getActions(slotProps)"
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
<a-button
<PermissionButton
:disabled="i.disabled"
style="padding: 0"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
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)"
style="padding: 0px"
:hasPermission="'device/Instance:' + i.key"
>
<a-button
:disabled="i.disabled"
style="padding: 0"
type="link"
<template #icon
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-space>
/></template>
</PermissionButton>
</template>
</JTable>
</j-space>
</template>
</JProTable>
<BindChildDevice v-if="visible" @change="closeBindDevice" />
</div>
</a-card>
</template>
@ -97,11 +119,17 @@ import { useInstanceStore } from '@/store/instance';
import { storeToRefs } from 'pinia';
import { message } from 'ant-design-vue';
import BindChildDevice from './BindChildDevice/index.vue';
import { usePermissionStore } from '@/store/permission';
import SaveChild from './SaveChild/index.vue';
const instanceStore = useInstanceStore();
const { detail } = storeToRefs(instanceStore);
const router = useRouter();
const childVisible = ref(false);
const permissionStore = usePermissionStore();
// watchEffect(() => {
// console.log(detail.value);
// });
const statusMap = new Map();
statusMap.set('online', 'success');
statusMap.set('offline', 'error');
@ -111,6 +139,7 @@ const childDeviceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const _selectedRowKeys = ref<string[]>([]);
const visible = ref<boolean>(false);
const current = ref({});
const columns = [
{
@ -192,7 +221,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
},
},
{
key: 'unbind',
key: 'action',
text: '解绑',
tooltip: {
title: '解绑',
@ -215,6 +244,18 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
},
},
},
{
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
},
icon: 'EditOutlined',
onClick: () => {
current.value = data;
childVisible.value = true;
},
},
];
};
@ -252,6 +293,10 @@ const closeBindDevice = (val: boolean) => {
childDeviceRef.value?.reload();
}
};
const closeChildSave = () => {
childVisible.value = false;
};
</script>
<style scoped lang="less">

View File

@ -306,23 +306,6 @@ const columns = [
const _selectedRowKeys = ref<string[]>([]);
const currentForm = ref({});
const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
const cancelSelect = () => {
_selectedRowKeys.value = [];
};
// const handleClick = (dt: any) => {
// if (_selectedRowKeys.value.includes(dt.id)) {
// const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
// _selectedRowKeys.value.splice(_index, 1);
// } else {
// _selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
// }
// };
const getActions = (
data: Partial<Record<string, any>>,
type: 'card' | 'table',

View File

@ -133,22 +133,22 @@ watch(
() => dimension.value,
(val) => {
if (val === 'today') {
dateRange[0] = moment().startOf('day').format('x');
dateRange.value[0] = moment().startOf('day').format('x');
}
if (val === 'week') {
dateRange[0] = moment().subtract(1, 'week').format('x');
dateRange.value[0] = moment().subtract(1, 'week').format('x');
}
if (val === 'month') {
dateRange[0] = moment().subtract(1, 'month').format('x');
dateRange.value[0] = moment().subtract(1, 'month').format('x');
}
if (val === 'year') {
dateRange[0] = moment().subtract(1, 'year').format('x');
dateRange.value[0] = moment().subtract(1, 'year').format('x');
}
dateRange[1] = moment().format('x');
dateRange.value[1] = moment().format('x');
emits('change', {
time: {
start: dateRange[0],
end: dateRange[1],
start: dateRange.value[0],
end: dateRange.value[1],
},
});
},

View File

@ -55,7 +55,22 @@
/>
</j-form-item>
</j-col>
<j-col :span="24">
<j-col :span="24" v-if="route.query.type === 'gb28181-2016'">
<j-form-item
label="厂商"
name="manufacturer"
:rules="[
{ required: false, message: '' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<j-input
v-model:value="formData.manufacturer"
placeholder="请输入厂商名称"
/>
</j-form-item>
</j-col>
<j-col :span="24" v-if="route.query.type === 'fixed-media'">
<j-form-item
name="media_url"
:rules="[
@ -112,6 +127,21 @@
/>
</j-form-item>
</j-col>
<j-col :span="24" v-if="route.query.type === 'gb28181-2016'">
<j-form-item label="云台类型" name="ptzType">
<j-select
v-model:value="formData.ptzType"
:options="[
{ label: '未知', value: 0 },
{ label: '球体', value: 1 },
{ label: '半球体', value: 2 },
{ label: '固定枪机', value: 3 },
{ label: '遥控枪机', value: 4 },
]"
placeholder="请选择云台类型"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item name="description" label="说明">
<j-textarea
@ -163,7 +193,9 @@ const formData = ref({
description: '',
deviceId: route.query.id,
name: '',
// , others
manufacturer: '',
ptzType: '',
// , others
media_password: '',
media_url: '',
media_username: '',
@ -172,6 +204,7 @@ const formData = ref({
watch(
() => props.channelData,
(val: any) => {
console.log('val: ', val);
const {
id,
address,
@ -179,6 +212,8 @@ watch(
description,
deviceId,
name,
manufacturer,
ptzType,
others,
...extra
} = val;
@ -189,6 +224,8 @@ watch(
description,
deviceId,
name,
manufacturer,
ptzType: ptzType?.value || 0,
...others,
};
},
@ -225,6 +262,8 @@ const handleSubmit = () => {
media_url,
media_password,
media_username,
manufacturer,
ptzType,
...extraFormData
} = formData.value;
if (media_url || media_password || media_username) {

View File

@ -84,6 +84,7 @@
?.addresses"
:key="`${i.address}_address${idx}`"
>
<Ellipsis>
<j-badge
:text="i.address"
:color="
@ -92,6 +93,7 @@
: 'green'
"
/>
</Ellipsis>
</p>
</j-col>
</j-row>
@ -263,6 +265,7 @@ const handleCancel = () => {
text-align: center;
.gateway-item {
padding: 16px;
text-align: left;
.card-item-content-title,
.desc,
.subtitle {

View File

@ -294,9 +294,12 @@ const getActions = (
data.state.value === 'notActive' ||
data.provider === 'fixed-media',
icon: 'SyncOutlined',
onClick: () => {
// updateChannel()
console.log('updateChannel: ', data);
onClick: async () => {
const res = await DeviceApi.updateChannels(data.id);
if (res.success) {
message.success('通道更新成功');
listRef.value?.reload();
}
},
},
{

View File

@ -116,6 +116,9 @@
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="
'rule-engine/Instance:' + i.key
"
>
<template #icon
><AIcon :type="i.icon"

View File

@ -1,153 +0,0 @@
<template>
<j-dropdown class='scene-select' trigger='click'>
<div :class='dropdownButtonClass'>
<span :style='LabelStyle'>
{{ label }}
</span>
</div>
<template #overlay>
<template v-if='options.length'>
<j-menu v-if='component === "select"' @click='menuSelect'>
<j-menu-item v-for='item in options' :key='item.value'>{{ item.label }}</j-menu-item>
</j-menu>
<j-tree
:selectedKeys='selectValue ? [selectValue] : []'
:treeData='options'
@select='treeSelect'
/>
</template>
<div class='scene-select-empty' v-else>
<j-empty />
</div>
</template>
</j-dropdown>
</template>
<script lang='ts' setup name='DropdownButton'>
import type { PropType } from 'vue'
type LabelType = string | number | undefined
type DropdownButtonOptions = {
label: string;
value: string;
children?: DropdownButtonOptions[];
[key: string]: any;
};
type Emit = {
(e: 'update:value', data: string | number): void
(e: 'select', data: DropdownButtonOptions | undefined ): void
}
const props = defineProps({
placeholder: {
type: String,
default: undefined
},
value: {
type: [String, Number],
default: undefined
},
options: {
type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => []
},
type: {
type: String,
default: 'column' // 'column' | 'termType' | 'value' | 'type'
},
component: {
type: String,
default: 'select' // 'select' | 'treeSelect'
}
})
const emit = defineEmits<Emit>()
const label = ref<LabelType>(props.placeholder)
const selectValue = ref(props.value)
const flatMapTree = new Map()
const LabelStyle = computed(() => {
return {
color: selectValue.value ? '#' : '#'
}
})
const dropdownButtonClass = computed(() => ({
'dropdown-button': true,
'column': props.type === 'column',
'termType': props.type === 'termType',
'value': props.type === 'value',
'type': props.type === 'type',
}))
const getOption = (key?: string | number): DropdownButtonOptions | undefined => {
let option
for(let i = props.options.length - 1; i >= 0; i --) {
const cacheOption = props.options[i]
if (cacheOption.value === key) {
option = {
...cacheOption
}
break
}
}
return option
}
const treeSelect = () => {
}
const menuSelect = (v: any) => {
const option = getOption(props.value)
emit('update:value', v.key)
emit('select', option)
}
watch([props.options, props.value], () => {
const option = getOption(props.value)
console.log(props.value)
if (option) {
label.value = option.label
} else {
label.value = props.value || props.placeholder
}
}, { immediate: true })
</script>
<style scoped lang='less'>
.dropdown-button {
display: flex;
align-items: center;
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 8px;
cursor: pointer;
}
.column {
color: #00a4fe;
background-color: rgba(154, 219, 255, 0.3);
border-color: rgba(0, 164, 254, 0.3);
}
.termType {
color: #2f54eb;
background-color: rgba(163, 202, 255, 0.3);
border-color: rgba(47, 84, 235, 0.3);
}
.value {
color: #692ca7;
background-color: rgba(188, 125, 238, 0.1);
border-color: rgba(188, 125, 238, 0.5);
}
.type {
padding: 5px 10px;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<j-dropdown class='scene-select' trigger='click'>
<div :class='dropdownButtonClass'>
<AIcon v-if='!!icon' :type='icon' />
{{ label }}
</div>
<template #overlay>
<div class='scene-select-content'>
<template v-if='options.length'>
<drop-menus
v-if='component === "select"'
:value='selectValue'
:options='options'
@click='menuSelect'
/>
<DropdownTimePicker
v-else-if='["date","time"].includes(component)'
:type='component'
@change='timeSelect'
/>
<div v-else>
<j-tree
:selectedKeys='selectValue ? [selectValue] : []'
:treeData='options'
@select='treeSelect'
:height='450'
:virtual='true'
>
<template #title="{ name, description }">
<j-space>
{{ name }}
<span v-if='description' class='tree-title-description'>{{ description }}</span>
</j-space>
</template>
</j-tree>
</div>
</template>
<div class='scene-select-empty' v-else>
<j-empty />
</div>
</div>
</template>
</j-dropdown>
</template>
<script lang='ts' setup name='DropdownButton'>
import type { PropType } from 'vue'
import DropMenus from './Menus.vue'
import DropdownTimePicker from './Time.vue'
import { getOption } from './util'
import type { DropdownButtonOptions } from './util'
type LabelType = string | number | boolean | undefined
type Emit = {
(e: 'update:value', data: string | number): void
(e: 'select', data: DropdownButtonOptions | string | undefined ): void
}
const props = defineProps({
icon: {
type: String,
default: ''
},
placeholder: {
type: String,
default: undefined
},
value: {
type: [String, Number, Boolean],
default: undefined
},
valueName: {
type: String,
default: 'value'
},
labelName: {
type: String,
default: 'label'
},
options: {
type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => []
},
type: {
type: String,
default: 'column' // 'column' | 'termType' | 'value' | 'type'
},
component: {
type: String,
default: 'select' // 'select' | 'treeSelect'
}
})
const emit = defineEmits<Emit>()
const label = ref<LabelType>(props.placeholder)
const selectValue = ref(props.value)
const flatMapTree = new Map()
const LabelStyle = computed(() => {
return {
color: selectValue.value ? '#' : '#'
}
})
const dropdownButtonClass = computed(() => ({
'dropdown-button': true,
'column': props.type === 'column',
'termType': props.type === 'termType',
'value': props.type === 'value',
'type': props.type === 'type',
}))
const treeSelect = (v: any) => {
}
const timeSelect = (v: string) => {
emit('update:value', v)
emit('select', v)
}
const menuSelect = (v: any) => {
const option = getOption(props.options, props.value, props.valueName)
emit('update:value', v.key)
emit('select', option)
}
watchEffect(() => {
const option = getOption(props.options, props.value, props.valueName)
if (option && Object.keys(option).length) {
label.value = option[props.labelName] || option.name
} else {
label.value = props.value || props.placeholder
}
})
</script>
<style scoped lang='less'>
@import './index.less';
</style>

View File

@ -0,0 +1,81 @@
<template>
<j-menu class='scene-dropdown-menus' @click='click' :selectedKeys='[myValue]'>
<j-menu-item v-for='item in myOptions' :key='item.value' :title='item.label'>
{{ item.label }}
</j-menu-item>
</j-menu>
</template>
<script lang='ts' setup name='DropdownMenus'>
import { isBoolean } from 'lodash-es'
import { getOption } from '../DropdownButton/util'
type ValueType = string| number | boolean
type Emits = {
(e: 'update:value', value: ValueType): void
(e: 'click', data: any): void
}
const props = defineProps({
value: {
type: [String, Number, Boolean],
default: undefined
},
options: {
type: Array,
default: () => []
}
})
const emit = defineEmits<Emits>()
const myOptions = computed(() => {
return props.options.map((item: any) => {
let _label = item.label || item.name
if (isBoolean(item.value)) {
_label = item.value === true ? '是' : '否'
}
return {
...item,
label: _label,
value: item.value || item.id,
}
})
})
const myValue = ref(props.value)
const click = (e: any) => {
const option = getOption(myOptions.value, e.key)
myValue.value = e.key
emit('update:value', e.key)
emit('click', {
key: e.key,
...option
})
}
</script>
<style scoped lang='less'>
.scene-dropdown-menus {
border: 0px;
:deep(.ant-menu-item){
height: 32px;
line-height: 32px;
padding: 0 4px;
margin: 0;
&:hover {
background-color: @item-hover-bg;
color: @text-color;
}
&.ant-menu-item-selected {
background-color: @primary-1;
color: @text-color;
}
}
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<div class='dropdown-time-picker'>
<j-time-picker
v-if='type === "time"'
open
class='manual-time-picker'
v-model:value='myValue'
:format='myFormat'
:valueFormat='myFormat'
:getPopupContainer='getPopupContainer'
popupClassName='manual-time-picker-popup'
@change='change'
/>
<j-date-picker
v-else
open
class='manual-time-picker'
v-model:value='myValue'
:format='myFormat'
:valueFormat='myFormat'
:getPopupContainer='getPopupContainer'
@change='change'
/>
</div>
</template>
<script setup lang='ts' name='DropdownTime'>
import dayjs from 'dayjs'
type Emit = {
(e: 'update:value', value: string) : void
(e: 'change', value: string) : void
}
const props = defineProps({
type: {
type: String,
default: 'time' // time | date
},
value: {
type: String,
default: undefined
},
format: {
type: String,
default: ''
}
})
const emit = defineEmits<Emit>()
const myFormat = props.format || ( props.type === 'time' ? 'HH:mm:ss' : 'YYYY-MM-DD HH:mm:ss')
const myValue = ref(props.value || dayjs(new Date()).format(myFormat))
const getPopupContainer = (trigger: HTMLElement) => {
return trigger?.parentNode || document.body
}
const change = (e: string) => {
myValue.value = e
emit('update:value', e)
emit('change', e)
}
</script>
<style lang='less'>
.dropdown-time-picker {
>div{
position: relative !important;
}
.manual-time-picker{
display: none;
}
.ant-picker-dropdown {
position: relative;
top: 0;
left: 0;
width: 100%;
.ant-picker-panel {
width: 100%
}
}
.ant-picker-panel-container {
box-shadow: unset;
}
}
</style>

View File

@ -0,0 +1,44 @@
.dropdown-button {
display: flex;
align-items: center;
padding: 6px 8px;
border: 1px solid #d9d9d9;
border-radius: 8px;
cursor: pointer;
}
.scene-select-content {
position: relative;
min-width: 220px;
padding: 4px;
background: #fff;
border-radius: 2px;
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
}
.column {
color: #00a4fe;
background-color: rgba(154, 219, 255, 0.3);
border-color: rgba(0, 164, 254, 0.3);
}
.termType {
color: #2f54eb;
background-color: rgba(163, 202, 255, 0.3);
border-color: rgba(47, 84, 235, 0.3);
}
.value {
color: #692ca7;
background-color: rgba(188, 125, 238, 0.1);
border-color: rgba(188, 125, 238, 0.5);
}
.type {
padding: 5px 10px;
}
.tree-title-description {
padding-left: 5px;
color: grey;
}

View File

@ -0,0 +1,9 @@
import Dropdown from './DropdownButton.vue'
import DropdownMenus from './Menus.vue'
import DropdownTimePicker from './Time.vue'
export default Dropdown
export {
DropdownMenus, DropdownTimePicker
}

View File

@ -0,0 +1,42 @@
export type DropdownButtonOptions = {
label: string;
value: string;
children?: DropdownButtonOptions[];
[key: string]: any;
};
export const getComponent = (type: string): string => {
switch (type) {
case 'int':
case 'long':
case 'float':
case 'double':
return 'number'
case 'metric':
case 'enum':
case 'boolean':
return 'menu'
case 'date':
return 'date'
case 'tree':
return 'tree'
default:
return 'input'
}
}
export const getOption = (data: any[], value?: string | number | boolean, key: string = 'name'): DropdownButtonOptions | any => {
let option = {}
if (!value) return option
for (let i = 0; i < data.length; i++) {
const item = data[i]
if (item[key] === value) {
option = data[i]
break
} else if (item.children && item.children.length){
option = getOption(item.children, value, key)
if (option) break
}
}
return option
}

View File

@ -1,13 +0,0 @@
<template>
</template>
<script>
export default {
name: 'ParamsDropdown'
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,49 @@
<template>
<ParamsDropdown
v-model:value='myValue[0]'
v-model:source='mySource'
:options='options'
:icon='icon'
:placeholder='placeholder'
:tabs-options='tabsOptions'
@select='onSelect'
/>
<ParamsDropdown
v-model:value='myValue[1]'
v-model:source='mySource'
:icon='icon'
:placeholder='placeholder'
:tabs-options='tabsOptions'
:options='options'
@select='onSelect'
/>
</template>
<script lang='ts' setup name='DoubleParamsDropdown'>
import ParamsDropdown from './index.vue'
import { defaultSetting, ValueType } from './typings'
type Emit = {
(e: 'update:value', data: ValueType): void
(e: 'update:source', data: string): void
(e: 'select', data: any): void
}
const props = defineProps({
...defaultSetting
})
const emit = defineEmits<Emit>()
const myValue = ref<ValueType>(props.value)
const mySource = ref<string>(props.source)
const onSelect = () => {
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,8 @@
import ParamsDropdown from './index.vue'
import DoubleParamsDropdown from './Double.vue'
export default ParamsDropdown
export {
DoubleParamsDropdown
}

View File

@ -0,0 +1,159 @@
<template>
<j-dropdown
class='scene-select-value'
trigger='click'
v-model:visible='visible'
@visibleChange='visibleChange'
>
<div class='dropdown-button value' @click.prevent='visible = true'>
<AIcon v-if='!!icon' :type='icon' />
{{ label }}
</div>
<template #overlay>
<div class='scene-select-content'>
<j-tabs
@change='tabsChange'
v-model:activeKey='mySource'
>
<j-tab-pane v-for='item in tabsOptions' :tab='item.label' :key='item.key'>
<div class='select-box-content'>
<DropdownTimePicker
v-if='["time","date"].includes(item.component)'
:type='item.component'
v-model:value='myValue'
@change='timeChange'
/>
<DropdownMenus
v-if='["metric","enum", "boolean"].includes(item.component)'
:options='options'
@change='onSelect'
/>
<ValueItem
v-else-if='valueItemKey.includes(item.component)'
v-model:modelValue='myValue'
:itemType='getComponent(item.component)'
:options='options'
@change='valueItemChange'
/>
<j-tree
v-else
:selectedKeys='myValue ? [myValue] : []'
:treeData='options'
@select='treeSelect'
:height='450'
:virtual='true'
>
<template #title="{ name, description }">
<j-space>
{{ name }}
<span v-if='description' class='tree-title-description'>{{ description }}</span>
</j-space>
</template>
</j-tree>
</div>
</j-tab-pane>
</j-tabs>
</div>
</template>
</j-dropdown>
</template>
<script lang='ts' setup name='ParamsDropdown'>
import ValueItem from '@/components/ValueItem/index.vue'
import type { ValueType } from './typings'
import { defaultSetting } from './typings'
import { DropdownMenus, DropdownTimePicker} from '../DropdownButton'
import { getComponent, getOption } from '../DropdownButton/util'
const valueItemKey = ['int', 'int','long','float','double','string', 'password']
type Emit = {
(e: 'update:value', data: ValueType): void
(e: 'update:source', data: string): void
(e: 'select', data: any): void
(e: 'tabChange', data: any): void
}
const props = defineProps({
...defaultSetting
})
const emit = defineEmits<Emit>()
const myValue = ref<ValueType>(props.value)
const mySource = ref<string>(props.source)
const label = ref<any>(props.placeholder)
const visible = ref(false)
nextTick(() => {
mySource.value = props.source
myValue.value = props.value
})
const tabsChange = (e: string) => {
mySource.value = e
myValue.value = undefined
}
const updateValue = () => {
emit('update:source', mySource.value)
emit('update:value', myValue.value)
}
const treeSelect = (e: any) => {
console.log('treeSelect', e)
visible.value = false
label.value = e.fullname || e.name
emit('update:value', e.id)
emit('select', e)
}
const valueItemChange = (e: string) => {
console.log('valueItemSelect', e)
label.value = e
emit('update:value', e)
emit('select', e)
}
const sonSelect = (e: string, option: any) => {
visible.value = false
label.value = option.label
emit('update:value', e)
emit('select', e)
}
const timeChange = (e: any) => {
label.value = e
visible.value = false
emit('update:value', e)
emit('select', e)
}
const visibleChange = (v: boolean) => {
visible.value = v
}
watch([props.options, props.value], () => {
const option = getOption(props.options, props.value as string, props.valueName) // label
if (option && Object.keys(option).length) {
label.value = option[props.labelName] || option.name
} else {
label.value = props.value || props.placeholder
}
}, { immediate: true })
</script>
<style scoped lang='less'>
@import '../DropdownButton/index.less';
.manual-time-picker {
position: absolute;
top: -2px;
left: 0;
border: none;
visibility: hidden;
:deep(.ant-picker-input) {
display: none;
}
}
</style>

View File

@ -0,0 +1,58 @@
import type { PropType } from 'vue'
export type LabelType = string | number | undefined
export type DropdownButtonOptions = {
label: string;
value: string;
children?: DropdownButtonOptions[];
[key: string]: any;
};
export type TabsOption = {
label: string;
key: string;
component: string,
options: DropdownButtonOptions[]
}
type ValueArrayType = [string, number]
export type ValueType = string | number | undefined | ValueArrayType
export const defaultSetting = {
icon: {
type: String,
default: ''
},
placeholder: {
type: String,
default: undefined
},
value: {
type: [String, Number, Array] as PropType<ValueType>,
default: undefined
},
valueName: {
type: String,
default: 'value'
},
labelName: {
type: String,
default: 'label'
},
source: {
type: String,
default: 'fixed'
},
options: {
type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => []
},
metricOptions: { // 指标值
type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => []
},
tabsOptions: {
type: Array as PropType<Array<TabsOption>>,
default: () => []
}
}

View File

@ -16,8 +16,11 @@
@mouseout='mouseout'
>
<DropdownButton
:options='options'
:options='columnOptions'
icon='icon-zhihangdongzuoxie-1'
type='column'
value-name='column'
label-name='fullName'
placeholder='请选择参数'
v-model:value='paramsValue.column'
component='treeSelect'
@ -26,18 +29,41 @@
<DropdownButton
:options='termTypeOptions'
type="termType"
value-name='id'
label-name='name'
placeholder="操作符"
v-model:value='paramsValue.termsType'
v-model:value='paramsValue.termType'
@select='termsTypeSelect'
/>
<termplate v-if='showDouble'>
</termplate>
<DoubleParamsDropdown
v-if='showDouble'
icon='icon-canshu'
placeholder='参数值'
:options='valueOptions'
:tabsOptions='[
{ label: "手动输入", component: "input", key: "fixed" },
{ label: "指标值", component: "time", key: "manual" }
]'
v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source'
/>
<ParamsDropdown
v-else
icon='icon-canshu'
placeholder='参数值'
:options='valueOptions'
:tabsOptions='[
{ label: "手动输入", component: "time", key: "fixed" },
{ label: "指标值", component: "input", key: "manual" },
]'
v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source'
/>
<j-popconfirm title='确认删除?' @confirm='onDelete'>
<div v-show='showDelete' class='button-delete'> <AIcon type='CloseOutlined' /></div>
</j-popconfirm>
</div>
<div class='term-add' @click.stop='termAdd'>
<div class='term-add' @click.stop='termAdd' v-if='isLast'>
<div class='terms-content'>
<AIcon type='PlusOutlined' style='font-size: 12px' />
</div>
@ -48,7 +74,9 @@
<script setup lang='ts' name='ParamsItem'>
import type { PropType } from 'vue'
import type { TermsType } from '@/views/rule-engine/Scene/typings'
import DropdownButton from '../DropdownButton.vue'
import DropdownButton from '../DropdownButton'
import { getOption } from '../DropdownButton/util'
import ParamsDropdown, { DoubleParamsDropdown } from '../ParamsDropdown'
import { inject } from 'vue'
import { ContextKey } from './util'
@ -70,32 +98,33 @@ const props = defineProps({
default: () => ({
column: '',
type: '',
termsType: undefined,
termType: undefined,
value: {
source: 'fixed',
value: undefined
}
})
}
})
const paramsValue = reactive<TermsType>({
column: '',
type: '',
termType: undefined,
value: undefined
column: props.value.column,
type: props.value.type,
termType: props.value.termType,
value: props.value.value
})
const showDelete = ref(false)
const columnOptions = inject(ContextKey)
const options = computed(() => {
function handleOptions() {
}
return []
})
const columnOptions: any = inject(ContextKey)
const options = ref<any>([])
const termTypeOptions = computed(() => {
const option = getOption(columnOptions.value, paramsValue.column, 'column')
return option && Object.keys(option).length ? option.termTypes : []
})
const tabsOptions = computed(() => {
// valueoption
return []
})
@ -131,7 +160,11 @@ const onDelete = () => {
}
onMounted(() => {
const valueOptions = computed(() => {
return []
})
nextTick(() => {
Object.assign(paramsValue, props.value)
})

View File

@ -56,7 +56,8 @@ const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore)
const open = ref(false)
const columnOptions = ref<any[]>([])
const columnOptions = ref<any>([])
provide(ContextKey, columnOptions)
@ -80,7 +81,7 @@ const queryColumn = (dataModel: FormModelType) => {
const cloneDevice = cloneDeep(dataModel)
cloneDevice.branches = cloneDevice.branches?.filter(item => !!item)
getParseTerm(cloneDevice).then(res => {
columnOptions.value = res as any
columnOptions.value = res.result
})
}
@ -89,12 +90,12 @@ const addBranches = () => {
}
const branchesDelete = (index: number) => {
if ((data as FormModelType).branches?.length === 2) {
(data as FormModelType).branches?.splice(index, 1, null as any)
if (data.value.branches?.length === 2) {
data.value.branches?.splice(index, 1, null as any)
} else {
(data as FormModelType).branches?.splice(index, 1)
data.value.branches?.splice(index, 1)
}
(data as FormModelType).options?.when?.splice(index, 1)
data.value.options?.when?.splice(index, 1)
}
const branchesDeleteAll = () => {
@ -102,15 +103,16 @@ const branchesDeleteAll = () => {
}
watchEffect(() => {
if ((data as FormModelType).trigger?.device) {
queryColumn((data as FormModelType))
console.log(data.value.trigger, data.value.trigger?.device)
if (data.value.trigger?.device) {
queryColumn(data.value)
}
})
watchEffect(() => {
open.value = !(
(data as FormModelType).branches &&
(data as FormModelType).branches?.length === 1
data.value.branches &&
data.value.branches?.length === 1
)
})

View File

@ -48,7 +48,7 @@
<script setup lang='ts' name='TermsItem'>
import type { PropType } from 'vue'
import type { TermsType } from '@/views/rule-engine/Scene/typings'
import DropdownButton from '../DropdownButton.vue'
import DropdownButton from '../DropdownButton'
import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
import ParamsItem from './ParamsItem.vue'
@ -86,14 +86,12 @@ const props = defineProps({
const showDelete = ref(false)
const mouseover = () => {
console.log(props.whenName)
if (props.whenName !== 0){
showDelete.value = true
}
}
const mouseout = () => {
console.log(props.whenName)
if (props.whenName !== 0){
showDelete.value = false
}

View File

@ -1,2 +1 @@
export const ContextKey = 'columnOptions'

View File

@ -50,7 +50,6 @@ export default defineConfig(({ mode}) => {
},
},
plugins: [
vue(),
monacoEditorPlugin({}),
vueJsx(),