Merge branch 'dev' into dev-hub
This commit is contained in:
commit
08ca907b99
|
@ -100,7 +100,7 @@ const matchComponents: IMatcher[] = [
|
|||
},
|
||||
{
|
||||
pattern: /^TimePicker|^TimeRangePicker/,
|
||||
styleDir: 'TimeTicker'
|
||||
styleDir: 'TimePicker'
|
||||
},
|
||||
{
|
||||
pattern: /^Radio/,
|
||||
|
|
|
@ -15,7 +15,7 @@ export const unBind_api = (appId: string) => server.post(`/application/sso/${app
|
|||
* @param type 类型
|
||||
* @param name 值
|
||||
*/
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`,name,{
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`,name,{},{
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export const validateField_api = (type: 'username' | 'password', name: string)
|
|||
* 校验旧密码是否正确
|
||||
* @param password 旧密码
|
||||
*/
|
||||
export const checkOldPassword_api = (password:string) => server.post(`/user/me/password/_validate`,password,{
|
||||
export const checkOldPassword_api = (password:string) => server.post(`/user/me/password/_validate`,password,{},{
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
// 获取记录列表
|
||||
export const getList_api = (data:object): any =>server.get(`/notifications/_query`,encodeParams(data))
|
||||
export const getList_api = (data:object): any =>server.post(`/notifications/_query`,data)
|
||||
// 修改记录状态
|
||||
export const changeStatus_api = (type:'_read'|'_unread',data:string[]): any =>server.post(`/notifications/${type}`,data)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -100,7 +100,7 @@ export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boo
|
|||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}`
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? `/${productId}` : ''}/export.${type}`
|
||||
|
||||
/**
|
||||
* 验证设备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
|
||||
|
|
|
@ -16,6 +16,9 @@ export default {
|
|||
// 删除
|
||||
del: (id: string) => server.remove(`/media/channel/${id}`),
|
||||
|
||||
// 查询树形数据
|
||||
queryTree: (id: string, data?: any) => server.post(`/media/device/${id}/catalog/_query/tree`, data),
|
||||
|
||||
// ========== 视频播放 ==========
|
||||
// 开始直播
|
||||
ptzStart: (deviceId: string, channelId: string, type: string) =>
|
||||
|
|
|
@ -7,7 +7,7 @@ export const getUserType_api = () => server.get(`/user/detail/types`);
|
|||
export const getUserList_api = (data: object) => server.post(`/user/detail/_query`, data);
|
||||
|
||||
// 校验字段合法性
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`, name, {
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`, name,{}, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export const addUser_api = (data: object) => server.post(`/user/detail/_create`,
|
|||
// 更新用户
|
||||
export const updateUser_api = (data: any) => server.put(`/user/detail/${data.id}/_update`, data);
|
||||
// 更新密码
|
||||
export const updatePassword_api = (data: { id: string, password: string }) => server.post(`/user/${data.id}/password/_reset`, data.password, {
|
||||
export const updatePassword_api = (data: { id: string, password: string }) => server.post(`/user/${data.id}/password/_reset`, data.password,{}, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ const iconKeys = [
|
|||
'CloudDownloadOutlined',
|
||||
'PauseCircleOutlined',,
|
||||
'FormOutlined',
|
||||
'EyeInvisibleOutlined',
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<a-table :columns="columns" :data-source="property" :pagination="false" bordered size="small">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-input v-model:value="record.id" size="small"></a-input>
|
||||
<j-auto-complete :options="options" v-model:value="record.id" size="small" width="130px"/>
|
||||
</template>
|
||||
<template v-if="column.key === 'current'">
|
||||
<a-input v-model:value="record.current" size="small"></a-input>
|
||||
|
@ -58,8 +58,8 @@
|
|||
</div>
|
||||
<div class="log">
|
||||
<a-descriptions>
|
||||
<a-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')" :key="item.time"
|
||||
:span="3">
|
||||
<a-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')"
|
||||
:key="item.time" :span="3">
|
||||
<a-tooltip placement="top" :title="item.content">
|
||||
{{ item.content }}
|
||||
</a-tooltip>
|
||||
|
@ -78,6 +78,7 @@ import { message } from 'ant-design-vue';
|
|||
import { useRuleEditorStore } from '@/store/ruleEditor';
|
||||
import moment from 'moment';
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
import { PropertyMetadata } from '@/views/device/Product/typings';
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -135,25 +136,25 @@ const runScript = () => {
|
|||
});
|
||||
|
||||
if (ws.value) {
|
||||
ws.value.unsubscribe();
|
||||
ws.value.unsubscribe?.();
|
||||
}
|
||||
if (!props.virtualRule?.script) {
|
||||
isBeginning.value = true;
|
||||
message.warning('请编辑规则');
|
||||
return;
|
||||
}
|
||||
ws.value = getWebSocket(`virtual-property-debug-${ruleEditorStore.state.property}-${new Date().getTime()}`,
|
||||
'/virtual-property-debug',
|
||||
{
|
||||
virtualId: `${virtualIdRef.value}-virtual-id`,
|
||||
property: ruleEditorStore.state.property,
|
||||
virtualRule: {
|
||||
...props.virtualRule,
|
||||
},
|
||||
properties: _properties || [],
|
||||
})
|
||||
ws.value = getWebSocket(`virtual-property-debug-${props.id}-${new Date().getTime()}`,
|
||||
'/virtual-property-debug',
|
||||
{
|
||||
virtualId: `${virtualIdRef.value}-virtual-id`,
|
||||
property: props.id,
|
||||
virtualRule: {
|
||||
...props.virtualRule,
|
||||
},
|
||||
properties: _properties || [],
|
||||
})
|
||||
ws.value.subscribe((data: any) => {
|
||||
ruleEditorStore.state.log.push({ time: new Date().getTime(), content: JSON.stringify(data.payload) });
|
||||
ruleEditorStore.state.log.push({ time: new Date().getTime(), content: JSON.stringify(data.payload) });
|
||||
})
|
||||
}
|
||||
const beginAction = () => {
|
||||
|
@ -163,7 +164,7 @@ const beginAction = () => {
|
|||
const stopAction = () => {
|
||||
isBeginning.value = true;
|
||||
if (ws.value) {
|
||||
ws.value.unsubscribe();
|
||||
ws.value.unsubscribe?.();
|
||||
}
|
||||
}
|
||||
const clearAction = () => {
|
||||
|
@ -172,9 +173,21 @@ const clearAction = () => {
|
|||
|
||||
onUnmounted(() => {
|
||||
if (ws.value) {
|
||||
ws.value.unsubscribe();
|
||||
ws.value.unsubscribe?.();
|
||||
}
|
||||
})
|
||||
|
||||
const options = ref<{ label: string, value: string }[]>()
|
||||
const getProperty = () => {
|
||||
const metadata = productStore.current.metadata || '{}';
|
||||
const _p: PropertyMetadata[] = JSON.parse(metadata).properties || [];
|
||||
options.value = _p.filter((p) => p.id !== props.id).map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
console.log(options.value)
|
||||
}
|
||||
getProperty()
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.debug-container {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<Editor key="simple" @change="change" v-model:value="_value" :id="id" />
|
||||
{{ _value }}
|
||||
<Advance v-if="ruleEditorStore.state.model === 'advance'" v-model:value="_value" :model="ruleEditorStore.state.model"
|
||||
:virtualRule="virtualRule" :id="id" @change="change" />
|
||||
</template>
|
||||
|
|
|
@ -2,36 +2,36 @@
|
|||
<div class="indicator-box">
|
||||
<template v-if="['int', 'long', 'double', 'float'].includes(type)">
|
||||
<template v-if="value.range">
|
||||
<a-input-number v-model:value="value.value[0]" :max="value.value[1]" size="small"
|
||||
style="width: 100%;"></a-input-number>
|
||||
<j-input-number v-model:value="value.value[0]" :max="value.value[1]" size="small"
|
||||
style="width: 100%;"></j-input-number>
|
||||
~
|
||||
<a-input-number v-model:value="value.value[1]" :min="value.value[0]" size="small"
|
||||
style="width: 100%;"></a-input-number>
|
||||
<j-input-number v-model:value="value.value[1]" :min="value.value[0]" size="small"
|
||||
style="width: 100%;"></j-input-number>
|
||||
</template>
|
||||
<a-input-number v-else v-model:value="value.value" size="small" style="width: 100%;"></a-input-number>
|
||||
<j-input-number v-else v-model:value="value.value" size="small" style="width: 100%;"></j-input-number>
|
||||
</template>
|
||||
<template v-else-if="type === 'date'">
|
||||
<a-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" />
|
||||
<a-date-picker v-else show-time v-model:value="value.value" size="small" />
|
||||
<j-range-picker v-if="value.range" show-time v-model:value="value.value" size="small" />
|
||||
<j-date-picker v-else show-time v-model:value="value.value" size="small" />
|
||||
</template>
|
||||
<template v-else-if="type === 'boolean'">
|
||||
<a-select v-model:value="value.value[0]" :options="list" size="small" placeholder="请选择"></a-select>
|
||||
<j-select v-model:value="value.value[0]" :options="list" size="small" placeholder="请选择"></j-select>
|
||||
</template>
|
||||
<template v-else-if="type === 'string'">
|
||||
<a-input v-model:value="value.value" size="small" placeholder="请输入"></a-input>
|
||||
<j-input v-model:value="value.value" size="small" placeholder="请输入"></j-input>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="value.range">
|
||||
<a-input v-model:value="value.value[0]" :max="value.value[1]" size="small" placeholder="请输入"></a-input>
|
||||
<j-input v-model:value="value.value[0]" :max="value.value[1]" size="small" placeholder="请输入"></j-input>
|
||||
~
|
||||
<a-input v-model:value="value.value[1]" :min="value.value[0]" size="small" placeholder="请输入"></a-input>
|
||||
<j-input v-model:value="value.value[1]" :min="value.value[0]" size="small" placeholder="请输入"></j-input>
|
||||
</template>
|
||||
<a-input-number v-else v-model:value="value.value" size="small" placeholder="请输入"></a-input-number>
|
||||
<j-input-number v-else v-model:value="value.value" size="small" placeholder="请输入"></j-input-number>
|
||||
</template>
|
||||
<div v-if="type !== 'boolean' && type !== 'string'">
|
||||
<a-checkbox style="min-width: 60px; margin-left: 5px;" v-model:checked="value.range" @change="changeChecked">
|
||||
<j-checkbox style="min-width: 60px; margin-left: 5px;" v-model:checked="value.range" @change="changeChecked">
|
||||
范围
|
||||
</a-checkbox>
|
||||
</j-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -26,7 +26,7 @@ import { notification } from 'ant-design-vue';
|
|||
import { changeStatus_api } from '@/api/account/notificationRecord';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
const updateCount = computed(()=>useUserInfo().$state.alarmUpdateCount);
|
||||
const updateCount = computed(() => useUserInfo().$state.alarmUpdateCount);
|
||||
|
||||
const total = ref(0);
|
||||
const list = ref<any[]>([]);
|
||||
|
@ -50,10 +50,20 @@ const subscribeNotice = () => {
|
|||
const getList = () => {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
'terms[0].column': 'state',
|
||||
'terms[0].value': 'unread',
|
||||
'sorts[0].name': 'notifyTime',
|
||||
'sorts[0].order': 'desc',
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
type: 'or',
|
||||
value: 'unread',
|
||||
termType: 'eq',
|
||||
column: 'state',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
getList_api(params)
|
||||
.then((resp: any) => {
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
<template>
|
||||
<a-popover :visible="visible" placement="left">
|
||||
<j-popover :visible="visible" placement="left">
|
||||
<template #title>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置元素</div>
|
||||
<AIcon type="CloseOutlined" @click="visible = false" />
|
||||
<div @click="visible = false"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style="max-width: 400px;">
|
||||
<div class="ant-form-vertical">
|
||||
<value-type-form v-model:value="_value" :name="name" isSub key="sub"></value-type-form>
|
||||
<a-form-item label="说明" :name="name.concat(['description'])" :rules="[
|
||||
<j-form-item label="说明" :name="name.concat(['description'])" :rules="[
|
||||
{ max: 200, message: '最多可输入200个字符' },
|
||||
]">
|
||||
<a-textarea v-model:value="_value.description" size="small"></a-textarea>
|
||||
</a-form-item>
|
||||
<j-textarea v-model:value="_value.description" size="small"></j-textarea>
|
||||
</j-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-button type="dashed" block @click="visible = true">
|
||||
<j-button type="dashed" block @click="visible = true">
|
||||
配置元素
|
||||
<AIcon type="EditOutlined" class="item-icon" />
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</j-button>
|
||||
</j-popover>
|
||||
</template>
|
||||
<script setup lang="ts" name="ArrayParam">
|
||||
import ValueTypeForm from '@/views/device/components/Metadata/Base/Edit/ValueTypeForm.vue';
|
||||
|
@ -33,7 +33,7 @@ type ValueType = Record<any, any>;
|
|||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<ValueType>,
|
||||
default: () => ({ extends: {} })
|
||||
default: () => ({ expands: {} })
|
||||
},
|
||||
name: {
|
||||
type: Array as PropType<(string | number)[]>,
|
||||
|
@ -55,7 +55,7 @@ const _value = computed({
|
|||
const visible = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
emit('update:value', { extends: {}, ...props.value })
|
||||
emit('update:value', { expands: {}, ...props.value })
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
<template>
|
||||
<div class="boolean-param">
|
||||
<a-row :gutter="4">
|
||||
<a-col :span="12">
|
||||
<a-form-item label=" " :name="name.concat(['trueText'])" :rules="[
|
||||
<j-row :gutter="4">
|
||||
<j-col :span="12">
|
||||
<j-form-item label=" " :name="name.concat(['trueText'])" :rules="[
|
||||
{ required: true, message: '请输入trueText' },
|
||||
]">
|
||||
<a-input v-model:value="value.trueText" placeholder="trueText" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="-" :name="name.concat(['trueValue'])" :rules="[
|
||||
<j-input v-model:value="value.trueText" placeholder="trueText" size="small" />
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item label="-" :name="name.concat(['trueValue'])" :rules="[
|
||||
{ required: true, message: '请输入trueValue' },
|
||||
]">
|
||||
<a-input v-model:value="value.trueValue" placeholder="trueValue" size="small"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label=" " :name="name.concat(['falseText'])" :rules="[
|
||||
<j-input v-model:value="value.trueValue" placeholder="trueValue" size="small"/>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item label=" " :name="name.concat(['falseText'])" :rules="[
|
||||
{ required: true, message: '请输入falseText' },
|
||||
]">
|
||||
<a-input v-model:value="value.falseText" placeholder="falseText" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="-" :name="name.concat(['falseValue'])" :rules="[
|
||||
<j-input v-model:value="value.falseText" placeholder="falseText" size="small" />
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item label="-" :name="name.concat(['falseValue'])" :rules="[
|
||||
{ required: true, message: '请输入falseValue' },
|
||||
]">
|
||||
<a-input v-model:value="value.falseValue" placeholder="falseValue" size="small" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<j-input v-model:value="value.falseValue" placeholder="falseValue" size="small" />
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="BooleanParam">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-popover placement="left" trigger="click">
|
||||
<j-popover placement="left" trigger="click">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">{{ config.name }}</div>
|
||||
|
@ -7,18 +7,18 @@
|
|||
</template>
|
||||
<template #content>
|
||||
<div style="max-width: 400px;" class="ant-form-vertical">
|
||||
<a-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name">
|
||||
<a-select v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
|
||||
<j-form-item v-for="item in config.properties" :name="name.concat([item.property])" :label="item.name">
|
||||
<j-select v-model:value="value[item.property]" :options="item.type?.elements?.map((e: { 'text': string, 'value': string }) => ({
|
||||
label: e.text,
|
||||
value: e.value,
|
||||
}))" size="small"></a-select>
|
||||
</a-form-item>
|
||||
}))" size="small"></j-select>
|
||||
</j-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<a-button type="dashed" block>
|
||||
<j-button type="dashed" block>
|
||||
存储配置<AIcon type="EditOutlined" class="item-icon"/>
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</j-button>
|
||||
</j-popover>
|
||||
</template>
|
||||
<script setup lang="ts" name="ConfigParam">
|
||||
import { PropType } from 'vue';
|
||||
|
|
|
@ -5,43 +5,45 @@
|
|||
<AIcon type="MenuOutlined" class="item-drag item-icon" />
|
||||
</div>
|
||||
<div class="item-middle item-editable">
|
||||
<a-popover :visible="editIndex === index" placement="top">
|
||||
<j-popover :visible="editIndex === index" placement="top">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">枚举项配置</div>
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
<div @click="handleClose"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="ant-form-vertical">
|
||||
<a-form-item label="Value" :name="name.concat([index, 'value'])" :rules="[
|
||||
<j-form-item label="Value" :name="name.concat([index, 'value'])" :rules="[
|
||||
{ required: true, message: '请输入Value' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].value" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[
|
||||
<j-input v-model:value="_value[index].value" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[
|
||||
{ required: true, message: '请输入Text' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].text" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<j-input v-model:value="_value[index].text" size="small"></j-input>
|
||||
</j-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<div class="item-edit" @click="handleEdit(index)">
|
||||
{{ item.text || '枚举项配置' }}
|
||||
<AIcon type="EditOutlined" class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</j-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<j-button type="dashed" block @click="handleAdd">
|
||||
<template #icon>
|
||||
<AIcon type="PlusOutlined" class="item-icon" />
|
||||
</template>
|
||||
新增枚举型
|
||||
</a-button>
|
||||
</j-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="BooleanParam">
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
<AIcon type="MenuOutlined" class="item-drag item-icon" />
|
||||
</div>
|
||||
<div class="item-middle item-editable">
|
||||
<a-popover :visible="editIndex === index" placement="left">
|
||||
<j-popover :visible="editIndex === index" placement="left">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置参数</div>
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
<div @click="handleClose"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style="max-width: 400px;" class="ant-form-vertical">
|
||||
<a-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
|
||||
<j-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
|
||||
{ required: true, message: '请输入标识' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{
|
||||
|
@ -22,14 +22,14 @@
|
|||
message: 'ID只能由数字、字母、下划线、中划线组成',
|
||||
},
|
||||
]">
|
||||
<a-input v-model:value="_value[index].id" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
|
||||
<j-input v-model:value="_value[index].id" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<j-input v-model:value="_value[index].name" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<value-type-form v-model:value="_value[index].valueType" :name="name.concat([index, 'valueType'])" isSub
|
||||
key="json_sub"></value-type-form>
|
||||
</div>
|
||||
|
@ -38,18 +38,18 @@
|
|||
{{ item.name || '配置参数' }}
|
||||
<AIcon type="EditOutlined" class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</j-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
|
||||
<div class="item-right" @click="handleDelete(index)">
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<j-button type="dashed" block @click="handleAdd">
|
||||
<template #icon>
|
||||
<AIcon type="PlusOutlined" class="item-icon" />
|
||||
</template>
|
||||
添加参数
|
||||
</a-button>
|
||||
</j-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="JsonParam">
|
||||
|
@ -96,6 +96,7 @@ const handleDelete = (index: number) => {
|
|||
_value.value.splice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
console.log(editIndex.value)
|
||||
editIndex.value = -1
|
||||
}
|
||||
const handleAdd = () => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{{ `#${index + 1}.` }}
|
||||
</div>
|
||||
<div class="item-middle item-editable">
|
||||
<a-popover :visible="editIndex === index" placement="top" @visible-change="change" trigger="click">
|
||||
<j-popover :visible="editIndex === index" placement="top" @visible-change="change" trigger="click">
|
||||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置参数</div>
|
||||
|
@ -16,7 +16,7 @@
|
|||
<template #content>
|
||||
<div>
|
||||
<div class="ant-form-vertical">
|
||||
<a-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
|
||||
<j-form-item label="标识" :name="name.concat([index, 'id'])" :rules="[
|
||||
{ required: true, message: '请输入标识' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{
|
||||
|
@ -24,20 +24,19 @@
|
|||
message: 'ID只能由数字、字母、下划线、中划线组成',
|
||||
},
|
||||
]">
|
||||
<a-input v-model:value="_value[index].id" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
|
||||
<j-input v-model:value="_value[index].id" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="名称" :name="name.concat([index, 'name'])" :rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="_value[index].name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="指标值" :name="name.concat([index, 'value'])" :rules="[
|
||||
{ required: true, message: '请输入指标值' },
|
||||
{ validator: () => validateIndicator(_value[index]), message: '请输入指标值' }
|
||||
<j-input v-model:value="_value[index].name" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="指标值" :name="name.concat([index, 'value'])" :rules="[
|
||||
{ required: true, validator: () => validateIndicator(_value[index]), message: '请输入指标值' }
|
||||
]">
|
||||
<JIndicators v-model:value="_value[index]" :type="type" size="small" :enum="enum" />
|
||||
</a-form-item>
|
||||
</j-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -45,18 +44,18 @@
|
|||
{{ item.name || '配置参数' }}
|
||||
<AIcon type="EditOutlined" class="item-icon" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</j-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="handleAdd">
|
||||
<j-button type="dashed" block @click="handleAdd">
|
||||
<template #icon>
|
||||
<AIcon type="PlusOutlined" class="item-icon" />
|
||||
</template>
|
||||
添加指标
|
||||
</a-button>
|
||||
</j-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="MetricsParam">
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
<template>
|
||||
<a-form-item :name="name.concat(['script'])">
|
||||
<f-rule-editor v-model:value="value.script" :id="id"></f-rule-editor>
|
||||
</a-form-item>
|
||||
<j-form-item :name="name.concat(['script'])">
|
||||
<f-rule-editor v-model:value="value.script" :id="id" :virtualRule="value"></f-rule-editor>
|
||||
</j-form-item>
|
||||
<template v-if="showWindow">
|
||||
<a-form-item label="规则配置" :name="name.concat(['isVirtualRule'])">
|
||||
<a-switch v-model:checked="value.isVirtualRule" :checked-value="true" :un-checked-value="false"
|
||||
@change="changeWindow"></a-switch>
|
||||
</a-form-item>
|
||||
<j-form-item label="规则配置" :name="name.concat(['isVirtualRule'])">
|
||||
<j-switch v-model:checked="value.isVirtualRule" :checked-value="true" :un-checked-value="false"
|
||||
@change="changeWindow"></j-switch>
|
||||
</j-form-item>
|
||||
<template v-if="value.isVirtualRule">
|
||||
<a-form-item label="窗口" :name="name.concat(['windowType'])" :rules="[
|
||||
<j-form-item label="窗口" :name="name.concat(['windowType'])" :rules="[
|
||||
{ required: true, message: '请选择窗口' },
|
||||
]">
|
||||
<a-select v-model:value="value.windowType" :options="windowTypeEnum" size="small" allow-clear></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="聚合函数" :name="name.concat(['aggType'])" :rules="[
|
||||
<j-select v-model:value="value.windowType" :options="windowTypeEnum" size="small" allow-clear></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="聚合函数" :name="name.concat(['aggType'])" :rules="[
|
||||
{ required: true, message: '请选择聚合函数' },
|
||||
]">
|
||||
<a-select v-model:value="value.aggType" :options="aggTypeOptions" size="small" allow-clear></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="spanLabel" :name="name.concat(['window', 'span'])" :rules="[
|
||||
<j-select v-model:value="value.aggType" :options="aggTypeOptions" size="small" allow-clear></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item :label="spanLabel" :name="name.concat(['window', 'span'])" :rules="[
|
||||
{ required: true, message: '请输入窗口长度' },
|
||||
]">
|
||||
<a-input-number v-model:value="value.window.span" size="small" style="width: 100%;"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item :label="everyLabel" :name="name.concat(['window', 'every'])" :rules="[
|
||||
<j-input-number v-model:value="value.window.span" size="small" style="width: 100%;"></j-input-number>
|
||||
</j-form-item>
|
||||
<j-form-item :label="everyLabel" :name="name.concat(['window', 'every'])" :rules="[
|
||||
{ required: true, message: '请输入步长' },
|
||||
]">
|
||||
<a-input-number v-model:value="value.window.every" size="small" style="width: 100%;"></a-input-number>
|
||||
</a-form-item>
|
||||
<j-input-number v-model:value="value.window.every" size="small" style="width: 100%;"></j-input-number>
|
||||
</j-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="{
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY)
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
accept=".xlsx,.csv"
|
||||
:maxCount="1"
|
||||
|
@ -27,18 +27,22 @@
|
|||
<div style="margin-top: 20px" v-if="importLoading">
|
||||
<a-badge v-if="flag" status="processing" text="进行中" />
|
||||
<a-badge v-else status="success" text="已完成" />
|
||||
<span>总数量:{{count}}</span>
|
||||
<p style="color: red">{{errMessage}}</p>
|
||||
<span>总数量:{{ count }}</span>
|
||||
<p style="color: red">{{ errMessage }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FILE_UPLOAD } from '@/api/comm'
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { downloadFile, downloadFileByUrl } from '@/utils/utils';
|
||||
import { deviceImport, deviceTemplateDownload ,templateDownload} from '@/api/device/instance'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
import {
|
||||
deviceImport,
|
||||
deviceTemplateDownload,
|
||||
templateDownload,
|
||||
} from '@/api/device/instance';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
type Emits = {
|
||||
|
@ -50,11 +54,11 @@ const props = defineProps({
|
|||
// 组件双向绑定的值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
product: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
file: {
|
||||
type: Object,
|
||||
|
@ -62,67 +66,62 @@ const props = defineProps({
|
|||
return {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const importLoading = ref<boolean>(false)
|
||||
const flag = ref<boolean>(false)
|
||||
const count = ref<number>(0)
|
||||
const errMessage = ref<string>('')
|
||||
const importLoading = ref<boolean>(false);
|
||||
const flag = ref<boolean>(false);
|
||||
const count = ref<number>(0);
|
||||
const errMessage = ref<string>('');
|
||||
|
||||
const downFile =async (type: string) => {
|
||||
const downFile = async (type: string) => {
|
||||
// downloadFile(deviceTemplateDownload(props.product, type));
|
||||
const res:any =await templateDownload(props.product, type)
|
||||
if(res){
|
||||
const res: any = await templateDownload(props.product, type);
|
||||
if (res) {
|
||||
const blob = new Blob([res], { type: type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
console.log(url);
|
||||
downloadFileByUrl(
|
||||
url,
|
||||
`设备导入模版`,
|
||||
type,
|
||||
);
|
||||
const url = URL.createObjectURL(blob);
|
||||
downloadFileByUrl(url, `设备导入模版`, type);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const submitData = async (fileUrl: string) => {
|
||||
if (!!fileUrl) {
|
||||
count.value = 0
|
||||
errMessage.value = ''
|
||||
flag.value = true
|
||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||
importLoading.value = true
|
||||
let dt = 0;
|
||||
const source = new EventSourcePolyfill(deviceImport(props.product, fileUrl, autoDeploy));
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message || '失败'
|
||||
}
|
||||
};
|
||||
source.onerror = (e: { status: number; }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员'
|
||||
flag.value = false
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
count.value = 0;
|
||||
errMessage.value = '';
|
||||
flag.value = true;
|
||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||
importLoading.value = true;
|
||||
let dt = 0;
|
||||
const source = new EventSourcePolyfill(
|
||||
deviceImport(props.product, fileUrl, autoDeploy),
|
||||
);
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt;
|
||||
} else {
|
||||
errMessage.value = res.message || '失败';
|
||||
}
|
||||
};
|
||||
source.onerror = (e: { status: number }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
|
||||
flag.value = false;
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
} else {
|
||||
message.error('请先上传文件')
|
||||
message.error('请先上传文件');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadChange = async (info: Record<string, any>) => {
|
||||
if (info.file.status === 'done') {
|
||||
const resp: any = info.file.response || { result: '' };
|
||||
await submitData(resp?.result || '');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
|
@ -4,32 +4,32 @@
|
|||
<div class="live-player-content">
|
||||
<!-- 工具栏 -->
|
||||
<div class="player-screen-tool" v-if="showScreen">
|
||||
<a-radio-group
|
||||
<j-radio-group
|
||||
:value="screen"
|
||||
button-style="solid"
|
||||
@change="handleScreenChange"
|
||||
>
|
||||
<a-radio-button :value="1">单屏</a-radio-button>
|
||||
<a-radio-button :value="4">四分屏</a-radio-button>
|
||||
<a-radio-button :value="9">九分屏</a-radio-button>
|
||||
<a-radio-button :value="0">全屏</a-radio-button>
|
||||
</a-radio-group>
|
||||
<j-radio-button :value="1">单屏</j-radio-button>
|
||||
<j-radio-button :value="4">四分屏</j-radio-button>
|
||||
<j-radio-button :value="9">九分屏</j-radio-button>
|
||||
<j-radio-button :value="0">全屏</j-radio-button>
|
||||
</j-radio-group>
|
||||
<div class="screen-tool-save">
|
||||
<a-tooltip title="可保存分屏配置记录">
|
||||
<j-tooltip title="可保存分屏配置记录">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
<a-popover
|
||||
</j-tooltip>
|
||||
<j-popover
|
||||
v-model:visible="visible"
|
||||
trigger="click"
|
||||
title="分屏名称"
|
||||
>
|
||||
<template #content>
|
||||
<a-form
|
||||
<j-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item
|
||||
<j-form-item
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
|
@ -42,37 +42,37 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-textarea v-model:value="formData.name" />
|
||||
</a-form-item>
|
||||
<a-button
|
||||
<j-textarea v-model:value="formData.name" />
|
||||
</j-form-item>
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="saveHistory"
|
||||
:loading="loading"
|
||||
style="width: 100%; margin-top: 16px"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
</a-form>
|
||||
</j-button>
|
||||
</j-form>
|
||||
</template>
|
||||
<a-dropdown-button
|
||||
<j-dropdown-button
|
||||
type="primary"
|
||||
@click="visible = true"
|
||||
>
|
||||
保存
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-empty
|
||||
<j-menu>
|
||||
<j-empty
|
||||
v-if="!historyList.length"
|
||||
description="暂无数据"
|
||||
/>
|
||||
<a-menu-item
|
||||
<j-menu-item
|
||||
v-for="(item, index) in historyList"
|
||||
:key="`his${index}`"
|
||||
@click="handleHistory(item)"
|
||||
>
|
||||
<a-space>
|
||||
<j-space>
|
||||
<span>{{ item.name }}</span>
|
||||
<a-popconfirm
|
||||
<j-popconfirm
|
||||
title="确认删除?"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
|
@ -89,13 +89,13 @@
|
|||
e?.stopPropagation()
|
||||
"
|
||||
/>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</j-popconfirm>
|
||||
</j-space>
|
||||
</j-menu-item>
|
||||
</j-menu>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
</a-popover>
|
||||
</j-dropdown-button>
|
||||
</j-popover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 播放器 -->
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
:muted="'muted' in props ? props.muted !== false : true"
|
||||
:hide-big-play-button="true"
|
||||
:poster="props.poster || ''"
|
||||
:timeout="props.timeout || 20"
|
||||
:timeout="props.timeout || 30"
|
||||
:video-url="url || ''"
|
||||
@play="props.onPlay"
|
||||
@pause="props.onPause"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,20 +25,23 @@
|
|||
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>
|
||||
<form-outlined @click="modalVis = true" />
|
||||
<AIcon type="FormOutlined" @click="modalVis = true" />
|
||||
</template>
|
||||
</j-input>
|
||||
<GeoComponent
|
||||
|
@ -50,7 +62,7 @@
|
|||
:showUploadList="false"
|
||||
@change="handleFileChange"
|
||||
>
|
||||
<cloud-upload-outlined />
|
||||
<AIcon type="CloudUploadOutlined" />
|
||||
</j-upload>
|
||||
</template>
|
||||
</j-input>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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,11 +10,12 @@ 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'
|
||||
import PathSimplifier from './AMapComponent/PathSimplifier.vue'
|
||||
import ValueItem from './ValueItem/index.vue'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
|
@ -35,5 +36,6 @@ export default {
|
|||
.component('JEmpty', JEmpty)
|
||||
.component('AMapComponent', AMapComponent)
|
||||
.component('PathSimplifier', PathSimplifier)
|
||||
.component('ValueItem', ValueItem)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,3 +12,4 @@ declare module '*.js';
|
|||
declare module '*.ts';
|
||||
declare module 'js-cookie';
|
||||
declare module 'jetlinks-ui-components';
|
||||
declare module 'vue3-json-viewer';
|
|
@ -67,10 +67,10 @@ export default [
|
|||
path: '/form',
|
||||
component: () => import('@/views/demo/Form.vue')
|
||||
},
|
||||
{
|
||||
path: '/system/Api',
|
||||
component: () => import('@/views/system/Platforms/index.vue')
|
||||
},
|
||||
// {
|
||||
// path: '/system/Api',
|
||||
// component: () => import('@/views/system/Platforms/index.vue')
|
||||
// },
|
||||
// end: 测试用, 可删除
|
||||
|
||||
// 初始化
|
||||
|
|
|
@ -45,4 +45,5 @@ export const SystemConst = {
|
|||
GET_METADATA: 'get_metadata',
|
||||
REFRESH_DEVICE: 'refresh_device',
|
||||
VERSION_CODE: 'version_code',
|
||||
AMAP_KEY : 'amap_key',
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
在特定场景下,设备无法直接接入阿里云物联网平台时,您可先将设备接入物联网平台,再使用阿里云“云云对接SDK”,快速构建桥接服务,搭建物联网平台与阿里云物联网平台的双向数据通道。
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="getImage('/northbound/aliyun2.png')" />
|
||||
<j-image width="100%" :src="getImage('/northbound/aliyun2.png')" />
|
||||
</div>
|
||||
<h1>2.配置说明</h1>
|
||||
<div>
|
||||
|
@ -26,14 +26,14 @@
|
|||
</div>
|
||||
<div>获取路径:“阿里云物联网平台”--“服务地址”</div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="getImage('/northbound/aliyun3.png')" />
|
||||
<j-image width="100%" :src="getImage('/northbound/aliyun3.png')" />
|
||||
</div>
|
||||
<h2> 2、AccesskeyID/Secret</h2>
|
||||
<div>
|
||||
用于程序通知方式调用云服务费API的用户标识和秘钥获取路径:“阿里云管理控制台”--“用户头像”--“”--“AccessKey管理”--“查看”
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="getImage('/northbound/aliyun1.jpg')" />
|
||||
<j-image width="100%" :src="getImage('/northbound/aliyun1.jpg')" />
|
||||
</div>
|
||||
<h2> 3. 网桥产品</h2>
|
||||
<div>
|
||||
|
@ -44,7 +44,7 @@
|
|||
将阿里云物联网平台中的产品实例与物联网平台的产品实例进行关联。关联后需要进入该产品下的每一个设备的实例信息页,填入对应的阿里云物联网平台设备的DeviceName、DeviceSecret进行一对一绑定。
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image width="100%" :src="getImage('/northbound/aliyun4.png')" />
|
||||
<j-image width="100%" :src="getImage('/northbound/aliyun4.png')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="16">
|
||||
<j-card>
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="16">
|
||||
<TitleComponent data="基本信息" />
|
||||
<a-form
|
||||
<j-form
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:model="modelRef"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
|
@ -25,14 +25,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
placeholder="请输入名称"
|
||||
v-model:value="modelRef.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
:name="['accessConfig', 'regionId']"
|
||||
:rules="[
|
||||
{
|
||||
|
@ -44,63 +44,62 @@
|
|||
<template #label>
|
||||
<span>
|
||||
服务地址
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="阿里云内部给每台机器设置的唯一编号"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
<j-select
|
||||
placeholder="请选择服务地址"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.regionId
|
||||
"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@blur="productChange"
|
||||
>
|
||||
<a-select-option
|
||||
<j-select-option
|
||||
v-for="item in regionsList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</a-select-option
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
:name="['accessConfig', 'instanceId']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
实例ID
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="阿里云物联网平台中的实例ID,没有则不填"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
<j-input
|
||||
placeholder="请输入实例ID"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.instanceId
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
:name="['accessConfig', 'accessKeyId']"
|
||||
:rules="[
|
||||
{
|
||||
|
@ -116,27 +115,27 @@
|
|||
<template #label>
|
||||
<span>
|
||||
accessKey
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="用于程序通知方式调用云服务API的用户标识"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
<j-input
|
||||
placeholder="请输入accessKey"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.accessKeyId
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
:name="['accessConfig', 'accessSecret']"
|
||||
:rules="[
|
||||
{
|
||||
|
@ -152,27 +151,27 @@
|
|||
<template #label>
|
||||
<span>
|
||||
accessSecret
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="用于程序通知方式调用云服务费API的秘钥标识"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input
|
||||
<j-input
|
||||
placeholder="请输入accessSecret"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.accessSecret
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
name="bridgeProductKey"
|
||||
:rules="{
|
||||
required: true,
|
||||
|
@ -182,44 +181,43 @@
|
|||
<template #label>
|
||||
<span>
|
||||
网桥产品
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="物联网平台对应的阿里云产品"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
<j-select
|
||||
placeholder="请选择网桥产品"
|
||||
v-model:value="
|
||||
modelRef.bridgeProductKey
|
||||
"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
<j-select-option
|
||||
v-for="item in aliyunProductList"
|
||||
:key="item.productKey"
|
||||
:value="item.productKey"
|
||||
:label="item.productName"
|
||||
>{{
|
||||
item.productName
|
||||
}}</a-select-option
|
||||
}}</j-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<p>产品映射</p>
|
||||
<a-collapse
|
||||
<j-collapse
|
||||
v-if="modelRef.mappings.length"
|
||||
:activeKey="activeKey"
|
||||
@change="onCollChange"
|
||||
>
|
||||
<a-collapse-panel
|
||||
<j-collapse-panel
|
||||
v-for="(
|
||||
item, index
|
||||
) in modelRef.mappings"
|
||||
|
@ -239,9 +237,9 @@
|
|||
type="DeleteOutlined"
|
||||
@click="delItem(index)"
|
||||
/></template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="阿里云产品"
|
||||
:name="[
|
||||
'mappings',
|
||||
|
@ -254,19 +252,16 @@
|
|||
'请选择阿里云产品',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
<j-select
|
||||
placeholder="请选择阿里云产品"
|
||||
v-model:value="
|
||||
item.productKey
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
<j-select-option
|
||||
v-for="i in getAliyunProductList(
|
||||
item.productKey,
|
||||
item?.productKey || ''
|
||||
)"
|
||||
:key="i.productKey"
|
||||
:value="
|
||||
|
@ -277,13 +272,13 @@
|
|||
"
|
||||
>{{
|
||||
i.productName
|
||||
}}</a-select-option
|
||||
}}</j-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="平台产品"
|
||||
:name="[
|
||||
'mappings',
|
||||
|
@ -296,36 +291,36 @@
|
|||
'请选择平台产品',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
<j-select
|
||||
placeholder="请选择平台产品"
|
||||
v-model:value="
|
||||
item.productId
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
<j-select-option
|
||||
v-for="i in getPlatProduct(
|
||||
item.productId,
|
||||
item.productId || ''
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="item.id"
|
||||
:value="i?.id"
|
||||
:label="i.name"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
}}</j-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-button
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-collapse-panel>
|
||||
</j-collapse>
|
||||
<j-card v-else>
|
||||
<j-empty />
|
||||
</j-card>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-button
|
||||
type="dashed"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
@click="addItem"
|
||||
|
@ -334,10 +329,10 @@
|
|||
type="PlusOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>添加
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24" style="margin-top: 20px">
|
||||
<a-form-item
|
||||
</j-button>
|
||||
</j-col>
|
||||
<j-col :span="24" style="margin-top: 20px">
|
||||
<j-form-item
|
||||
label="说明"
|
||||
name="description"
|
||||
:rules="{
|
||||
|
@ -345,16 +340,16 @@
|
|||
message: '最多输入200个字符',
|
||||
}"
|
||||
>
|
||||
<a-textarea
|
||||
<j-textarea
|
||||
v-model:value="modelRef.description"
|
||||
placeholder="请输入说明"
|
||||
showCount
|
||||
:maxlength="200"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-form>
|
||||
<div v-if="type === 'edit'">
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
|
@ -365,12 +360,12 @@
|
|||
保存
|
||||
</PermissionButton>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
</j-col>
|
||||
<j-col :span="8">
|
||||
<Doc />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
@ -384,7 +379,7 @@ import {
|
|||
queryProductList,
|
||||
} from '@/api/northbound/alicloud';
|
||||
import _ from 'lodash';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
@ -430,10 +425,6 @@ const loading = ref<boolean>(false);
|
|||
const type = ref<'edit' | 'view'>('edit');
|
||||
const activeKey = ref<string[]>(['0']);
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const queryRegionsList = async () => {
|
||||
const resp = await getRegionsList();
|
||||
if (resp.status === 200) {
|
||||
|
@ -503,8 +494,9 @@ const saveBtn = () => {
|
|||
);
|
||||
data.bridgeProductName = product?.productName || '';
|
||||
loading.value = true;
|
||||
const resp = await savePatch({...toRaw(modelRef), ...data});
|
||||
loading.value = false;
|
||||
const resp = await savePatch({...toRaw(modelRef), ...data}).finally(() => {
|
||||
loading.value = false;
|
||||
})
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search
|
||||
<j-advanced-search
|
||||
:columns="columns"
|
||||
target="northbound-aliyun"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
<JProTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
|
@ -13,7 +13,7 @@
|
|||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<j-space>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="handleAdd"
|
||||
|
@ -22,7 +22,7 @@
|
|||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
新增
|
||||
</PermissionButton>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
|
@ -45,20 +45,20 @@
|
|||
>
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
网桥产品
|
||||
</div>
|
||||
<div>{{ slotProps?.bridgeProductName }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
<label>说明</label>
|
||||
</div>
|
||||
<div>{{ slotProps?.description }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
|
@ -81,13 +81,13 @@
|
|||
</CardBox>
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
<j-badge
|
||||
:text="slotProps.state?.text"
|
||||
:status="statusMap.get(slotProps.state?.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space>
|
||||
<j-space>
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
|
@ -104,23 +104,21 @@
|
|||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</JProTable>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { query, _undeploy, _deploy, _delete } from '@/api/northbound/alicloud';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import type { ActionsType } from '@/views/device/Instance/typings'
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const router = useRouter();
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
const current = ref<Record<string, any>>({});
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
|
@ -149,6 +147,9 @@ const columns = [
|
|||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from "vue-demi";
|
||||
import { PropType } from "vue";
|
||||
|
||||
|
||||
type Emits = {
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<j-form-item name="messageType" label="指令类型" :rules="{
|
||||
required: true,
|
||||
message: '请选择指令类型',
|
||||
}">
|
||||
<j-select placeholder="请选择指令类型" v-model:value="modelRef.messageType" show-search :filter-option="filterOption">
|
||||
}" class="other">
|
||||
<j-select placeholder="请选择指令类型" v-model:value="modelRef.messageType" show-search>
|
||||
<j-select-option value="READ_PROPERTY">读取属性</j-select-option>
|
||||
<j-select-option value="WRITE_PROPERTY">修改属性</j-select-option>
|
||||
<j-select-option value="INVOKE_FUNCTION">调用功能</j-select-option>
|
||||
|
@ -22,7 +22,7 @@
|
|||
required: true,
|
||||
message: '请选择属性',
|
||||
}">
|
||||
<j-select placeholder="请选择属性" v-model:value="modelRef.message.properties" show-search :filter-option="filterOption">
|
||||
<j-select placeholder="请选择属性" v-model:value="modelRef.message.properties" show-search @change="onPropertyChange">
|
||||
<j-select-option v-for="i in (metadata?.properties) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</j-select-option>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
|
@ -32,7 +32,27 @@
|
|||
required: true,
|
||||
message: '请输入值',
|
||||
}">
|
||||
<j-input />
|
||||
<ValueItem
|
||||
v-model:modelValue="modelRef.message.value"
|
||||
:itemType="property.type || property.valueType?.type || 'int'"
|
||||
:options="
|
||||
property.valueType?.type === 'enum'
|
||||
? (property?.dataType?.elements || []).map(
|
||||
(item) => {
|
||||
return {
|
||||
label: item?.text,
|
||||
value: item?.value,
|
||||
};
|
||||
},
|
||||
)
|
||||
: property.valueType?.type === 'boolean'
|
||||
? [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
]
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24" v-if="modelRef.messageType === 'INVOKE_FUNCTION'">
|
||||
|
@ -40,7 +60,7 @@
|
|||
required: true,
|
||||
message: '请选择功能',
|
||||
}">
|
||||
<j-select placeholder="请选择功能" v-model:value="modelRef.message.functionId" show-search :filter-option="filterOption" @change="funcChange">
|
||||
<j-select placeholder="请选择功能" v-model:value="modelRef.message.functionId" show-search @change="funcChange">
|
||||
<j-select-option v-for="i in (metadata?.functions) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</j-select-option>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
|
@ -62,10 +82,6 @@ import EditTable from './EditTable.vue'
|
|||
|
||||
const formRef = ref();
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
actionType: {
|
||||
type: String,
|
||||
|
@ -89,10 +105,12 @@ const props = defineProps({
|
|||
type Emits = {
|
||||
(e: 'update:modelValue', data: any): void;
|
||||
};
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const modelRef = computed({
|
||||
get: () => {
|
||||
onPropertyChange(props.modelValue?.message?.properties)
|
||||
return props.modelValue || {
|
||||
messageType: undefined,
|
||||
message: {
|
||||
|
@ -107,6 +125,8 @@ const modelRef = computed({
|
|||
}
|
||||
})
|
||||
|
||||
const property = ref<any>({})
|
||||
|
||||
const funcChange = (val: string) => {
|
||||
if(val){
|
||||
const arr = props.metadata?.functions.find((item: any) => item.id === val)?.inputs || []
|
||||
|
@ -122,6 +142,13 @@ const funcChange = (val: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const onPropertyChange = (val: string) => {
|
||||
if(val){
|
||||
const _item = props.metadata?.properties.find((item: any) => item.id === val)
|
||||
property.value = _item?.[0] || {}
|
||||
}
|
||||
}
|
||||
|
||||
const saveBtn = () => new Promise((resolve) => {
|
||||
formRef.value.validate()
|
||||
.then(() => {
|
||||
|
@ -140,3 +167,12 @@ const saveBtn = () => new Promise((resolve) => {
|
|||
defineExpose({ saveBtn })
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item){
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.other {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
</style>
|
|
@ -51,7 +51,6 @@
|
|||
placeholder="请选择产品"
|
||||
v-model:value="modelRef.id"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="productChange"
|
||||
>
|
||||
<j-select-option
|
||||
|
@ -89,7 +88,6 @@
|
|||
placeholder="请选择设备类型"
|
||||
v-model:value="modelRef.applianceType"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="typeChange"
|
||||
>
|
||||
<j-select-option
|
||||
|
@ -170,13 +168,10 @@
|
|||
item.action
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<j-select-option
|
||||
v-for="i in getTypesActions(
|
||||
item.action,
|
||||
item.action || ''
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="i.id"
|
||||
|
@ -218,9 +213,6 @@
|
|||
item.actionType
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<j-select-option
|
||||
value="command"
|
||||
|
@ -261,6 +253,9 @@
|
|||
</j-row>
|
||||
</j-collapse-panel>
|
||||
</j-collapse>
|
||||
<j-card v-else>
|
||||
<j-empty />
|
||||
</j-card>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-button
|
||||
|
@ -323,13 +318,10 @@
|
|||
item.source
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<j-select-option
|
||||
v-for="i in getDuerOSProperties(
|
||||
item.source,
|
||||
item.source || '',
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="i.id"
|
||||
|
@ -361,16 +353,13 @@
|
|||
"
|
||||
mode="tags"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<j-select-option
|
||||
v-for="i in getProductProperties(
|
||||
item.target,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="item.id"
|
||||
:value="i.id"
|
||||
>{{
|
||||
i.name
|
||||
}}</j-select-option
|
||||
|
@ -381,6 +370,9 @@
|
|||
</j-row>
|
||||
</j-collapse-panel>
|
||||
</j-collapse>
|
||||
<j-card v-else>
|
||||
<j-empty />
|
||||
</j-card>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-button
|
||||
|
@ -494,10 +486,6 @@ const onActionCollChange = (_key: string[]) => {
|
|||
actionActiveKey.value = _key;
|
||||
};
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const addItem = () => {
|
||||
actionActiveKey.value.push(String(modelRef.actionMappings.length));
|
||||
modelRef.actionMappings.push({
|
||||
|
@ -636,8 +624,9 @@ const saveBtn = async () => {
|
|||
.then(async (data: any) => {
|
||||
if (tasks.every((item) => item) && data) {
|
||||
loading.value = true;
|
||||
const resp = await savePatch(data);
|
||||
loading.value = false;
|
||||
const resp = await savePatch(data).finally(() => {
|
||||
loading.value = false;
|
||||
})
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
@ok="handleOk"
|
||||
width="770px"
|
||||
@cancel="emits('update:visible', false)"
|
||||
:confirmLoading="loading"
|
||||
>
|
||||
<j-form :model="form" layout="vertical" ref="formRef">
|
||||
<j-row :gutter="24">
|
||||
|
@ -12,7 +13,10 @@
|
|||
<j-form-item
|
||||
label="姓名"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: '姓名必填' }]"
|
||||
:rules="[
|
||||
{ required: true, message: '姓名必填' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="form.name"
|
||||
|
@ -56,7 +60,16 @@
|
|||
</j-row>
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<j-form-item label="手机号">
|
||||
<j-form-item
|
||||
label="手机号"
|
||||
name="telephone"
|
||||
:rules="[
|
||||
{
|
||||
pattern: /^1[3456789]\d{9}$/,
|
||||
message: '请输入正确手机号',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="form.telephone"
|
||||
placeholder="请输入手机号"
|
||||
|
@ -64,7 +77,11 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item label="邮箱">
|
||||
<j-form-item
|
||||
label="邮箱"
|
||||
name="email"
|
||||
:rules="[{ type: 'email',message:'邮箱不是一个有效的email' }]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="form.email"
|
||||
placeholder="请输入邮箱"
|
||||
|
@ -87,17 +104,19 @@ const props = defineProps<{
|
|||
visible: boolean;
|
||||
data: userInfoType;
|
||||
}>();
|
||||
const loading = ref(false)
|
||||
const form = ref(props.data);
|
||||
const formRef = ref<FormInstance>();
|
||||
const handleOk = () => {
|
||||
formRef.value?.validate().then(() => {
|
||||
loading.value = true
|
||||
updateMeInfo_api(form.value).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('保存成功');
|
||||
emits('ok');
|
||||
emits('update:visible', false);
|
||||
}
|
||||
});
|
||||
}).finally(()=>loading.value = false)
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
title="重置密码"
|
||||
@ok="handleOk"
|
||||
width="520px"
|
||||
:confirmLoading="loading"
|
||||
@cancel="emits('update:visible', false)"
|
||||
>
|
||||
<j-form :model="form" layout="vertical" ref="formRef">
|
||||
|
@ -11,7 +12,7 @@
|
|||
label="旧密码"
|
||||
name="oldPassword"
|
||||
:rules="[
|
||||
{ required: true },
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ validator: checkMothods.old, trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
|
@ -24,7 +25,7 @@
|
|||
label="密码"
|
||||
name="newPassword"
|
||||
:rules="[
|
||||
{ required: true },
|
||||
{ required: true,message:'请输入密码' },
|
||||
{ validator: checkMothods.new, trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
|
@ -37,7 +38,7 @@
|
|||
label="确认密码"
|
||||
name="confirmPassword"
|
||||
:rules="[
|
||||
{ required: true },
|
||||
{ required: true, message: '请输入确认密码' },
|
||||
{ validator: checkMothods.confirm, trigger: 'blur' },
|
||||
]"
|
||||
>
|
||||
|
@ -63,6 +64,7 @@ const emits = defineEmits(['ok', 'update:visible']);
|
|||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
}>();
|
||||
const loading = ref(false)
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = ref<formType>({
|
||||
oldPassword: '',
|
||||
|
@ -72,7 +74,7 @@ const form = ref<formType>({
|
|||
|
||||
const checkMothods = {
|
||||
old: async (_rule: Rule, value: string) => {
|
||||
if (!value) return Promise.reject('请输入密码');
|
||||
if (!value) return Promise.reject();
|
||||
try {
|
||||
const resp: any = await checkOldPassword_api(value);
|
||||
if (resp.status === 200 && !resp.result.passed)
|
||||
|
@ -83,7 +85,7 @@ const checkMothods = {
|
|||
}
|
||||
},
|
||||
new: async (_rule: Rule, value: string) => {
|
||||
if (!value) return Promise.reject('请输入密码');
|
||||
if (!value) return Promise.reject();
|
||||
else if (
|
||||
form.value.confirmPassword &&
|
||||
value !== form.value.confirmPassword
|
||||
|
@ -99,7 +101,7 @@ const checkMothods = {
|
|||
}
|
||||
},
|
||||
confirm: async (_rule: Rule, value: string) => {
|
||||
if (!value) return Promise.reject('请输入确认密码');
|
||||
if (!value) return Promise.reject();
|
||||
|
||||
try {
|
||||
const resp: any = await validateField_api('password', value);
|
||||
|
@ -114,6 +116,7 @@ const checkMothods = {
|
|||
|
||||
const handleOk = () => {
|
||||
formRef.value?.validate().then(() => {
|
||||
loading.value = true
|
||||
const params = {
|
||||
oldPassword: form.value.oldPassword,
|
||||
newPassword: form.value.newPassword,
|
||||
|
@ -124,7 +127,7 @@ const handleOk = () => {
|
|||
emits('ok');
|
||||
emits('update:visible', false);
|
||||
}
|
||||
});
|
||||
}).finally(()=>loading.value = false)
|
||||
});
|
||||
};
|
||||
console.clear();
|
||||
|
|
|
@ -8,10 +8,15 @@
|
|||
style="width: 350px; justify-content: center"
|
||||
>
|
||||
<img
|
||||
v-if="userInfo.avatar"
|
||||
:src="userInfo.avatar"
|
||||
style="width: 140px; border-radius: 70px"
|
||||
alt=""
|
||||
/>
|
||||
<div class="default-avatar" v-else>
|
||||
<AIcon type="UserOutlined" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
|
@ -29,6 +34,7 @@
|
|||
}"
|
||||
:action="`${BASE_API_PATH}/file/static`"
|
||||
@change="upload.changeBackUpload"
|
||||
:beforeUpload="upload.beforeUpload"
|
||||
>
|
||||
<j-button>
|
||||
<AIcon type="UploadOutlined" />
|
||||
|
@ -51,11 +57,17 @@
|
|||
</div>
|
||||
<div class="info-card">
|
||||
<p>注册时间</p>
|
||||
<p>{{ moment(userInfo.createTime).format('YYYY-MM-DD HH:mm:ss') }}</p>
|
||||
<p>
|
||||
{{
|
||||
moment(userInfo.createTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>电话</p>
|
||||
<p>{{ userInfo.telephone }}</p>
|
||||
<p>{{ userInfo.telephone || '-' }}</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p>姓名</p>
|
||||
|
@ -117,7 +129,7 @@
|
|||
type="link"
|
||||
@click="editPasswordVisible = true"
|
||||
>
|
||||
<AIcon type="EditOutlined" style="color: #1d39c4;" />
|
||||
<AIcon type="EditOutlined" style="color: #1d39c4" />
|
||||
</PermissionButton>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -205,7 +217,7 @@
|
|||
<EditInfoDialog
|
||||
v-if="editInfoVisible"
|
||||
v-model:visible="editInfoVisible"
|
||||
:data="{...userInfo}"
|
||||
:data="{ ...userInfo }"
|
||||
@ok="getUserInfo"
|
||||
/>
|
||||
<EditPasswordDialog
|
||||
|
@ -277,6 +289,15 @@ const upload = reactive({
|
|||
message.error('logo上传失败,请稍后再试');
|
||||
}
|
||||
},
|
||||
beforeUpload: ({ size, type }: File) => {
|
||||
const imageTypes = ['jpg', 'png', 'jfif', 'pjp', 'pjpeg', 'jpeg'];
|
||||
const typeBool =
|
||||
imageTypes.filter((typeStr) => type.includes(typeStr)).length > 0;
|
||||
const sizeBool = size < 4 * 1024 * 1024;
|
||||
|
||||
(typeBool && sizeBool) || message.error('请上传正确格式的图片');
|
||||
return typeBool && sizeBool;
|
||||
},
|
||||
});
|
||||
// 首页视图
|
||||
const isApiUser = ref<boolean>();
|
||||
|
@ -346,7 +367,7 @@ function getViews() {
|
|||
background-color: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
.card {
|
||||
margin: 24px;
|
||||
margin: 16px 0;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
@ -370,6 +391,18 @@ function getViews() {
|
|||
flex-wrap: wrap;
|
||||
.content-item {
|
||||
margin-right: 24px;
|
||||
|
||||
.default-avatar {
|
||||
background-color: #ccc;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 70px;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.info-card {
|
||||
width: 25%;
|
||||
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="notification-record-container">
|
||||
<Search :columns="columns" @search="query.search" />
|
||||
<j-advanced-search
|
||||
:columns="columns"
|
||||
@search="(params:any)=>queryParams = {...params}"
|
||||
/>
|
||||
|
||||
<j-pro-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:request="getList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:params="queryParams"
|
||||
:defaultParams="{
|
||||
'sorts[0].name': 'notifyTime',
|
||||
'sorts[0].order': 'desc',
|
||||
|
@ -52,8 +55,8 @@
|
|||
? '标为未读'
|
||||
: '标为已读',
|
||||
}"
|
||||
>1
|
||||
<AIcon type="ReadIconOutlined" />
|
||||
>
|
||||
<AIcon type="icon-a-PIZHU1" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
|
@ -158,15 +161,10 @@ const columns = [
|
|||
key: 'action',
|
||||
ellipsis: true,
|
||||
scopedSlots: true,
|
||||
width:'200px'
|
||||
width: '200px',
|
||||
},
|
||||
];
|
||||
const query = {
|
||||
params: ref({}),
|
||||
search: (params: object) => {
|
||||
query.params.value = { ...params };
|
||||
},
|
||||
};
|
||||
const queryParams = ref({});
|
||||
|
||||
const tableRef = ref();
|
||||
const table = {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
visible
|
||||
:title="props.data.id ? '编辑' : '新增'"
|
||||
width="865px"
|
||||
:confirmLoading="loading"
|
||||
@ok="confirm"
|
||||
@cancel="emits('update:visible', false)"
|
||||
>
|
||||
|
@ -92,6 +93,7 @@ const props = defineProps<{
|
|||
data: rowType;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const initForm = {
|
||||
subscribeName: '',
|
||||
topicConfig: {},
|
||||
|
@ -106,13 +108,16 @@ const form = ref({
|
|||
const confirm = () => {
|
||||
formRef.value &&
|
||||
formRef.value.validate().then(() => {
|
||||
save_api(form.value).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
emits('ok')
|
||||
emits('update:visible', false);
|
||||
}
|
||||
});
|
||||
loading.value = true;
|
||||
save_api(form.value)
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
emits('ok');
|
||||
emits('update:visible', false);
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="notification-subscription-container">
|
||||
<Search :columns="columns" @search="query.search" />
|
||||
<j-advanced-search
|
||||
:columns="columns"
|
||||
@search="(params:any)=>queryParams = {...params}"
|
||||
/>
|
||||
<j-pro-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:request="getNoticeList_api"
|
||||
model="TABLE"
|
||||
:params="query.params.value"
|
||||
:params="queryParams"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'notifyTime', order: 'desc' }],
|
||||
}"
|
||||
|
@ -105,7 +108,7 @@ import EditDialog from './components/EditDialog.vue';
|
|||
import {
|
||||
getNoticeList_api,
|
||||
changeStatus_api,
|
||||
remove_api
|
||||
remove_api,
|
||||
} from '@/api/account/notificationSubscription';
|
||||
import { rowType } from './typing';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
@ -147,19 +150,14 @@ const columns = [
|
|||
key: 'action',
|
||||
ellipsis: true,
|
||||
scopedSlots: true,
|
||||
width: '200px'
|
||||
width: '200px',
|
||||
},
|
||||
];
|
||||
const query = {
|
||||
params: ref({}),
|
||||
search: (params: object) => {
|
||||
query.params.value = {...params};
|
||||
},
|
||||
};
|
||||
const queryParams = ref({});
|
||||
const dialogVisible = ref<boolean>(false);
|
||||
const tableRef = ref();
|
||||
const table = {
|
||||
seletctRow: ref<rowType>(),
|
||||
seletctRow: ref<any>({}),
|
||||
edit: (row?: rowType) => {
|
||||
table.seletctRow = {
|
||||
...(row || ({} as any)),
|
||||
|
@ -176,12 +174,12 @@ const table = {
|
|||
});
|
||||
},
|
||||
delete: (row: rowType) => {
|
||||
remove_api(row.id as string).then(resp=>{
|
||||
if(resp.status === 200) {
|
||||
message.success('操作成功!')
|
||||
table.refresh()
|
||||
}else message.warning('操作失败!')
|
||||
})
|
||||
remove_api(row.id as string).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
table.refresh();
|
||||
} else message.warning('操作失败!');
|
||||
});
|
||||
},
|
||||
refresh: () => {
|
||||
tableRef.value && tableRef.value.reload();
|
||||
|
|
|
@ -1,19 +1,46 @@
|
|||
<template>
|
||||
<div style="width: 100%; height: 400px">
|
||||
<el-amap
|
||||
>
|
||||
</el-amap>
|
||||
<AmapComponent>
|
||||
<el-amap-label-marker
|
||||
v-for="i in point"
|
||||
:key="i"
|
||||
:position="i.geometry.coordinates"
|
||||
:text="{
|
||||
content: i.properties.deviceName,
|
||||
direction: 'right',
|
||||
style: {
|
||||
fontSize: 15,
|
||||
fillColor: '#fff',
|
||||
strokeColor: 'rgba(255,0,0,0.5)',
|
||||
strokeWidth: 2,
|
||||
padding: [3, 10],
|
||||
backgroundColor: 'yellow',
|
||||
borderColor: '#ccc',
|
||||
borderWidth: 3,
|
||||
},
|
||||
}"
|
||||
:icon="{
|
||||
image: 'https://a.amap.com/jsapi_demos/static/images/poi-marker.png',
|
||||
anchor: 'bottom-center',
|
||||
size: [25, 34],
|
||||
clipOrigin: [459, 92],
|
||||
clipSize: [50, 68],
|
||||
}"
|
||||
>123</el-amap-label-marker
|
||||
>
|
||||
</AmapComponent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||
import '@vuemap/vue-amap/dist/style.css';
|
||||
initAMapApiLoader({
|
||||
// key: '95fa72137f4263f8e64ae01f766ad09c',
|
||||
key: 'a0415acfc35af15f10221bfa5a6850b4',
|
||||
securityJsCode: 'cae6108ec3dd222f946d1a7237c78be0',
|
||||
});
|
||||
import AmapComponent from '@/components/AMapComponent/index.vue';
|
||||
import { getGo } from '@/api/device/dashboard';
|
||||
let point = ref();
|
||||
const getMapData = async () => {
|
||||
const res = await getGo({});
|
||||
point.value = res.result?.features;
|
||||
};
|
||||
getMapData();
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
|
@ -28,8 +28,7 @@
|
|||
:chartXData="barChartXData"
|
||||
:chartYData="barChartYData"
|
||||
></BarChart> -->
|
||||
<Charts :options="onlineOptions"></Charts>
|
||||
</TopCard
|
||||
<Charts :options="onlineOptions"></Charts> </TopCard
|
||||
></a-col>
|
||||
<a-col :span="6"
|
||||
><TopCard
|
||||
|
@ -54,7 +53,7 @@
|
|||
</template>
|
||||
</Guide>
|
||||
<div class="message-chart">
|
||||
<Charts :options="devMegOptions"></Charts>
|
||||
<Charts :options="devMegOptions"></Charts>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
|
@ -74,7 +73,7 @@
|
|||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TimeSelect from './components/TimeSelect.vue';
|
||||
import Charts from './components/Charts.vue'
|
||||
import Charts from './components/Charts.vue';
|
||||
import Guide from './components/Guide.vue';
|
||||
import {
|
||||
productCount,
|
||||
|
@ -86,7 +85,8 @@ import encodeQuery from '@/utils/encodeQuery';
|
|||
import { getImage } from '@/utils/comm';
|
||||
import type { Footer } from '@/views/device/DashBoard/typings';
|
||||
import TopCard from '@/views/device/DashBoard/components/TopCard.vue';
|
||||
import Amap from './components/Amap.vue'
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import Amap from './components/Amap.vue';
|
||||
let productTotal = ref(0);
|
||||
let productFooter = ref<Footer[]>([
|
||||
{
|
||||
|
@ -133,6 +133,7 @@ let messageMaxChartYData = ref<number>();
|
|||
let onlineOptions = ref<any>({});
|
||||
let TodayDevOptions = ref<any>({});
|
||||
let devMegOptions = ref<any>({});
|
||||
const menuStore = useMenuStore();
|
||||
const quickBtnList = [
|
||||
{ label: '昨日', value: 'yesterday' },
|
||||
{ label: '近一周', value: 'week' },
|
||||
|
@ -140,54 +141,60 @@ const quickBtnList = [
|
|||
{ label: '近一年', value: 'year' },
|
||||
];
|
||||
const getProductData = () => {
|
||||
productCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
productTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[0].value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[1].value = res.result;
|
||||
}
|
||||
});
|
||||
if (menuStore.hasMenu('device/Product')) {
|
||||
productCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
productTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '1',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[0].value = res.result;
|
||||
}
|
||||
});
|
||||
productCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
productFooter.value[1].value = res.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
getProductData();
|
||||
const getDeviceData = () => {
|
||||
deviceCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'online' } })).then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[0].value = res.result;
|
||||
deviceOnline.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'offline' } })).then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[1].value = res.result;
|
||||
}
|
||||
});
|
||||
if (menuStore.hasMenu('device/Instance')) {
|
||||
deviceCount().then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceTotal.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'online' } })).then((res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[0].value = res.result;
|
||||
deviceOnline.value = res.result;
|
||||
}
|
||||
});
|
||||
deviceCount(encodeQuery({ terms: { state: 'offline' } })).then(
|
||||
(res) => {
|
||||
if (res.status == 200) {
|
||||
deviceFooter.value[1].value = res.result;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
getDeviceData();
|
||||
const getOnline = () => {
|
||||
|
@ -213,163 +220,167 @@ const getOnline = () => {
|
|||
.reverse();
|
||||
const y = res.result.map((item: any) => item.data.value);
|
||||
const onlineYdata = y;
|
||||
onlineYdata.reverse()
|
||||
setOnlineChartOpition(x,onlineYdata);
|
||||
onlineYdata.reverse();
|
||||
setOnlineChartOpition(x, onlineYdata);
|
||||
deviceFooter.value[0].value = y?.[1];
|
||||
}
|
||||
});
|
||||
};
|
||||
const setOnlineChartOpition = (x:Array<any>,y:Array<number>):void=>{
|
||||
const setOnlineChartOpition = (x: Array<any>, y: Array<number>): void => {
|
||||
onlineOptions.value = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: x,
|
||||
show: false,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: false,
|
||||
},
|
||||
grid: {
|
||||
top: '5%',
|
||||
bottom: 0,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '在线数',
|
||||
data: y,
|
||||
type: 'bar',
|
||||
showBackground: true,
|
||||
itemStyle: {
|
||||
color: '#D3ADF7',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
const setTodayDevChartOption = (x:Array<any>,y:Array<number>):void =>{
|
||||
TodayDevOptions = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
show: false,
|
||||
data:x
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: false,
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
bottom: 0,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '消息量',
|
||||
data: y,
|
||||
type: 'line',
|
||||
smooth: true, // 是否平滑曲线
|
||||
symbolSize: 0, // 拐点大小
|
||||
color: '#F29B55',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#FBBB87', // 100% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#FFFFFF', // 0% 处的颜色
|
||||
},
|
||||
],
|
||||
global: false, // 缺省为 false
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
const setDevMesChartOption = (x:Array<any>,y:Array<number>,maxY:number):void =>{
|
||||
devMegOptions.value = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: x,
|
||||
type: 'category',
|
||||
data: x,
|
||||
show: false,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b0}<br />{a0}: {c0}',
|
||||
// formatter: '{b0}<br />{a0}: {c0}<br />{a1}: {c1}%'
|
||||
type: 'value',
|
||||
show: false,
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
bottom: '5%',
|
||||
left: maxY > 100000 ? '90px' : '50px',
|
||||
right: '50px',
|
||||
top: '5%',
|
||||
bottom: 0,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '消息量',
|
||||
data: y,
|
||||
type: 'bar',
|
||||
// type: 'line',
|
||||
// smooth: true,
|
||||
color: '#597EF7',
|
||||
barWidth: '30%',
|
||||
// areaStyle: {
|
||||
// color: {
|
||||
// type: 'linear',
|
||||
// x: 0,
|
||||
// y: 0,
|
||||
// x2: 0,
|
||||
// y2: 1,
|
||||
// colorStops: [
|
||||
// {
|
||||
// offset: 0,
|
||||
// color: '#685DEB', // 100% 处的颜色
|
||||
// },
|
||||
// {
|
||||
// offset: 1,
|
||||
// color: '#FFFFFF', // 0% 处的颜色
|
||||
// },
|
||||
// ],
|
||||
// global: false, // 缺省为 false
|
||||
// },
|
||||
// },
|
||||
},
|
||||
{
|
||||
name: '占比',
|
||||
data: y,
|
||||
// data: percentageY,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 0, // 拐点大小
|
||||
color: '#96ECE3',
|
||||
},
|
||||
{
|
||||
name: '在线数',
|
||||
data: y,
|
||||
type: 'bar',
|
||||
showBackground: true,
|
||||
itemStyle: {
|
||||
color: '#D3ADF7',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
const setTodayDevChartOption = (x: Array<any>, y: Array<number>): void => {
|
||||
TodayDevOptions = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
show: false,
|
||||
data: x,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: false,
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
bottom: 0,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '消息量',
|
||||
data: y,
|
||||
type: 'line',
|
||||
smooth: true, // 是否平滑曲线
|
||||
symbolSize: 0, // 拐点大小
|
||||
color: '#F29B55',
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: '#FBBB87', // 100% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: '#FFFFFF', // 0% 处的颜色
|
||||
},
|
||||
],
|
||||
global: false, // 缺省为 false
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
const setDevMesChartOption = (
|
||||
x: Array<any>,
|
||||
y: Array<number>,
|
||||
maxY: number,
|
||||
): void => {
|
||||
devMegOptions.value = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: x,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b0}<br />{a0}: {c0}',
|
||||
// formatter: '{b0}<br />{a0}: {c0}<br />{a1}: {c1}%'
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
bottom: '5%',
|
||||
left: maxY > 100000 ? '90px' : '50px',
|
||||
right: '50px',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '消息量',
|
||||
data: y,
|
||||
type: 'bar',
|
||||
// type: 'line',
|
||||
// smooth: true,
|
||||
color: '#597EF7',
|
||||
barWidth: '30%',
|
||||
// areaStyle: {
|
||||
// color: {
|
||||
// type: 'linear',
|
||||
// x: 0,
|
||||
// y: 0,
|
||||
// x2: 0,
|
||||
// y2: 1,
|
||||
// colorStops: [
|
||||
// {
|
||||
// offset: 0,
|
||||
// color: '#685DEB', // 100% 处的颜色
|
||||
// },
|
||||
// {
|
||||
// offset: 1,
|
||||
// color: '#FFFFFF', // 0% 处的颜色
|
||||
// },
|
||||
// ],
|
||||
// global: false, // 缺省为 false
|
||||
// },
|
||||
// },
|
||||
},
|
||||
{
|
||||
name: '占比',
|
||||
data: y,
|
||||
// data: percentageY,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 0, // 拐点大小
|
||||
color: '#96ECE3',
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
getOnline();
|
||||
//今日设备消息量
|
||||
const getDevice = () => {
|
||||
|
@ -427,7 +438,7 @@ const getDevice = () => {
|
|||
);
|
||||
const x = today.map((item: any) => item.data.timeString).reverse();
|
||||
const y = today.map((item: any) => item.data.value).reverse();
|
||||
setTodayDevChartOption(x,y);
|
||||
setTodayDevChartOption(x, y);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -468,7 +479,7 @@ const getEcharts = (data: any) => {
|
|||
to: data.end,
|
||||
},
|
||||
},
|
||||
]).then((res:any) => {
|
||||
]).then((res: any) => {
|
||||
if (res.status === 200) {
|
||||
const x = res.result
|
||||
.map((item: any) =>
|
||||
|
@ -478,14 +489,18 @@ const getEcharts = (data: any) => {
|
|||
)
|
||||
.reverse();
|
||||
const y = res.result.map((item: any) => item.data.value).reverse();
|
||||
const maxY = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]);
|
||||
setDevMesChartOption(x,y,maxY);
|
||||
const maxY = Math.max.apply(
|
||||
null,
|
||||
messageChartYData.value.length ? messageChartYData.value : [0],
|
||||
);
|
||||
setDevMesChartOption(x, y, maxY);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.message-card,.device-position{
|
||||
.message-card,
|
||||
.device-position {
|
||||
margin-top: 24px;
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
|
@ -494,7 +509,7 @@ const getEcharts = (data: any) => {
|
|||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
.amap-box{
|
||||
.amap-box {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 props = 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: props.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: props.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>
|
|
@ -0,0 +1,160 @@
|
|||
<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';
|
||||
import { getEdgeMap, removeEdgeMap } from '@/api/device/instance';
|
||||
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) {
|
||||
current.value.parentId = props.childData.id;
|
||||
form.name = props.childData?.name;
|
||||
form.productId = props.childData?.productId;
|
||||
if (props.childData.deriveMetadata) {
|
||||
const metadata = JSON.parse(
|
||||
props.childData?.deriveMetadata || {},
|
||||
)?.properties?.map((item: any) => ({
|
||||
metadataId: item.id,
|
||||
metadataName: `${item.name}(${item.id})`,
|
||||
metadataType: 'property',
|
||||
name: item.name,
|
||||
}));
|
||||
if (metadata && metadata.length !== 0) {
|
||||
getEdgeMap(current.value.id, {
|
||||
deviceId: props.childData.id,
|
||||
query: {},
|
||||
}).then((res) => {
|
||||
if (res.status === 200) {
|
||||
// console.log(res.result)
|
||||
//合并物模型
|
||||
const array = res.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,
|
||||
);
|
||||
//删除物模型
|
||||
const items = array.filter(
|
||||
(item: any) => item.metadataName,
|
||||
);
|
||||
current.value.metadata = items;
|
||||
const delList = array
|
||||
.filter((a: any) => !a.metadataName)
|
||||
.map((b: any) => b.id);
|
||||
//删除后解绑
|
||||
if (delList && delList.length !== 0) {
|
||||
removeEdgeMap(current.value.id, {
|
||||
deviceId: props.childData.id,
|
||||
idList: [...delList],
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
visible.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
const validate = async () => {
|
||||
return formRef.value.validateFields();
|
||||
};
|
||||
provide('validate', validate);
|
||||
const comeBack = () => {
|
||||
emit('closeChildSave');
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -1,91 +1,113 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="child-device"
|
||||
@search="handleSearch"
|
||||
class="child-device-search"
|
||||
<SaveChild
|
||||
v-if="childVisible"
|
||||
@close-child-save="closeChildSave"
|
||||
:childData="current"
|
||||
/>
|
||||
<JTable
|
||||
ref="childDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="{
|
||||
terms: [
|
||||
{
|
||||
column: 'parentId',
|
||||
value: detail?.id || '',
|
||||
termType: 'eq',
|
||||
},
|
||||
],
|
||||
}"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
: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>
|
||||
</template>
|
||||
<template #registryTime="slotProps">
|
||||
{{
|
||||
slotProps.registryTime
|
||||
? moment(slotProps.registryTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state.text"
|
||||
:status="statusMap.get(slotProps.state.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
<div v-else>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="child-device"
|
||||
@search="handleSearch"
|
||||
class="child-device-search"
|
||||
/>
|
||||
<JProTable
|
||||
ref="childDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="{
|
||||
terms: [
|
||||
{
|
||||
column: 'parentId',
|
||||
value: detail?.id || '',
|
||||
termType: 'eq',
|
||||
},
|
||||
],
|
||||
}"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
:model="'TABLE'"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-space>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
v-if="
|
||||
detail?.accessProvider ===
|
||||
'official-edge-gateway'
|
||||
"
|
||||
hasPermission="device/Instance:update"
|
||||
@click="
|
||||
current = {};
|
||||
childVisible = true;
|
||||
"
|
||||
>新增并绑定</PermissionButton
|
||||
>
|
||||
<a-button
|
||||
<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">
|
||||
{{
|
||||
slotProps.registryTime
|
||||
? moment(slotProps.registryTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state.text"
|
||||
:status="statusMap.get(slotProps.state.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space :size="16">
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
>
|
||||
<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-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<BindChildDevice v-if="visible" @change="closeBindDevice" />
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:' + i.key"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon :type="i.icon"
|
||||
/></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</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 { 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">
|
||||
|
|
|
@ -1693,7 +1693,7 @@ const Status = defineComponent({
|
|||
/>
|
||||
)}
|
||||
{
|
||||
bindParentVisible && (
|
||||
bindParentVisible.value && (
|
||||
<BindParentDevice
|
||||
data={device.value}
|
||||
onCancel={() => {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<div class="wrapper">
|
||||
<a-tabs v-model="activeKey" tab-position="left">
|
||||
<a-tab-pane
|
||||
<j-tabs v-model="activeKey" tab-position="left">
|
||||
<j-tab-pane
|
||||
v-for="func in newFunctions"
|
||||
:key="func.id"
|
||||
:tab="func.name"
|
||||
>
|
||||
<a-row :gutter="30">
|
||||
<a-col :span="15">
|
||||
<j-row :gutter="30">
|
||||
<j-col :span="15">
|
||||
<MonacoEditor
|
||||
:ref="`monacoEditor${func.id}`"
|
||||
v-model="func.json"
|
||||
|
@ -15,31 +15,31 @@
|
|||
style="height: 400px"
|
||||
/>
|
||||
<div class="editor-btn">
|
||||
<a-space>
|
||||
<a-button
|
||||
<j-space>
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="handleExecute(func)"
|
||||
>
|
||||
执行
|
||||
</a-button>
|
||||
<a-button
|
||||
</j-button>
|
||||
<j-button
|
||||
type="default"
|
||||
@click="handleClear(func)"
|
||||
>
|
||||
清空
|
||||
</a-button>
|
||||
</a-space>
|
||||
</j-button>
|
||||
</j-space>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="9">
|
||||
</j-col>
|
||||
<j-col :span="9">
|
||||
<h6>执行结果:</h6>
|
||||
<span class="execute-result">
|
||||
{{ func.executeResult }}
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-tab-pane>
|
||||
</j-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="tips">
|
||||
<a-space>
|
||||
<j-space>
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
<span>精简模式下参数只支持输入框的方式录入</span>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</div>
|
||||
<a-tabs v-model="activeKey" tab-position="left">
|
||||
<a-tab-pane
|
||||
<j-tabs v-model="activeKey" tab-position="left">
|
||||
<j-tab-pane
|
||||
v-for="func in newFunctions"
|
||||
:key="func.id"
|
||||
:tab="func.name"
|
||||
>
|
||||
<a-row :gutter="30">
|
||||
<a-col :span="15">
|
||||
<a-form :ref="`${func.id}Ref`" :model="func">
|
||||
<a-table
|
||||
<j-row :gutter="30">
|
||||
<j-col :span="15">
|
||||
<j-form :ref="`${func.id}Ref`" :model="func">
|
||||
<j-table
|
||||
:columns="columns"
|
||||
:data-source="func.table"
|
||||
:pagination="false"
|
||||
|
@ -26,7 +26,7 @@
|
|||
v-if="column.dataIndex === 'type'"
|
||||
>
|
||||
<span>{{ record.type }}</span>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
v-if="record.type === 'object'"
|
||||
>
|
||||
<template slot="title">
|
||||
|
@ -40,12 +40,12 @@
|
|||
cursor: 'help',
|
||||
}"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<template
|
||||
v-if="column.dataIndex === 'value'"
|
||||
>
|
||||
<a-form-item
|
||||
<j-form-item
|
||||
:name="['table', index, 'value']"
|
||||
:rules="{
|
||||
required: true,
|
||||
|
@ -82,37 +82,37 @@
|
|||
: undefined
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</j-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
</j-table>
|
||||
</j-form>
|
||||
<div class="editor-btn">
|
||||
<a-space>
|
||||
<a-button
|
||||
<j-space>
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="handleExecute(func)"
|
||||
>
|
||||
执行
|
||||
</a-button>
|
||||
<a-button
|
||||
</j-button>
|
||||
<j-button
|
||||
type="default"
|
||||
@click="handleClear(func)"
|
||||
>
|
||||
清空
|
||||
</a-button>
|
||||
</a-space>
|
||||
</j-button>
|
||||
</j-space>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="9">
|
||||
</j-col>
|
||||
<j-col :span="9">
|
||||
<h6>执行结果:</h6>
|
||||
<span class="execute-result">
|
||||
{{ func.executeResult }}
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-tab-pane>
|
||||
</j-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<a-empty
|
||||
<j-card>
|
||||
<j-empty
|
||||
v-if="!metadata || (metadata && !metadata.functions.length)"
|
||||
style="margin-top: 50px"
|
||||
>
|
||||
|
@ -9,15 +9,15 @@
|
|||
<!-- <a @click="emits('onJump', 'Metadata')">物模型属性功能</a> -->
|
||||
<a @click="onJump">物模型属性功能</a>
|
||||
</template>
|
||||
</a-empty>
|
||||
</j-empty>
|
||||
<template v-else>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="Simple" tab="精简模式" />
|
||||
<a-tab-pane key="Advance" tab="高级模式" />
|
||||
</a-tabs>
|
||||
<j-tabs v-model:activeKey="activeKey">
|
||||
<j-tab-pane key="Simple" tab="精简模式" />
|
||||
<j-tab-pane key="Advance" tab="高级模式" />
|
||||
</j-tabs>
|
||||
<component :is="tabs[activeKey]" />
|
||||
</template>
|
||||
</a-card>
|
||||
</j-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
instanceStore.current.deviceType?.text
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="固件版本">{{
|
||||
instanceStore.current.firmwareInfo?.version
|
||||
instanceStore.current?.firmwareInfo?.version
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="连接协议">{{
|
||||
instanceStore.current?.protocolName
|
||||
instanceStore.current?.transport
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="消息协议">{{
|
||||
instanceStore.current.transport
|
||||
instanceStore.current.protocolName
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="创建时间">{{
|
||||
instanceStore.current.createTime
|
||||
|
|
|
@ -109,7 +109,9 @@ const loading = ref<boolean>(false);
|
|||
const instanceStore = useInstanceStore();
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({
|
||||
const modelRef = reactive<{
|
||||
metrics: any[]
|
||||
}>({
|
||||
metrics: [],
|
||||
});
|
||||
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
:tab="i.tab"
|
||||
/>
|
||||
</j-tabs>
|
||||
<JEmpty v-else style="margin: 250px 0" />
|
||||
<JEmpty v-else style="margin: 180px 0" />
|
||||
</div>
|
||||
<div class="property-box-right">
|
||||
<Event v-if="type === 'event'" :data="data" />
|
||||
<Property v-else :data="properties" />
|
||||
<Property v-else-if="type === 'property'" :data="properties" />
|
||||
<JEmpty v-else style="margin: 220px 0" />
|
||||
</div>
|
||||
</div>
|
||||
</j-card>
|
||||
|
@ -97,6 +98,13 @@ const onSearch = () => {
|
|||
} else {
|
||||
tabList.value = _.cloneDeep(arr)
|
||||
}
|
||||
const dt = tabList.value?.[0]
|
||||
if (dt) {
|
||||
data.value = dt
|
||||
type.value = dt.type;
|
||||
} else {
|
||||
type.value = ''
|
||||
}
|
||||
};
|
||||
const tabChange = (key: string) => {
|
||||
const dt = tabList.value.find((i) => i.key === key);
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
<div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<AIcon type="ArrowLeftOutlined" @click="onBack" />
|
||||
<div style="margin-left: 20px">{{ instanceStore.current.name }}</div>
|
||||
<div style="margin-left: 20px">
|
||||
{{ instanceStore.current.name }}
|
||||
</div>
|
||||
<j-divider type="vertical" />
|
||||
<j-space>
|
||||
<j-badge
|
||||
|
@ -116,8 +118,8 @@ import Function from './Function/index.vue';
|
|||
import Modbus from './Modbus/index.vue';
|
||||
import OPCUA from './OPCUA/index.vue';
|
||||
import EdgeMap from './EdgeMap/index.vue';
|
||||
import Parsing from './Parsing/index.vue'
|
||||
import Log from './Log/index.vue'
|
||||
import Parsing from './Parsing/index.vue';
|
||||
import Log from './Log/index.vue';
|
||||
import { _deploy, _disconnect } from '@/api/device/instance';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
@ -149,17 +151,13 @@ const list = ref([
|
|||
key: 'Metadata',
|
||||
tab: '物模型',
|
||||
},
|
||||
{
|
||||
key: 'Log',
|
||||
tab: '日志管理',
|
||||
},
|
||||
{
|
||||
key: 'Function',
|
||||
tab: '设备功能',
|
||||
},
|
||||
{
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备',
|
||||
key: 'Log',
|
||||
tab: '日志管理',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -174,7 +172,7 @@ const tabs = {
|
|||
OPCUA,
|
||||
EdgeMap,
|
||||
Parsing,
|
||||
Log
|
||||
Log,
|
||||
};
|
||||
|
||||
const getStatus = (id: string) => {
|
||||
|
@ -255,6 +253,17 @@ watchEffect(() => {
|
|||
tab: '设备诊断',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.features?.find(
|
||||
(item: any) => item.id === 'transparentCodec',
|
||||
) &&
|
||||
!keys.includes('Parsing')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Parsing',
|
||||
tab: '数据解析',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.protocol === 'modbus-tcp' &&
|
||||
!keys.includes('Modbus')
|
||||
|
@ -273,6 +282,13 @@ watchEffect(() => {
|
|||
tab: 'OPC UA',
|
||||
});
|
||||
}
|
||||
if (instanceStore.current.deviceType?.value === 'gateway') {
|
||||
// 产品类型为网关的情况下才显示此模块
|
||||
list.value.push({
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.accessProvider === 'edge-child-device' &&
|
||||
instanceStore.current.parentId &&
|
||||
|
@ -283,15 +299,6 @@ watchEffect(() => {
|
|||
tab: '边缘端映射',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.features?.find((item: any) => item.id === 'transparentCodec') &&
|
||||
!keys.includes('Parsing')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Parsing',
|
||||
tab: '数据解析',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
import { downloadFile } from '@/utils/utils';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import { BASE_API_PATH } from '@/utils/variable';
|
||||
import { deviceExport } from '@/api/device/instance';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
|
|
@ -1,92 +1,106 @@
|
|||
<template>
|
||||
<j-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleCancel" @cancel="handleCancel">
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="800px"
|
||||
:visible="true"
|
||||
title="当前进度"
|
||||
@ok="handleCancel"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div>
|
||||
<j-badge v-if="flag" status="processing" text="进行中" />
|
||||
<j-badge v-else status="success" text="已完成" />
|
||||
</div>
|
||||
<p>总数量:{{count}}</p>
|
||||
<a style="color: red">{{errMessage}}</a>
|
||||
<p>总数量:{{ count }}</p>
|
||||
<a style="color: red">{{ errMessage }}</a>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
api: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const eventSource = ref<Record<string, any>>({})
|
||||
const count = ref<number>(0)
|
||||
const flag = ref<boolean>(false)
|
||||
const errMessage = ref<string>('')
|
||||
const isSource = ref<boolean>(false)
|
||||
const id = ref<string>('')
|
||||
const source = ref<Record<string, any>>({})
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
// const eventSource = ref<Record<string, any>>({})
|
||||
const count = ref<number>(0);
|
||||
const flag = ref<boolean>(false);
|
||||
const errMessage = ref<string>('');
|
||||
const isSource = ref<boolean>(false);
|
||||
const id = ref<string>('');
|
||||
const source = ref<Record<string, any>>({});
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
emit('close');
|
||||
emit('save');
|
||||
};
|
||||
|
||||
// const handleOk = () => {
|
||||
// emit('close');
|
||||
// emit('save');
|
||||
// };
|
||||
|
||||
const getData = (api: string) => {
|
||||
let dt = 0
|
||||
const _source = new EventSourcePolyfill(api)
|
||||
source.value = _source
|
||||
let dt = 0;
|
||||
const _source = new EventSourcePolyfill(api);
|
||||
source.value = _source;
|
||||
_source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
switch (props.type) {
|
||||
case 'active':
|
||||
if (res.success) {
|
||||
dt += res.total;
|
||||
count.value = dt
|
||||
} else {
|
||||
if (res.source) {
|
||||
const msg = `${res.source.name}: ${res.message}`;
|
||||
errMessage.value = msg
|
||||
id.value = res.source.id
|
||||
isSource.value = true
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sync':
|
||||
dt += res;
|
||||
count.value = dt
|
||||
break;
|
||||
case 'import':
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const res = JSON.parse(e.data);
|
||||
switch (props.type) {
|
||||
case 'active':
|
||||
if (res.success) {
|
||||
dt += res.total;
|
||||
count.value = dt;
|
||||
} else {
|
||||
if (res.source) {
|
||||
const msg = `${res.source.name}: ${res.message}`;
|
||||
errMessage.value = msg;
|
||||
id.value = res.source.id;
|
||||
isSource.value = true;
|
||||
} else {
|
||||
errMessage.value = res.message;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sync':
|
||||
dt += res;
|
||||
count.value = dt;
|
||||
break;
|
||||
case 'import':
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt;
|
||||
} else {
|
||||
errMessage.value = res.message;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
_source.onerror = () => {
|
||||
flag.value = false
|
||||
_source.close();
|
||||
flag.value = false;
|
||||
_source.close();
|
||||
};
|
||||
_source.onopen = () => {};
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.api,
|
||||
watch(
|
||||
() => props.api,
|
||||
(newValue) => {
|
||||
if(newValue) {
|
||||
getData(newValue)
|
||||
if (newValue) {
|
||||
getData(newValue);
|
||||
}
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
)
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
</script>
|
|
@ -9,12 +9,7 @@
|
|||
:confirmLoading="loading"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<j-form
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:rules="rules"
|
||||
:model="modelRef"
|
||||
>
|
||||
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
|
||||
<j-row type="flex">
|
||||
<j-col flex="180px">
|
||||
<j-form-item name="photoUrl">
|
||||
|
@ -22,14 +17,33 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col flex="auto">
|
||||
<j-form-item name="id">
|
||||
<j-form-item
|
||||
name="id"
|
||||
:rules="[
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
ID
|
||||
<j-tooltip title="若不填写,系统将自动生成唯一ID">
|
||||
<j-tooltip
|
||||
title="若不填写,系统将自动生成唯一ID"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -39,7 +53,20 @@
|
|||
:disabled="!!data?.id"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item label="名称" name="name">
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="modelRef.name"
|
||||
placeholder="请输入名称"
|
||||
|
@ -47,13 +74,23 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item name="productId">
|
||||
<j-form-item
|
||||
name="productId"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>所属产品
|
||||
<span
|
||||
>所属产品
|
||||
<j-tooltip title="只能选择“正常”状态的产品">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -68,10 +105,20 @@
|
|||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>{{item.name}}</j-select-option>
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="说明" name="describe">
|
||||
<j-form-item
|
||||
label="说明"
|
||||
name="describe"
|
||||
:rules="[
|
||||
{
|
||||
max: 200,
|
||||
message: '最多输入200个字符'
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-textarea
|
||||
v-model:value="modelRef.describe"
|
||||
placeholder="请输入说明"
|
||||
|
@ -123,45 +170,6 @@ const vailId = async (_: Record<string, any>, value: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
],
|
||||
photoUrl: [
|
||||
{
|
||||
required: true,
|
||||
message: '请上传图标',
|
||||
},
|
||||
],
|
||||
productId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
],
|
||||
id: [
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
pattern: /^[j-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(newValue) => {
|
||||
|
@ -199,13 +207,13 @@ const handleSave = () => {
|
|||
.validate()
|
||||
.then(async (_data: any) => {
|
||||
loading.value = true;
|
||||
const obj = {...toRaw(modelRef), ..._data}
|
||||
if(!obj.id){
|
||||
delete obj.id
|
||||
const obj = { ..._data };
|
||||
if (!obj.id) {
|
||||
delete obj.id;
|
||||
}
|
||||
const resp = await update(obj).finally(() => {
|
||||
loading.value = false;
|
||||
})
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
emit('save');
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
|
@ -253,6 +252,7 @@
|
|||
@close="operationVisible = false"
|
||||
:api="api"
|
||||
:type="type"
|
||||
@save="onRefresh"
|
||||
/>
|
||||
<Save
|
||||
v-if="visible"
|
||||
|
@ -315,6 +315,7 @@ const columns = [
|
|||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
defaultTermType: 'eq'
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -323,6 +324,7 @@ const columns = [
|
|||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -390,6 +392,7 @@ const columns = [
|
|||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
rename: 'productId$product-info',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
getProviders().then((resp: any) => {
|
||||
|
@ -643,10 +646,6 @@ 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);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-form-item label="标识" name="id" :rules="[
|
||||
<j-form-item label="标识" name="id" :rules="[
|
||||
{ required: true, message: '请输入标识' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
{
|
||||
|
@ -7,14 +7,14 @@
|
|||
message: 'ID只能由数字、字母、下划线、中划线组成',
|
||||
},
|
||||
]">
|
||||
<a-input v-model:value="value.id" size="small" @change="asyncOtherConfig" :disabled="metadataStore.model.action === 'edit'"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="名称" name="name" :rules="[
|
||||
<j-input v-model:value="value.id" size="small" @change="asyncOtherConfig" :disabled="metadataStore.model.action === 'edit'"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="名称" name="name" :rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<a-input v-model:value="value.name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<j-input v-model:value="value.name" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<template v-if="modelType === 'properties'">
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"
|
||||
@change-type="changeValueType"></value-type-form>
|
||||
|
@ -22,42 +22,42 @@
|
|||
:valueType="value.valueType"></expands-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'functions'">
|
||||
<a-form-item label="是否异步" name="async" :rules="[
|
||||
<j-form-item label="是否异步" name="async" :rules="[
|
||||
{ required: true, message: '请选择是否异步' },
|
||||
]">
|
||||
<a-radio-group v-model:value="value.async">
|
||||
<a-radio :value="true">是</a-radio>
|
||||
<a-radio :value="false">否</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="输入参数" name="inputs" :rules="[
|
||||
{ required: true, message: '请输入输入参数' },
|
||||
<j-radio-group v-model:value="value.async">
|
||||
<j-radio :value="true">是</j-radio>
|
||||
<j-radio :value="false">否</j-radio>
|
||||
</j-radio-group>
|
||||
</j-form-item>
|
||||
<j-form-item label="输入参数" name="inputs" :rules="[
|
||||
{ required: true, validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, '输入参数') },
|
||||
]">
|
||||
<JsonParam v-model:value="value.inputs" :name="['inputs']"></JsonParam>
|
||||
</a-form-item>
|
||||
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数"></value-type-form>
|
||||
</j-form-item>
|
||||
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数" :required="false"></value-type-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'events'">
|
||||
<a-form-item label="级别" :name="['expands', 'level']" :rules="[
|
||||
<j-form-item label="级别" :name="['expands', 'level']" :rules="[
|
||||
{ required: true, message: '请选择级别' },
|
||||
]">
|
||||
<a-select v-model:value="value.expands.level" :options="EventLevel" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数"></value-type-form>
|
||||
<j-select v-model:value="value.expands.level" :options="EventLevel" size="small"></j-select>
|
||||
</j-form-item>
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数" only-object></value-type-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'tags'">
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"></value-type-form>
|
||||
<a-form-item label="读写类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
<j-form-item label="标签类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择标签类型' },
|
||||
]">
|
||||
<a-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<j-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select>
|
||||
</j-form-item>
|
||||
</template>
|
||||
<a-form-item label="说明" name="description" :rules="[
|
||||
<j-form-item label="说明" name="description" :rules="[
|
||||
{ max: 200, message: '最多可输入200个字符' },
|
||||
]">
|
||||
<a-textarea v-model:value="value.description" size="small"></a-textarea>
|
||||
</a-form-item>
|
||||
<j-textarea v-model:value="value.description" size="small"></j-textarea>
|
||||
</j-form-item>
|
||||
</template>
|
||||
<script setup lang="ts" name="BaseForm">
|
||||
import { PropType } from 'vue';
|
||||
|
@ -68,6 +68,8 @@ import { getMetadataConfig } from '@/api/device/product'
|
|||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
import { EventLevel, ExpandsTypeList } from '@/views/device/data';
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
import { validateJson } from './validator';
|
||||
import { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
|
|
|
@ -1,26 +1,30 @@
|
|||
<template>
|
||||
<a-form-item label="来源" :name="name.concat(['source'])" v-if="type === 'product'" :rules="[
|
||||
<j-form-item label="来源" :name="name.concat(['source'])" v-if="type === 'product'" :rules="[
|
||||
{ required: true, message: '请选择来源' },
|
||||
]">
|
||||
<a-select v-model:value="_value.source" :options="PropertySource" size="small"
|
||||
:disabled="metadataStore.model.action === 'edit'"></a-select>
|
||||
</a-form-item>
|
||||
<j-select v-model:value="_value.source" :options="PropertySource" size="small"
|
||||
:disabled="metadataStore.model.action === 'edit'" @change="changeSource"></j-select>
|
||||
</j-form-item>
|
||||
<virtual-rule-param v-if="_value.source === 'rule'" v-model:value="_value.virtualRule"
|
||||
:name="name.concat(['virtualRule'])" :id="id" :showWindow="_value.source === 'rule'"></virtual-rule-param>
|
||||
<a-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
|
||||
<j-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
]">
|
||||
<a-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="其他配置" v-if="config.length > 0">
|
||||
<a-form-item v-for="(item, index) in config" :key="index">
|
||||
<j-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small" :disabled="['manual', 'rule'].includes(_value.source)"></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="其他配置" v-if="config.length > 0">
|
||||
<j-form-item v-for="(item, index) in config" :key="index">
|
||||
<config-param v-model:value="_value" :config="item" :name="name"></config-param>
|
||||
</a-form-item>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="type === 'product' && ['int', 'float', 'double', 'long', 'date', 'string', 'boolean'].includes(valueType.type)"
|
||||
label="指标配置" :name="name.concat(['metrics'])">
|
||||
<metrics-param v-model:value="_value.metrics" :type="valueType.type" :enum="valueType" :name="name.concat(['metrics'])"></metrics-param>
|
||||
</a-form-item>
|
||||
</j-form-item>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="type === 'product' && ['int', 'float', 'double', 'long', 'date', 'string', 'boolean'].includes(valueType.type)"
|
||||
label="指标配置" :name="name.concat(['metrics'])" :rules="[
|
||||
{ validator: () => validateMetrics(_value.metrics), message: '请输入指标配置' }
|
||||
]">
|
||||
<metrics-param v-model:value="_value.metrics" :type="valueType.type" :enum="valueType"
|
||||
:name="name.concat(['metrics'])"></metrics-param>
|
||||
</j-form-item>
|
||||
</template>
|
||||
<script setup lang="ts" name="ExpandsForm">
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
|
@ -82,9 +86,29 @@ const metadataStore = useMetadataStore()
|
|||
|
||||
onMounted(() => {
|
||||
if (props.type === 'product' || !props.value?.source) {
|
||||
emit('update:value', { ...props.value, source: 'device' })
|
||||
emit('update:value', { source: 'device', ...props.value })
|
||||
}
|
||||
})
|
||||
|
||||
const validateMetrics = (value: Record<any, any>[]) => {
|
||||
const flag = value.every((item) => {
|
||||
return item.id && item.name && item.value;
|
||||
});
|
||||
if (!flag) {
|
||||
return Promise.reject(new Error('请输入指标配置'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const changeSource = (val: string) => {
|
||||
if (val === 'manual') {
|
||||
_value.value.type = ['write']
|
||||
} else if (val === 'rule') {
|
||||
_value.value.type = ['report']
|
||||
} else {
|
||||
_value.value.type = []
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
|
@ -1,48 +1,54 @@
|
|||
<template>
|
||||
<a-form-item :label="title" :name="name.concat(['type'])" :rules="[
|
||||
metadataStore.model.type !== 'functions' ? { required: true, message: `请选择${title}` } : {},
|
||||
<j-form-item :label="title" :name="name.concat(['type'])" :rules="[
|
||||
required ? { required: true, message: `请选择${title}` } : {},
|
||||
]">
|
||||
<a-select v-model:value="_value.type" :options="metadataStore.model.type === 'events' ? eventDataTypeList : _dataTypeList" size="small" @change="changeType"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)">
|
||||
<j-select v-model:value="_value.type"
|
||||
:options="onlyObject ? eventDataTypeList : _dataTypeList" size="small"
|
||||
@change="changeType"></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)">
|
||||
<InputSelect v-model:value="_value.unit" :options="unit.unitOptions" size="small"></InputSelect>
|
||||
</a-form-item>
|
||||
<a-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)">
|
||||
<a-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0" :default-value="2"
|
||||
style="width: 100%"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
|
||||
</j-form-item>
|
||||
<j-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)">
|
||||
<j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0"
|
||||
style="width: 100%"></j-input-number>
|
||||
</j-form-item>
|
||||
<j-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
|
||||
<BooleanParam :name="name" v-model:value="_value"></BooleanParam>
|
||||
</a-form-item>
|
||||
<a-form-item label="枚举项" :name="name.concat(['elements'])" v-if="['enum'].includes(_value.type)" :rules="[
|
||||
{ required: true, message: '请配置枚举项' }
|
||||
</j-form-item>
|
||||
<j-form-item label="枚举项" :name="name.concat(['elements'])" v-if="['enum'].includes(_value.type)" :rules="[
|
||||
{ required: true, validator: validateEnum }
|
||||
]">
|
||||
<EnumParam v-model:value="_value.elements" :name="name.concat(['elements'])"></EnumParam>
|
||||
</a-form-item>
|
||||
<a-form-item :name="name.concat(['expands', 'maxLength'])" v-if="['string', 'password'].includes(_value.type)">
|
||||
</j-form-item>
|
||||
<j-form-item :name="name.concat(['expands', 'maxLength'])" v-if="['string', 'password'].includes(_value.type)">
|
||||
<template #label>
|
||||
<a-space>
|
||||
<j-space>
|
||||
最大长度
|
||||
<a-tooltip title="字节">
|
||||
<j-tooltip title="字节">
|
||||
<question-circle-outlined style="color: rgb(136, 136, 136); font-size: 12px;" />
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</j-tooltip>
|
||||
</j-space>
|
||||
</template>
|
||||
<a-input-number v-model:value="_value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0"
|
||||
style="width: 100%;"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(_value.type)">
|
||||
<j-input-number v-model:value="_value.expands.maxLength" size="small" :max="2147483647" :min="1" :precision="0"
|
||||
style="width: 100%;"></j-input-number>
|
||||
</j-form-item>
|
||||
<j-form-item label="元素配置" :name="name.concat(['elementType'])" v-if="['array'].includes(_value.type)" :rules="[
|
||||
{ validator: validateArray }
|
||||
]">
|
||||
<ArrayParam v-model:value="_value.elementType" :name="name.concat(['elementType'])"></ArrayParam>
|
||||
</a-form-item>
|
||||
<a-form-item label="JSON对象" :name="name.concat(['properties'])" v-if="['object'].includes(_value.type)" :rules="[]">
|
||||
</j-form-item>
|
||||
<j-form-item label="JSON对象" :name="name.concat(['properties'])" v-if="['object'].includes(_value.type)" :rules="[
|
||||
{ validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, 'JSON对象') }
|
||||
]">
|
||||
<JsonParam v-model:value="_value.properties" :name="name.concat(['properties'])"></JsonParam>
|
||||
</a-form-item>
|
||||
<a-form-item label="文件类型" :name="name.concat(['fileType'])" v-if="['file'].includes(_value.type)" initialValue="url"
|
||||
</j-form-item>
|
||||
<j-form-item label="文件类型" :name="name.concat(['fileType'])" v-if="['file'].includes(_value.type)" initialValue="url"
|
||||
:rules="[
|
||||
{ required: true, message: '请选择文件类型' },
|
||||
]">
|
||||
<a-select v-model:value="_value.fileType" :options="FileTypeList" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<j-select v-model:value="_value.fileType" :options="FileTypeList" size="small"></j-select>
|
||||
</j-form-item>
|
||||
</template>
|
||||
<script lang="ts" setup mame="BaseForm">
|
||||
import { DataTypeList, FileTypeList } from '@/views/device/data';
|
||||
|
@ -56,6 +62,8 @@ import EnumParam from '@/components/Metadata/EnumParam/index.vue'
|
|||
import ArrayParam from '@/components/Metadata/ArrayParam/index.vue'
|
||||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
import { validateEnum, validateArray, validateJson } from './validator'
|
||||
import { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
|
@ -73,8 +81,16 @@ const props = defineProps({
|
|||
required: true
|
||||
},
|
||||
title: {
|
||||
String,
|
||||
type: String,
|
||||
default: '数据类型'
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
onlyObject: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -84,15 +100,8 @@ interface Emits {
|
|||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// emit('update:value', { extends: {}, ...props.value })
|
||||
|
||||
const metadataStore = useMetadataStore()
|
||||
// const _value = computed({
|
||||
// get: () => props.value,
|
||||
// set: val => {
|
||||
// emit('update:value', val)
|
||||
// }
|
||||
// })
|
||||
|
||||
const _value = ref<ValueType>({})
|
||||
watchEffect(() => {
|
||||
_value.value = props.value || {
|
||||
|
@ -107,7 +116,7 @@ watch(_value,
|
|||
{ deep: true, immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
if (metadataStore.model.type === 'events') {
|
||||
if (props.onlyObject) {
|
||||
_value.value = {
|
||||
type: 'object',
|
||||
expands: {}
|
||||
|
@ -140,8 +149,30 @@ const eventDataTypeList = [
|
|||
]
|
||||
|
||||
const changeType = (val: SelectValue) => {
|
||||
if (['float', 'double'].includes(_value.value.type) && _value.value.scale === undefined) {
|
||||
_value.value.scale = 2
|
||||
}
|
||||
emit('changeType', val as string)
|
||||
}
|
||||
|
||||
// const rules = ref({
|
||||
// type: [
|
||||
// metadataStore.model.type !== 'functions' ? { required: true, message: `请选择${props.title}` } : {},
|
||||
// ],
|
||||
// elements: [
|
||||
// { required: true, validator: validateEnum, message: '请配置枚举项' }
|
||||
// ],
|
||||
// elementType: [
|
||||
// { validator: validateArray, message: '请输入元素配置' }
|
||||
// ],
|
||||
// properties: [
|
||||
// { validator: validateJson, message: '请输入配置参数' }
|
||||
// ],
|
||||
// fileType: [
|
||||
// { required: true, message: '请选择文件类型' },
|
||||
// ]
|
||||
// })
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item-label) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<a-drawer :mask-closable="false" width="25vw" visible :title="`${title}-${typeMapping[metadataStore.model.type]}`"
|
||||
<j-drawer :mask-closable="false" width="25vw" visible :title="`${title}-${typeMapping[metadataStore.model.type]}`"
|
||||
@close="close" destroy-on-close :z-index="1000" placement="right">
|
||||
<template #extra>
|
||||
<a-button :loading="save.loading" type="primary" @click="save.saveMetadata">保存</a-button>
|
||||
<j-button :loading="save.loading" type="primary" @click="save.saveMetadata">保存</j-button>
|
||||
</template>
|
||||
<a-form ref="formRef" :model="form.model" layout="vertical">
|
||||
<j-form ref="formRef" :model="form.model" layout="vertical">
|
||||
<BaseForm :model-type="metadataStore.model.type" :type="type" v-model:value="form.model"></BaseForm>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</j-form>
|
||||
</j-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup name="Edit">
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
|
@ -22,6 +22,7 @@ import { DeviceInstance } from '@/views/device/Instance/typings';
|
|||
import BaseForm from './BaseForm.vue';
|
||||
import { PropType } from 'vue';
|
||||
import { _deploy } from '@/api/device/product';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
|
@ -60,7 +61,7 @@ const form = reactive({
|
|||
model: {} as any,
|
||||
})
|
||||
if (metadataStore.model.action === 'edit') {
|
||||
form.model = metadataStore.model.item
|
||||
form.model = cloneDeep(metadataStore.model.item)
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
@ -75,7 +76,9 @@ const save = reactive({
|
|||
const type = metadataStore.model.type
|
||||
const _detail: ProductItem | DeviceInstance = props.type === 'device' ? instanceStore.detail : productStore.current
|
||||
const _metadata = JSON.parse(_detail?.metadata || '{}')
|
||||
const list = _metadata[type] as any[]
|
||||
console.log(_metadata)
|
||||
console.log(type)
|
||||
const list = (_metadata[type] as any[]) || []
|
||||
if (formValue.id) {
|
||||
if (metadataStore.model.action === 'add' && list.some(item => item.id === formValue.id)) {
|
||||
message.error('标识已存在')
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { Rule } from "ant-design-vue/es/form";
|
||||
|
||||
export const validateEnum = async (_rule: Rule, val: Record<any, any>[]) => {
|
||||
if (val.length === 0) return Promise.reject(new Error('请配置枚举项'));
|
||||
const flag = val.every((item) => {
|
||||
return item.value && item.text;
|
||||
});
|
||||
if (!flag) {
|
||||
return Promise.reject(new Error('请配置枚举项'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateArray = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val) return Promise.reject(new Error(`请输入元素配置`));
|
||||
await validateValueType(_rule, val)
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateJson = async (_rule: Rule, val: Record<any, any>[], title = '配置参数') => {
|
||||
if (!val || val.length === 0) {
|
||||
return Promise.reject(new Error(`请输入${title}`));
|
||||
}
|
||||
for (let item of val) {
|
||||
if (!item) return Promise.reject(new Error(`请输入${title}`));
|
||||
await validateIdName(_rule, item)
|
||||
await validateValueType(_rule, item.valueType)
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateIdName = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val.id) {
|
||||
return Promise.reject(new Error('请输入标识'))
|
||||
}
|
||||
if (!val.name) {
|
||||
return Promise.reject(new Error('请输入名称'))
|
||||
}
|
||||
}
|
||||
|
||||
export const validateValueType = async (_rule: Rule, val: Record<any, any>, title = '数据类型') => {
|
||||
console.log(val)
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
if (!val?.type) {
|
||||
return Promise.reject(new Error(`请选择${title}`))
|
||||
}
|
||||
if (['enum'].includes(val.type)) {
|
||||
await validateEnum(_rule, val.elements)
|
||||
}
|
||||
if (['array'].includes(val.type)) {
|
||||
await validateArray(_rule, val.elementType)
|
||||
}
|
||||
if (['object'].includes(val.type)) {
|
||||
await validateJson(_rule, val.properties)
|
||||
}
|
||||
if (['file'].includes(val.type) && !val.fileType) {
|
||||
return Promise.reject(new Error('请选择文件类型'))
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<j-pro-table :loading="loading" :data-source="data" size="small" :columns="columns" row-key="id" model="TABLE">
|
||||
<template #headerTitle>
|
||||
<a-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></a-input-search>
|
||||
</template>
|
||||
<template #rightExtraRender>
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<j-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch"></j-input-search>
|
||||
</div>
|
||||
<div>
|
||||
<PermissionButton type="primary" :uhas-permission="`${permission}:update`" key="add" @click="handleAddClick"
|
||||
:disabled="operateLimits('add', type)" :tooltip="{
|
||||
title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
|
||||
|
@ -14,45 +14,50 @@
|
|||
新增
|
||||
</PermissionButton>
|
||||
<Edit v-if="metadataStore.model.edit" :type="target" :tabs="type" @refresh="refreshMetadata"></Edit>
|
||||
</div>
|
||||
</div>
|
||||
<a-table :loading="loading" :data-source="data" :columns="columns" row-key="id" model="TABLE" size="small"
|
||||
:pagination="pagination">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'level'">
|
||||
{{ levelMap[record.expands?.level] || '-' }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'async'">
|
||||
{{ record.async ? '是' : '否' }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'valueType'">
|
||||
{{ record.valueType?.type }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'source'">
|
||||
{{ sourceMap[record.expands?.source] }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'type'">
|
||||
<j-tag v-for="item in (record.expands?.type || [])" :key="item">
|
||||
{{ expandsType[item] }}
|
||||
</j-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<j-space>
|
||||
<PermissionButton :has-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
:udisabled="operateLimits('updata', type)" @click="handleEditClick(record)" :tooltip="{
|
||||
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
|
||||
}">
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton :has-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
:pop-confirm="{
|
||||
title: '确认删除?', onConfirm: async () => {
|
||||
await removeItem(record);
|
||||
},
|
||||
}" :tooltip="{
|
||||
title: '删除',
|
||||
}">
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
</template>
|
||||
</template>
|
||||
<template #level="slotProps">
|
||||
{{ levelMap[slotProps.expands?.level] || '-' }}
|
||||
</template>
|
||||
<template #async="slotProps">
|
||||
{{ slotProps.async ? '是' : '否' }}
|
||||
</template>
|
||||
<template #valueType="slotProps">
|
||||
{{ slotProps.valueType?.type }}
|
||||
</template>
|
||||
<template #source="slotProps">
|
||||
{{ sourceMap[slotProps.expands?.source] }}
|
||||
</template>
|
||||
<template #type="slotProps">
|
||||
<j-tag v-for="item in (slotProps.expands?.type || [])" :key="item">
|
||||
{{ expandsType[item] }}
|
||||
</j-tag>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space>
|
||||
<PermissionButton :uhas-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
:udisabled="operateLimits('updata', type)" @click="handleEditClick(slotProps)" :tooltip="{
|
||||
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
|
||||
}">
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton :uhas-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
:pop-confirm="{
|
||||
title: '确认删除?', onConfirm: async () => {
|
||||
await removeItem(slotProps);
|
||||
},
|
||||
}" :tooltip="{
|
||||
title: '删除',
|
||||
}">
|
||||
<Aicon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-pro-table>
|
||||
</a-table>
|
||||
</template>
|
||||
<script setup lang="ts" name="BaseMetadata">
|
||||
import type { MetadataItem, MetadataType } from '@/views/device/Product/typings'
|
||||
|
@ -61,14 +66,10 @@ import { useInstanceStore } from '@/store/instance'
|
|||
import { useProductStore } from '@/store/product'
|
||||
import { useMetadataStore } from '@/store/metadata'
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import { message } from 'ant-design-vue/es'
|
||||
import { SystemConst } from '@/utils/consts'
|
||||
import { Store } from 'jetlinks-store'
|
||||
import { TablePaginationConfig, message } from 'ant-design-vue/es'
|
||||
import { asyncUpdateMetadata, removeMetadata } from '../metadata'
|
||||
import { detail } from '@/api/device/instance'
|
||||
import Edit from './Edit/index.vue'
|
||||
// import { detail } from '@/api/device/instance'
|
||||
// import { detail as productDetail } from '@/api/device/product'
|
||||
interface Props {
|
||||
type: MetadataType;
|
||||
target: 'product' | 'device';
|
||||
|
@ -106,6 +107,15 @@ const actions = [
|
|||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
const pagination = {
|
||||
showTotal: (num: number, range: number[]) => {
|
||||
return `第 ${range[0]} - ${range[1]} 条/总共 ${num} 条`;
|
||||
},
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: false,
|
||||
defaultPageSize: 10,
|
||||
size: 'small',
|
||||
} as TablePaginationConfig
|
||||
const columns = computed(() => MetadataMapping.get(type)!.concat(actions))
|
||||
const items = computed(() => JSON.parse((target === 'product' ? productStore.current?.metadata : instanceStore.current.metadata) || '{}') as MetadataItem[])
|
||||
const searchValue = ref<string>()
|
||||
|
@ -118,10 +128,6 @@ const handleSearch = (searchValue: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
|
||||
const refreshMetadata = () => {
|
||||
loading.value = true
|
||||
// const res = target === 'product'
|
||||
|
@ -135,6 +141,13 @@ const refreshMetadata = () => {
|
|||
watch([route.params.id, type], refreshMetadata, { immediate: true })
|
||||
|
||||
const metadataStore = useMetadataStore()
|
||||
watch(() => metadataStore.model.importMetadata,
|
||||
(val: boolean) => {
|
||||
if (!!val) {
|
||||
refreshMetadata()
|
||||
metadataStore.set('importMetadata', false)
|
||||
}
|
||||
})
|
||||
const handleAddClick = () => {
|
||||
metadataStore.set('edit', true)
|
||||
metadataStore.set('item', undefined)
|
||||
|
@ -165,11 +178,18 @@ const handleEditClick = (record: MetadataItem) => {
|
|||
}
|
||||
|
||||
const resetMetadata = async () => {
|
||||
// const { id } = route.params
|
||||
// const resp = await detail(id as string);
|
||||
// if (resp.status === 200) {
|
||||
// instanceStore.setCurrent(resp?.result || []);
|
||||
// }
|
||||
const { id } = route.params
|
||||
const resp = await detail(id as string);
|
||||
if (resp.status === 200) {
|
||||
instanceStore.setCurrent(resp?.result || []);
|
||||
if (target === 'device') {
|
||||
instanceStore.refresh(id as string)
|
||||
} else {
|
||||
productStore.refresh(id as string)
|
||||
}
|
||||
metadataStore.set('importMetadata', true)
|
||||
};
|
||||
|
||||
const removeItem = async (record: MetadataItem) => {
|
||||
|
@ -180,7 +200,7 @@ const removeItem = async (record: MetadataItem) => {
|
|||
const result = await asyncUpdateMetadata(target, _currentData);
|
||||
if (result.status === 200) {
|
||||
message.success('操作成功!');
|
||||
Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
|
||||
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
|
||||
metadataStore.model.edit = false;
|
||||
metadataStore.model.item = {};
|
||||
resetMetadata();
|
||||
|
@ -190,5 +210,9 @@ const removeItem = async (record: MetadataItem) => {
|
|||
};
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0;
|
||||
}
|
||||
</style>
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<a-drawer :mask-closable="false" title="查看物模型" width="700" v-model:visible="_visible" destroy-on-close @close="close">
|
||||
<j-drawer :mask-closable="false" title="查看物模型" width="700" v-model:visible="_visible" destroy-on-close @close="close">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleExport">
|
||||
<j-space>
|
||||
<j-button type="primary" @click="handleExport">
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</j-button>
|
||||
</j-space>
|
||||
</template>
|
||||
<a-spin :spinning="loading">
|
||||
<j-spin :spinning="loading">
|
||||
<div class="cat-content">
|
||||
<p class="cat-tip">
|
||||
物模型是对设备在云端的功能描述,包括设备的属性、服务和事件。物联网平台通过定义一种物的描述语言来描述物模型,称之为
|
||||
|
@ -15,15 +15,15 @@
|
|||
组装上报设备的数据。您可以导出完整物模型,用于云端应用开发。
|
||||
</p>
|
||||
</div>
|
||||
<a-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane>
|
||||
<a-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
|
||||
<j-tabs @change="handleConvertMetadata" destroy-inactive-tab-pane>
|
||||
<j-tab-pane v-for="item in codecs" :key="item.id" :tab="item.name">
|
||||
<div class="cat-panel">
|
||||
<MonacoEditor v-model="value" theme="vs" style="height: 100%"></MonacoEditor>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
</a-drawer>
|
||||
</j-tab-pane>
|
||||
</j-tabs>
|
||||
</j-spin>
|
||||
</j-drawer>
|
||||
</template>
|
||||
<script setup lang="ts" name="Cat">
|
||||
import { message } from 'ant-design-vue/es';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-modal :mask-closable="false" title="导入物模型" destroy-on-close v-model:visible="_visible" @cancel="close"
|
||||
<j-modal :mask-closable="false" title="导入物模型" destroy-on-close v-model:visible="_visible" @cancel="close"
|
||||
@ok="handleImport" :confirm-loading="loading">
|
||||
<div class="import-content">
|
||||
<p class="import-tip">
|
||||
|
@ -7,46 +7,46 @@
|
|||
导入的物模型会覆盖原来的属性、功能、事件、标签,请谨慎操作。
|
||||
</p>
|
||||
</div>
|
||||
<a-form layout="vertical" v-model="formModel">
|
||||
<a-form-item v-if="type === 'product'" label="导入方式" v-bind="validateInfos.type">
|
||||
<a-select v-model:value="formModel.type">
|
||||
<a-select-option value="copy">拷贝产品</a-select-option>
|
||||
<a-select-option value="import">导入物模型</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'">
|
||||
<a-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="物模型类型" v-bind="validateInfos.metadata"
|
||||
<j-form layout="vertical" v-model="formModel">
|
||||
<j-form-item v-if="type === 'product'" label="导入方式" v-bind="validateInfos.type">
|
||||
<j-select v-model:value="formModel.type">
|
||||
<j-select-option value="copy">拷贝产品</j-select-option>
|
||||
<j-select-option value="import">导入物模型</j-select-option>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'">
|
||||
<j-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label"></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="物模型类型" v-bind="validateInfos.metadata"
|
||||
v-if="type === 'device' || formModel.type === 'import'">
|
||||
<a-select v-model:value="formModel.metadata">
|
||||
<a-select-option value="jetlinks">Jetlinks物模型</a-select-option>
|
||||
<a-select-option value="alink">阿里云物模型TSL</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="导入类型" v-bind="validateInfos.metadataType"
|
||||
<j-select v-model:value="formModel.metadata">
|
||||
<j-select-option value="jetlinks">Jetlinks物模型</j-select-option>
|
||||
<j-select-option value="alink">阿里云物模型TSL</j-select-option>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="导入类型" v-bind="validateInfos.metadataType"
|
||||
v-if="type === 'device' || formModel.type === 'import'">
|
||||
<a-select v-model:value="formModel.metadataType">
|
||||
<a-select-option value="file">文件上传</a-select-option>
|
||||
<a-select-option value="script">脚本</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="文件上传" v-bind="validateInfos.upload" v-if="formModel.metadataType === 'file'">
|
||||
<a-input v-model:value="formModel.upload">
|
||||
<j-select v-model:value="formModel.metadataType">
|
||||
<j-select-option value="file">文件上传</j-select-option>
|
||||
<j-select-option value="script">脚本</j-select-option>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="文件上传" v-bind="validateInfos.upload" v-if="formModel.metadataType === 'file'">
|
||||
<j-input v-model:value="formModel.upload">
|
||||
<template #addonAfter>
|
||||
<a-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json"
|
||||
<j-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json"
|
||||
:show-upload-list="false" :action="FILE_UPLOAD" @change="fileChange"
|
||||
:headers="{ 'X-Access-Token': getToken()}">
|
||||
<AIcon type="UploadOutlined" class="upload-button" />
|
||||
</a-upload>
|
||||
</j-upload>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'">
|
||||
</j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="物模型" v-bind="validateInfos.import" v-if="formModel.metadataType === 'script'">
|
||||
<MonacoEditor v-model="formModel.import" theme="vs" style="height: 300px"></MonacoEditor>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-modal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Import">
|
||||
import { useForm } from 'ant-design-vue/es/form';
|
||||
|
@ -54,13 +54,14 @@ import { saveMetadata } from '@/api/device/instance'
|
|||
import { queryNoPagingPost, convertMetadata, modify } from '@/api/device/product'
|
||||
import type { DefaultOptionType } from 'ant-design-vue/es/select';
|
||||
import type { UploadProps, UploadFile, UploadChangeParam } from 'ant-design-vue/es';
|
||||
import type { DeviceMetadata, ProductItem } from '@/views/device/Product/typings'
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { DeviceMetadata } from '@/views/device/Product/typings'
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { useInstanceStore } from '@/store/instance'
|
||||
import { useProductStore } from '@/store/product';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { getToken } from '@/utils/comm';
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue'
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
|
||||
const route = useRoute()
|
||||
const instanceStore = useInstanceStore()
|
||||
|
@ -87,7 +88,6 @@ const _visible = computed({
|
|||
})
|
||||
|
||||
const close = () => {
|
||||
console.log(1)
|
||||
emits('update:visible', false);
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,7 @@ const operateLimits = (mdata: DeviceMetadata) => {
|
|||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
const metadataStore = useMetadataStore()
|
||||
const handleImport = async () => {
|
||||
validate().then(async (data) => {
|
||||
loading.value = true
|
||||
|
@ -224,6 +224,7 @@ const handleImport = async () => {
|
|||
} else {
|
||||
productStore.refresh(id as string)
|
||||
}
|
||||
metadataStore.set('importMetadata', true)
|
||||
// Store.set(SystemConst.GET_METADATA, true)
|
||||
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
|
||||
close()
|
||||
|
@ -263,13 +264,14 @@ const handleImport = async () => {
|
|||
message.success('导入成功')
|
||||
}
|
||||
}
|
||||
// Store.set(SystemConst.GET_METADATA, true)
|
||||
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
|
||||
if (props?.type === 'device') {
|
||||
instanceStore.refresh(id as string)
|
||||
} else {
|
||||
productStore.refresh(id as string)
|
||||
}
|
||||
metadataStore.set('importMetadata', true)
|
||||
// Store.set(SystemConst.GET_METADATA, true)
|
||||
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
|
||||
close();
|
||||
} catch (e) {
|
||||
loading.value = false
|
||||
|
|
|
@ -1,58 +1,91 @@
|
|||
<template>
|
||||
<a-card class="device-count-container">
|
||||
<template #title>
|
||||
<h5 class="title">基础统计</h5>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span style="color: #1d39c4; cursor: pointer" @click="jumpPage"
|
||||
>详情</span
|
||||
>
|
||||
</template>
|
||||
<div class="device-count-container">
|
||||
<h5 class="title">基础统计</h5>
|
||||
<span class="detail" @click="jumpPage('link/DashBoard')"> 详情 </span>
|
||||
|
||||
<div class="box-list">
|
||||
<div class="box-item">
|
||||
<div class="label">CPU使用率</div>
|
||||
<div class="value">{{ cpu }}</div>
|
||||
<div class="value">{{ cpu + '%' }}</div>
|
||||
<Pie
|
||||
class="chart"
|
||||
:value="cpu"
|
||||
chart-ref="cpuChart"
|
||||
:value="20"
|
||||
:color-arr="['#ebebeb', '#d3adf7']"
|
||||
image="/images/home/top-3.svg"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-item">
|
||||
<div class="label">JVM内存</div>
|
||||
<div class="value">{{ jvm }}</div>
|
||||
<div class="value">{{ jvm + '%' }}</div>
|
||||
<Pie
|
||||
class="chart"
|
||||
chart-ref="jvmChart"
|
||||
:value="31"
|
||||
:value="jvm"
|
||||
:color-arr="['#d6e4ff', '#85a5ff']"
|
||||
image="/images/home/top-4.svg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
import Pie from './Pie.vue';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
const cpu = ref('20%');
|
||||
const jvm = ref('31%');
|
||||
const cpu = ref(0);
|
||||
const jvm = ref(0);
|
||||
|
||||
const getData = ()=>{
|
||||
const { jumpPage } = useMenuStore();
|
||||
|
||||
}
|
||||
const cpuSocket = getWebSocket(
|
||||
'operations-statistics-system-info-cpu-realTime',
|
||||
'/dashboard/systemMonitor/stats/info/realTime',
|
||||
{
|
||||
type: 'cpu',
|
||||
interval: '2s',
|
||||
agg: 'avg',
|
||||
},
|
||||
)
|
||||
?.pipe(map((res: any) => res.payload))
|
||||
.subscribe((resp: any) => {
|
||||
cpu.value = resp.value?.systemUsage || 0;
|
||||
});
|
||||
const jvmSocket = getWebSocket(
|
||||
`operations-statistics-system-info-memory-realTime`,
|
||||
`/dashboard/systemMonitor/stats/info/realTime`,
|
||||
{
|
||||
type: 'memory',
|
||||
interval: '2s',
|
||||
agg: 'avg',
|
||||
},
|
||||
)
|
||||
?.pipe(map((res: any) => res.payload))
|
||||
.subscribe((payload: any) => {
|
||||
jvm.value = payload.value?.jvmHeapUsage || 0;
|
||||
});
|
||||
|
||||
const jumpPage = () => {};
|
||||
onUnmounted(() => {
|
||||
cpuSocket && cpuSocket.unsubscribe();
|
||||
jvmSocket && jvmSocket.unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.device-count-container {
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0;
|
||||
background-color: #fff;
|
||||
padding: 24px 14px;
|
||||
position: relative;
|
||||
.detail {
|
||||
color: #1d39c4;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 24px;
|
||||
z-index: 3;
|
||||
}
|
||||
.title {
|
||||
position: relative;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div
|
||||
class="box-item"
|
||||
v-for="(item, index) in cardData"
|
||||
@click="jumpPage(item.link,item.params)"
|
||||
@click="jumpPage(item)"
|
||||
>
|
||||
<div class="item-english">{{ item.english }}</div>
|
||||
<div class="item-title">{{ item.label }}</div>
|
||||
|
@ -21,10 +21,11 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { bootConfig } from "../typing";
|
||||
import { bootConfig } from '../typing';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const { jumpPage } = useMenuStore();
|
||||
const { jumpPage: _jumpPage } = useMenuStore();
|
||||
|
||||
const props = defineProps({
|
||||
cardData: Array<bootConfig>,
|
||||
|
@ -32,6 +33,10 @@ const props = defineProps({
|
|||
});
|
||||
const { cardData, cardTitle } = toRefs(props);
|
||||
|
||||
const jumpPage = (item: bootConfig) => {
|
||||
if (item.auth === undefined || item.auth) _jumpPage(item.link, item.params);
|
||||
else message.warning('暂无权限,请联系管理员');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -28,34 +28,19 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { message } from 'ant-design-vue';
|
||||
import { bootConfig } from "../typing";
|
||||
import { bootConfig } from '../typing';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
const router = useRouter();
|
||||
const props = defineProps({
|
||||
cardData: Array<bootConfig>,
|
||||
cardTitle: String,
|
||||
});
|
||||
const { cardData, cardTitle } = toRefs(props);
|
||||
const { jumpPage: _jumpPage } = useMenuStore();
|
||||
|
||||
const jumpPage = (row: bootConfig): void => {
|
||||
if (row.auth && row.link) {
|
||||
router.push(`${row.link}${objToParams(row.params || {})}`);
|
||||
} else {
|
||||
message.warning('暂无权限,请联系管理员');
|
||||
}
|
||||
};
|
||||
|
||||
const objToParams = (source: object): string => {
|
||||
if (Object.prototype.toString.call(source) === '[object Object]') {
|
||||
const paramsArr = <any>[];
|
||||
// 直接使用for in遍历对象ts会报错
|
||||
Object.entries(source).forEach(([prop, value]) => {
|
||||
if (typeof value === 'object') value = JSON.stringify(value);
|
||||
paramsArr.push(`${prop}=${value}`);
|
||||
});
|
||||
if (paramsArr.length > 0) return '?' + paramsArr.join('&');
|
||||
}
|
||||
return '';
|
||||
const jumpPage = (item: bootConfig) => {
|
||||
if (item.auth === undefined || item.auth) _jumpPage(item.link, item.params);
|
||||
else message.warning('暂无权限,请联系管理员');
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
<template>
|
||||
<div class="comprehensive-home-conatiner">
|
||||
<a-row :gutter="24" class="top" style="margin-bottom: 24px">
|
||||
<a-col :span="6" class="left">
|
||||
<j-row :gutter="24" class="top" style="margin-bottom: 24px">
|
||||
<j-col :span="6" class="left">
|
||||
<BootCardSmall
|
||||
:cardData="deviceBootConfig"
|
||||
cardTitle="物联网引导"
|
||||
/>
|
||||
<div style="width: 100%; height: 24px"></div>
|
||||
<BootCardSmall :cardData="opsBootConfig" cardTitle="运维引导" />
|
||||
</a-col>
|
||||
<a-col :span="18" class="right">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12"><DeviceCountCard /></a-col>
|
||||
<a-col :span="12"><BasicCountCard /></a-col>
|
||||
<a-col :span="24" style="margin-top: 24px">
|
||||
</j-col>
|
||||
<j-col :span="18" class="right">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12"><DeviceCountCard /></j-col>
|
||||
<j-col :span="12"><BasicCountCard /></j-col>
|
||||
<j-col :span="24" style="margin-top: 24px">
|
||||
<PlatformPicCard image="/images/home/content1.png" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
|
||||
<StepCard
|
||||
cardTitle="设备接入推荐步骤"
|
||||
|
@ -31,19 +31,36 @@
|
|||
tooltip="请根据业务需要对下述步骤进行选择性操作。"
|
||||
:dataList="opsStepDetails"
|
||||
/>
|
||||
|
||||
<div class="dialog">
|
||||
<ProductChooseDialog
|
||||
v-if="productDialogVisible"
|
||||
v-model:visible="productDialogVisible"
|
||||
@confirm="(id:string)=>jumpPage('device/Product/Detail', { id, tab: 'Device'})"
|
||||
/>
|
||||
<DeviceChooseDialog
|
||||
v-if="deviceDialogVisible"
|
||||
v-model:visible="deviceDialogVisible"
|
||||
@confirm="(id:string)=>jumpPage('device/Instance/Detail', { id })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ComprehensiveHome">
|
||||
import ProductChooseDialog from '../dialogs/ProductChooseDialog.vue';
|
||||
import DeviceChooseDialog from '../dialogs/DeviceChooseDialog.vue';
|
||||
import BootCardSmall from '../BootCardSmall.vue';
|
||||
import DeviceCountCard from '../DeviceCountCard.vue';
|
||||
import BasicCountCard from '../BasicCountCard.vue';
|
||||
import PlatformPicCard from '../PlatformPicCard.vue';
|
||||
import StepCard from '../StepCard.vue';
|
||||
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
import { recommendList, bootConfig } from '../../typing';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
const { jumpPage } = useMenuStore();
|
||||
|
||||
// 按钮权限控制
|
||||
const hasPermission = usePermissionStore().hasPermission;
|
||||
|
@ -53,16 +70,15 @@ const devicePermission = (action: string) =>
|
|||
hasPermission(`device/Instance:${action}`);
|
||||
const rulePermission = (action: string) =>
|
||||
hasPermission(`rule-engine/Instance:${action}`);
|
||||
// 页面权限控制
|
||||
const menuPermission = useMenuStore().hasPermission;
|
||||
|
||||
// 物联网引导-数据
|
||||
const _deviceBootConfig: bootConfig[] = [
|
||||
const deviceBootConfig: bootConfig[] = [
|
||||
{
|
||||
english: 'STEP1',
|
||||
label: '创建产品',
|
||||
link: '/iot/device/Product',
|
||||
auth: productPermission('add'),
|
||||
image: '/images/home/guide-home1.png',
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
|
@ -72,8 +88,9 @@ const _deviceBootConfig: bootConfig[] = [
|
|||
label: '创建设备',
|
||||
link: '/iot/device/Instance',
|
||||
auth: devicePermission('add'),
|
||||
image: '/images/home/guide-home1.png',
|
||||
params: {
|
||||
save: true,
|
||||
type: 'add',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -81,20 +98,12 @@ const _deviceBootConfig: bootConfig[] = [
|
|||
label: '规则引擎',
|
||||
link: '/iot/rule-engine/Instance',
|
||||
auth: rulePermission('add'),
|
||||
image: '/images/home/guide-home3.png',
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
const deviceImages = [
|
||||
'/images/home/guide-home1.png',
|
||||
'/images/home/guide-home2.png',
|
||||
'/images/home/guide-home3.png',
|
||||
];
|
||||
const deviceBootConfig = _deviceBootConfig.map((item, i) => ({
|
||||
...item,
|
||||
image: deviceImages[i],
|
||||
}));
|
||||
|
||||
// 设备接入推荐步骤-数据
|
||||
const deviceStepDetails: recommendList[] = [
|
||||
|
@ -103,7 +112,7 @@ const deviceStepDetails: recommendList[] = [
|
|||
details:
|
||||
'产品是设备的集合,通常指一组具有相同功能的设备。物联设备必须通过产品进行接入方式配置。',
|
||||
iconUrl: '/images/home/bottom-4.png',
|
||||
linkUrl: '/iot/device/Product',
|
||||
linkUrl: 'device/Product',
|
||||
auth: productPermission('add'),
|
||||
params: {
|
||||
save: true,
|
||||
|
@ -114,18 +123,20 @@ const deviceStepDetails: recommendList[] = [
|
|||
details:
|
||||
'通过产品对同一类型的设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
|
||||
iconUrl: '/images/home/bottom-1.png',
|
||||
linkUrl: '/iot/device/Product/detail',
|
||||
linkUrl: 'device/Product/Detail',
|
||||
auth: productPermission('update'),
|
||||
dialogTag: 'accessMethod',
|
||||
onClick: () => {
|
||||
productDialogVisible.value = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '添加测试设备',
|
||||
details: '添加单个设备,用于验证产品模型是否配置正确。',
|
||||
iconUrl: '/images/home/bottom-5.png',
|
||||
linkUrl: '/iot/device/Instance',
|
||||
linkUrl: 'device/Instance',
|
||||
auth: devicePermission('add'),
|
||||
params: {
|
||||
save: true,
|
||||
type: 'add',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -133,16 +144,17 @@ const deviceStepDetails: recommendList[] = [
|
|||
details:
|
||||
'对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
|
||||
iconUrl: '/images/home/bottom-2.png',
|
||||
linkUrl: '/iot/device/Instance/detail',
|
||||
// auth: devicePermission('update'),
|
||||
auth: true,
|
||||
dialogTag: 'funcTest',
|
||||
linkUrl: 'device/Instance/Detail',
|
||||
auth: devicePermission('update'),
|
||||
onClick: () => {
|
||||
deviceDialogVisible.value = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '批量添加设备',
|
||||
details: '批量添加同一产品下的设备',
|
||||
iconUrl: '/images/home/bottom-3.png',
|
||||
linkUrl: '/iot/device/Instance',
|
||||
linkUrl: 'device/Instance',
|
||||
auth: devicePermission('import'),
|
||||
params: {
|
||||
import: true,
|
||||
|
@ -151,41 +163,29 @@ const deviceStepDetails: recommendList[] = [
|
|||
];
|
||||
|
||||
// 运维管理引导-数据
|
||||
const _opsBootConfig: bootConfig[] = [
|
||||
const opsBootConfig: bootConfig[] = [
|
||||
{
|
||||
english: 'STEP1',
|
||||
label: '设备接入配置',
|
||||
link: '/iot/link/accessConfig',
|
||||
auth: menuPermission('link/accessConfig'),
|
||||
link: 'link/AccessConfig',
|
||||
image: '/images/home/guide-home4.png',
|
||||
},
|
||||
{
|
||||
english: 'STEP2',
|
||||
label: '日志排查',
|
||||
link: '/iot/link/Log',
|
||||
auth: menuPermission('link/Log'),
|
||||
link: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
},
|
||||
image: '/images/home/guide-home5.png',
|
||||
},
|
||||
{
|
||||
english: 'STEP3',
|
||||
label: '实时监控',
|
||||
link: '/iot/link/dashboard',
|
||||
auth: menuPermission('link/dashboard'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
link: 'link/DashBoard',
|
||||
image: '/images/home/guide-home6.png',
|
||||
},
|
||||
];
|
||||
const opsImages = [
|
||||
'/images/home/guide-home4.png',
|
||||
'/images/home/guide-home5.png',
|
||||
'/images/home/guide-home6.png',
|
||||
];
|
||||
const opsBootConfig = _opsBootConfig.map((item, i) => ({
|
||||
...item,
|
||||
image: opsImages[i],
|
||||
}));
|
||||
|
||||
// 运维管理推荐步骤-数据
|
||||
const opsStepDetails: recommendList[] = [
|
||||
|
@ -194,42 +194,50 @@ const opsStepDetails: recommendList[] = [
|
|||
details:
|
||||
'根据业务需求自定义开发对应的产品(设备模型)接入协议,并上传到平台。',
|
||||
iconUrl: '/images/home/bottom-1.png',
|
||||
linkUrl: '/iot/link/protocol',
|
||||
auth: menuPermission('link/Protocol'),
|
||||
linkUrl: 'link/Protocol',
|
||||
},
|
||||
{
|
||||
title: '证书管理',
|
||||
details: '统一维护平台内的证书,用于数据通信加密。',
|
||||
iconUrl: '/images/home/bottom-6.png',
|
||||
linkUrl: '/iot/link/Certificate',
|
||||
auth: menuPermission('link/Certificate'),
|
||||
linkUrl: 'link/Certificate',
|
||||
},
|
||||
{
|
||||
title: '网络组件',
|
||||
details: '根据不同的传输类型配置平台底层网络组件相关参数。',
|
||||
iconUrl: '/images/home/bottom-3.png',
|
||||
linkUrl: '/iot/link/type',
|
||||
auth: menuPermission('link/Type'),
|
||||
linkUrl: 'link/Type',
|
||||
},
|
||||
{
|
||||
title: '设备接入网关',
|
||||
details: '根据不同的传输类型,关联消息协议,配置设备接入网关相关参数。',
|
||||
iconUrl: '/images/home/bottom-4.png',
|
||||
linkUrl: '/iot/link/accessConfig',
|
||||
auth: menuPermission('link/AccessConfig'),
|
||||
linkUrl: 'link/AccessConfig',
|
||||
},
|
||||
{
|
||||
title: '日志管理',
|
||||
details: '监控系统日志,及时处理系统异常。',
|
||||
iconUrl: '/images/home/bottom-5.png',
|
||||
linkUrl: '/iot/link/Log',
|
||||
auth: menuPermission('Log'),
|
||||
linkUrl: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const productDialogVisible = ref(false);
|
||||
const deviceDialogVisible = ref(false);
|
||||
|
||||
|
||||
|
||||
// 设备管理-产品---新增弹窗 {save:true}
|
||||
// 设备管理-产品-产品详情---设备接入标签页 {id: 'xxxx', tab:'xxx'}
|
||||
// 规则引擎-规则编排---新增弹窗 {save: true}
|
||||
|
||||
|
||||
// 设备管理-设备---新增弹窗
|
||||
// 设备管理-设备---导入弹窗
|
||||
// 设备管理-设备-设备详情---设备诊断标签页
|
||||
|
||||
// 运维管理-日志管理---系统日志标签页
|
||||
</script>
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<template>
|
||||
<div class="device-home-container">
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="14">
|
||||
<j-row :gutter="10">
|
||||
<j-col :span="14">
|
||||
<BootCard :cardData="opsBootConfig" cardTitle="运维引导" />
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<BasicCountCard />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<PlatformPicCard />
|
||||
</a-row>
|
||||
<a-row>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<StepCard
|
||||
cardTitle="运维管理推荐步骤"
|
||||
tooltip="请根据业务需要对下述步骤进行选择性操作。"
|
||||
:dataList="opsStepDetails"
|
||||
/>
|
||||
</a-row>
|
||||
</j-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -26,24 +26,18 @@ import BootCard from '../BootCard.vue';
|
|||
import BasicCountCard from '../BasicCountCard.vue';
|
||||
import PlatformPicCard from '../PlatformPicCard.vue';
|
||||
import StepCard from '../StepCard.vue';
|
||||
import { useMenuStore } from "@/store/menu";
|
||||
|
||||
import { bootConfig, recommendList } from '../../typing';
|
||||
// 页面权限控制
|
||||
const menuPermission = useMenuStore().hasPermission;
|
||||
|
||||
import type { bootConfig, recommendList } from '../../typing';
|
||||
const opsBootConfig: bootConfig[] = [
|
||||
{
|
||||
english: 'STEP1',
|
||||
label: '设备接入配置',
|
||||
link: '/iot/link/accessConfig',
|
||||
auth: menuPermission('link/accessConfig'),
|
||||
link: 'link/AccessConfig',
|
||||
},
|
||||
{
|
||||
english: 'STEP2',
|
||||
label: '日志排查',
|
||||
link: '/iot/link/Log',
|
||||
auth: menuPermission('link/Log'),
|
||||
link: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
},
|
||||
|
@ -51,10 +45,9 @@ const opsBootConfig: bootConfig[] = [
|
|||
{
|
||||
english: 'STEP3',
|
||||
label: '实时监控',
|
||||
link: '/iot/link/dashboard',
|
||||
auth: menuPermission('link/dashboard'),
|
||||
link: 'link/DashBoard',
|
||||
params: {
|
||||
save: true,
|
||||
type: 'add',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -64,45 +57,36 @@ const opsStepDetails: recommendList[] = [
|
|||
details:
|
||||
'根据业务需求自定义开发对应的产品(设备模型)接入协议,并上传到平台。',
|
||||
iconUrl: '/images/home/bottom-1.png',
|
||||
linkUrl: '/iot/link/protocol',
|
||||
auth: menuPermission('link/Protocol'),
|
||||
|
||||
linkUrl: 'link/Protocol',
|
||||
},
|
||||
{
|
||||
title: '证书管理',
|
||||
details: '统一维护平台内的证书,用于数据通信加密。',
|
||||
iconUrl: '/images/home/bottom-6.png',
|
||||
linkUrl: '/iot/link/Certificate',
|
||||
auth: menuPermission('link/Certificate'),
|
||||
|
||||
linkUrl: 'link/Certificate',
|
||||
},
|
||||
{
|
||||
title: '网络组件',
|
||||
details: '根据不同的传输类型配置平台底层网络组件相关参数。',
|
||||
iconUrl: '/images/home/bottom-3.png',
|
||||
linkUrl: '/iot/link/type',
|
||||
auth: menuPermission('link/Type'),
|
||||
linkUrl: 'link/Type',
|
||||
},
|
||||
{
|
||||
title: '设备接入网关',
|
||||
details: '根据不同的传输类型,关联消息协议,配置设备接入网关相关参数。',
|
||||
iconUrl: '/images/home/bottom-4.png',
|
||||
linkUrl: '/iot/link/accessConfig',
|
||||
auth: menuPermission('link/AccessConfig'),
|
||||
linkUrl: 'link/AccessConfig',
|
||||
},
|
||||
{
|
||||
title: '日志管理',
|
||||
details: '监控系统日志,及时处理系统异常。',
|
||||
iconUrl: '/images/home/bottom-5.png',
|
||||
linkUrl: '/iot/link/Log',
|
||||
auth: menuPermission('Log'),
|
||||
linkUrl: 'Log',
|
||||
params: {
|
||||
key: 'system',
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -26,13 +26,19 @@ const { jumpPage } = useMenuStore();
|
|||
const projectNum = ref(0);
|
||||
const deviceNum = ref(0);
|
||||
|
||||
const menuPermission = useMenuStore().hasPermission;
|
||||
const getData = () => {
|
||||
getDeviceCount_api().then((resp: any) => {
|
||||
deviceNum.value = resp.result;
|
||||
});
|
||||
getProductCount_api({}).then((resp: any) => {
|
||||
projectNum.value = resp.result;
|
||||
});
|
||||
// 有产品菜单权限则获取数据
|
||||
menuPermission('device/Product') &&
|
||||
getDeviceCount_api().then((resp: any) => {
|
||||
deviceNum.value = resp.result;
|
||||
});
|
||||
|
||||
// 有设备菜单权限则获取数据
|
||||
menuPermission('device/Instance') &&
|
||||
getProductCount_api({}).then((resp: any) => {
|
||||
projectNum.value = resp.result;
|
||||
});
|
||||
};
|
||||
getData();
|
||||
</script>
|
||||
|
|
|
@ -1,27 +1,42 @@
|
|||
<template>
|
||||
<div class="device-home-container">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="14">
|
||||
<BootCard :cardData="deviceBootConfig" cardTitle="物联网引导" />
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<DeviceCountCard />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<PlatformPicCard />
|
||||
</a-row>
|
||||
<a-row>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<StepCard
|
||||
cardTitle="设备接入推荐步骤"
|
||||
tooltip="不同的设备因为通信协议的不同,存在接入步骤的差异"
|
||||
:dataList="deviceStepDetails"
|
||||
/>
|
||||
</a-row>
|
||||
</j-row>
|
||||
|
||||
<div class="dialog">
|
||||
<ProductChooseDialog
|
||||
v-if="productDialogVisible"
|
||||
v-model:visible="productDialogVisible"
|
||||
@confirm="(id:string)=>jumpPage('device/Product/Detail', { id, tab: 'Device'})"
|
||||
/>
|
||||
<DeviceChooseDialog
|
||||
v-if="deviceDialogVisible"
|
||||
v-model:visible="deviceDialogVisible"
|
||||
@confirm="(id:string)=>jumpPage('device/Instance/Detail', { id })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="deviceHome">
|
||||
import ProductChooseDialog from '../dialogs/ProductChooseDialog.vue';
|
||||
import DeviceChooseDialog from '../dialogs/DeviceChooseDialog.vue';
|
||||
import BootCard from '../BootCard.vue';
|
||||
import DeviceCountCard from '../DeviceCountCard.vue';
|
||||
import PlatformPicCard from '../PlatformPicCard.vue';
|
||||
|
@ -29,6 +44,7 @@ import StepCard from '../StepCard.vue';
|
|||
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
import { bootConfig, recommendList } from '../../typing';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
// 按钮权限控制
|
||||
const hasPermission = usePermissionStore().hasPermission;
|
||||
|
@ -39,6 +55,11 @@ const devicePermission = (action: string) =>
|
|||
const rulePermission = (action: string) =>
|
||||
hasPermission(`rule-engine/Instance:${action}`);
|
||||
|
||||
const { jumpPage } = useMenuStore();
|
||||
|
||||
const productDialogVisible = ref(false);
|
||||
const deviceDialogVisible = ref(false);
|
||||
|
||||
const deviceBootConfig: bootConfig[] = [
|
||||
{
|
||||
english: 'STEP1',
|
||||
|
@ -46,7 +67,7 @@ const deviceBootConfig: bootConfig[] = [
|
|||
link: 'device/Product',
|
||||
auth: productPermission('add'),
|
||||
params: {
|
||||
type: 'add',
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -64,7 +85,7 @@ const deviceBootConfig: bootConfig[] = [
|
|||
link: 'rule-engine/Instance',
|
||||
auth: rulePermission('add'),
|
||||
params: {
|
||||
type: 'add',
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -77,7 +98,7 @@ const deviceStepDetails: recommendList[] = [
|
|||
linkUrl: 'device/Product',
|
||||
auth: productPermission('add'),
|
||||
params: {
|
||||
type: 'add',
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -87,7 +108,9 @@ const deviceStepDetails: recommendList[] = [
|
|||
iconUrl: '/images/home/bottom-1.png',
|
||||
linkUrl: 'device/Product/Detail',
|
||||
auth: productPermission('update'),
|
||||
dialogTag: 'accessMethod',
|
||||
onClick: () => {
|
||||
productDialogVisible.value = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '添加测试设备',
|
||||
|
@ -105,8 +128,9 @@ const deviceStepDetails: recommendList[] = [
|
|||
'对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
|
||||
iconUrl: '/images/home/bottom-2.png',
|
||||
linkUrl: 'device/Instance/Detail',
|
||||
auth: true,
|
||||
dialogTag: 'funcTest',
|
||||
onClick: () => {
|
||||
deviceDialogVisible.value = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '批量添加设备',
|
||||
|
|
|
@ -3,34 +3,34 @@
|
|||
<div class="title">请选择首页视图</div>
|
||||
|
||||
<div class="choose-view">
|
||||
<a-row class="view-content" :gutter="24">
|
||||
<a-col
|
||||
<j-row class="view-content" :gutter="24">
|
||||
<j-col
|
||||
:span="8"
|
||||
class="select-item"
|
||||
:class="{ selected: selectValue === 'device' }"
|
||||
@click="selectValue = 'device'"
|
||||
>
|
||||
<img :src="getImage('/home/device.png')" alt="" />
|
||||
</a-col>
|
||||
<a-col
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="8"
|
||||
class="select-item"
|
||||
:class="{ selected: selectValue === 'ops' }"
|
||||
@click="selectValue = 'ops'"
|
||||
>
|
||||
<img :src="getImage('/home/ops.png')" alt="" />
|
||||
</a-col>
|
||||
<a-col
|
||||
</j-col>
|
||||
<j-col
|
||||
:span="8"
|
||||
class="select-item"
|
||||
:class="{ selected: selectValue === 'comprehensive' }"
|
||||
@click="selectValue = 'comprehensive'"
|
||||
>
|
||||
<img :src="getImage('/home/comprehensive.png')" alt="" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" class="btn" @click="confirm"
|
||||
>确定</a-button
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-button type="primary" class="btn" @click="confirm"
|
||||
>确定</j-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<dic class="step-container">
|
||||
<div class="step-container">
|
||||
<h5 class="title">
|
||||
<span style="margin-right: 12px">{{ props.cardTitle }}</span>
|
||||
<j-tooltip placement="top">
|
||||
|
@ -19,27 +19,12 @@
|
|||
<div class="box-details">{{ item.details }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dialogs">
|
||||
<ProductChooseDialog
|
||||
v-if="productDialogVisible"
|
||||
v-model:visible="productDialogVisible"
|
||||
@confirm="againJumpPage"
|
||||
/>
|
||||
<DeviceChooseDialog
|
||||
v-if="deviceDialogVisible"
|
||||
v-model:visible="deviceDialogVisible"
|
||||
@confirm="againJumpPage"
|
||||
/>
|
||||
</div>
|
||||
</dic>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import ProductChooseDialog from './dialogs/ProductChooseDialog.vue';
|
||||
import DeviceChooseDialog from './dialogs/DeviceChooseDialog.vue';
|
||||
import { recommendList } from '../typing';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
|
@ -51,33 +36,10 @@ const props = defineProps({
|
|||
dataList: Array as PropType<recommendList[]>,
|
||||
});
|
||||
|
||||
let selectRow: recommendList | rowType = {
|
||||
params: {},
|
||||
linkUrl: '',
|
||||
};
|
||||
// 跳转页面
|
||||
const jumpPage = (row: recommendList) => {
|
||||
if (!row.auth) return message.warning('暂无权限,请联系管理员');
|
||||
selectRow = row; // 二次跳转需要使用
|
||||
if (row.dialogTag == 'accessMethod')
|
||||
return (productDialogVisible.value = true);
|
||||
else if (row.dialogTag === 'funcTest')
|
||||
return (deviceDialogVisible.value = true);
|
||||
else if (row.linkUrl) {
|
||||
_jumpPage(row.linkUrl, row.params);
|
||||
}
|
||||
};
|
||||
// 弹窗返回后的二次跳转
|
||||
const againJumpPage = (id: string) => {
|
||||
_jumpPage(selectRow.linkUrl, { id });
|
||||
};
|
||||
|
||||
const productDialogVisible = ref(false);
|
||||
const deviceDialogVisible = ref(false);
|
||||
|
||||
type rowType = {
|
||||
params: object;
|
||||
linkUrl: string;
|
||||
if (row.auth === false) return message.warning('暂无权限,请联系管理员');
|
||||
row.onClick ? row.onClick(row) : _jumpPage(row.linkUrl, row.params);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -17,12 +17,22 @@
|
|||
<div class="card">
|
||||
<h3 style="margin: 0 0 24px 0">基本信息</h3>
|
||||
<p>
|
||||
<span style="font-weight: bold">clientId: </span>
|
||||
<span>{{ clientId }}</span>
|
||||
<span class="label">clientId: </span>
|
||||
<span class="value">{{ clientId }}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span style="font-weight: bold">secureKey:</span>
|
||||
<span>{{ secureKey }}</span>
|
||||
<span class="label">secureKey:</span>
|
||||
<span class="value">
|
||||
{{ showKey ? secureKey : '****************' }}
|
||||
</span>
|
||||
<AIcon
|
||||
:type="
|
||||
showKey
|
||||
? 'EyeOutlined'
|
||||
: 'EyeInvisibleOutlined'
|
||||
"
|
||||
@click="showKey = !showKey"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -47,14 +57,15 @@ const currentView = ref<string>('');
|
|||
const loading = ref<boolean>(true);
|
||||
const clientId = useUserInfo().$state.userInfos.id;
|
||||
const secureKey = ref<string>('');
|
||||
|
||||
const showKey = ref(false);
|
||||
// 获取选择的视图
|
||||
const setCurrentView = () => {
|
||||
getView_api().then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
if (resp.result) currentView.value = resp.result?.content;
|
||||
else if (resp.result.username === 'admin') {
|
||||
currentView.value = 'comprehensive';
|
||||
if (resp.result) {
|
||||
if (resp.result.username === 'admin')
|
||||
currentView.value = 'comprehensive';
|
||||
else currentView.value = resp.result?.content;
|
||||
} else currentView.value = 'init';
|
||||
}
|
||||
});
|
||||
|
@ -90,6 +101,15 @@ if (isNoCommunity) {
|
|||
p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.value {
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ export interface recommendList {
|
|||
details: string;
|
||||
iconUrl: string;
|
||||
linkUrl: string;
|
||||
params?: object;
|
||||
auth: boolean;
|
||||
dialogTag?: 'accessMethod' | 'funcTest';
|
||||
params?: any;
|
||||
auth?: boolean;
|
||||
onClick?: Function
|
||||
}
|
||||
// 产品列表里的每项
|
||||
export interface productItem {
|
||||
|
@ -25,7 +25,7 @@ export interface bootConfig {
|
|||
english: string,
|
||||
label: string,
|
||||
link: string,
|
||||
auth: boolean,
|
||||
auth?: boolean,
|
||||
image?:string,
|
||||
params?: object,
|
||||
params?: any,
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 国标级联-绑定通道 -->
|
||||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
v-model:visible="_vis"
|
||||
title="绑定通道"
|
||||
cancelText="取消"
|
||||
|
@ -51,19 +51,19 @@
|
|||
<h3>通道列表</h3>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<a-space>
|
||||
<a-badge
|
||||
<j-space>
|
||||
<j-badge
|
||||
:status="
|
||||
slotProps.status.value === 'online'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="slotProps.status.text"
|
||||
></a-badge>
|
||||
</a-space>
|
||||
></j-badge>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
</a-modal>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -28,34 +28,34 @@
|
|||
<h3>通道列表</h3>
|
||||
</template>
|
||||
<template #rightExtraRender>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="bindVis = true">
|
||||
<j-space>
|
||||
<j-button type="primary" @click="bindVis = true">
|
||||
绑定通道
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
</j-button>
|
||||
<j-popconfirm
|
||||
title="确认解绑?"
|
||||
@confirm="handleMultipleUnbind"
|
||||
>
|
||||
<a-button> 批量解绑 </a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
<j-button> 批量解绑 </j-button>
|
||||
</j-popconfirm>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #gbChannelIdHeader="title">
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="国标级联有16位、20位两种格式。在当前页面修改不会修改视频设备-通道页面中的国标ID"
|
||||
>
|
||||
<a-space>
|
||||
<j-space>
|
||||
<span>{{ title }}</span>
|
||||
<AIcon type="InfoCircleOutlined" />
|
||||
</a-space>
|
||||
</a-tooltip>
|
||||
</j-space>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<template #gbChannelId="slotProps">
|
||||
<a-space>
|
||||
<j-space>
|
||||
<Ellipsis>
|
||||
{{ slotProps.gbChannelId }}
|
||||
</Ellipsis>
|
||||
<a-popover
|
||||
<j-popover
|
||||
v-model:visible="slotProps.popVis"
|
||||
trigger="click"
|
||||
>
|
||||
|
@ -70,7 +70,7 @@
|
|||
</template>
|
||||
<template #content>
|
||||
<div class="simple-form">
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="gbID"
|
||||
@change="validField(slotProps)"
|
||||
/>
|
||||
|
@ -82,67 +82,67 @@
|
|||
该国标ID在同一设备下已存在
|
||||
</div>
|
||||
</div>
|
||||
<a-button
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="handleSave(slotProps)"
|
||||
:loading="loading"
|
||||
style="width: 100%"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
</j-button>
|
||||
</template>
|
||||
<a-button type="link" @click="slotProps.popVis = true">
|
||||
<j-button type="link" @click="slotProps.popVis = true">
|
||||
<AIcon type="EditOutlined" />
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</a-space>
|
||||
</j-button>
|
||||
</j-popover>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<a-space>
|
||||
<a-badge
|
||||
<j-space>
|
||||
<j-badge
|
||||
:status="
|
||||
slotProps.status.value === 'online'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="slotProps.status.text"
|
||||
></a-badge>
|
||||
</a-space>
|
||||
></j-badge>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
<j-space :size="16">
|
||||
<j-tooltip
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
<j-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<a-button
|
||||
<j-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
/></j-button>
|
||||
</j-popconfirm>
|
||||
<j-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
<j-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
/></j-button>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 国标级联-推送 -->
|
||||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
v-model:visible="_vis"
|
||||
title="推送"
|
||||
cancelText="取消"
|
||||
|
@ -9,10 +9,10 @@
|
|||
@ok="_vis = false"
|
||||
@cancel="_vis = false"
|
||||
>
|
||||
<a-row :gutter="20">
|
||||
<a-col :span="8">
|
||||
<j-row :gutter="20">
|
||||
<j-col :span="8">
|
||||
<p>成功:{{ successCount }}</p>
|
||||
<a-space>
|
||||
<j-space>
|
||||
<p>失败:{{ failCount }}</p>
|
||||
<a
|
||||
v-if="errMessage.length"
|
||||
|
@ -24,19 +24,19 @@
|
|||
"
|
||||
>下载</a
|
||||
>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
</j-space>
|
||||
</j-col>
|
||||
<j-col :span="8">
|
||||
<p>推送通道数量:{{ data.count }}</p>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
</j-col>
|
||||
<j-col :span="8">
|
||||
<p>已推送通道数量:{{ successCount + failCount }}</p>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<div v-if="flag">
|
||||
<a-textarea :rows="10" v-model:value="errStr" />
|
||||
<j-textarea :rows="10" v-model:value="errStr" />
|
||||
</div>
|
||||
</a-modal>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<!-- 国标级联新增/编辑 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||
<a-row :gutter="24">
|
||||
<j-card>
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<j-form ref="formRef" layout="vertical" :model="formData">
|
||||
<j-row :gutter="24">
|
||||
<TitleComponent data="基本信息" />
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="cascadeName"
|
||||
:rules="[
|
||||
|
@ -22,14 +22,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.cascadeName"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="代理视频流"
|
||||
name="proxyStream"
|
||||
:rules="[
|
||||
|
@ -39,23 +39,23 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-radio-group
|
||||
<j-radio-group
|
||||
button-style="solid"
|
||||
v-model:value="formData.proxyStream"
|
||||
>
|
||||
<a-radio-button :value="true">
|
||||
<j-radio-button :value="true">
|
||||
启用
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="false">
|
||||
</j-radio-button>
|
||||
<j-radio-button :value="false">
|
||||
禁用
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</j-radio-button>
|
||||
</j-radio-group>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
|
||||
<TitleComponent data="信令服务配置" />
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
name="clusterNodeId"
|
||||
:rules="[
|
||||
{
|
||||
|
@ -67,25 +67,25 @@
|
|||
<template #label>
|
||||
<span>
|
||||
集群节点
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="使用此集群节点级联到上级平台"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select
|
||||
<j-select
|
||||
v-model:value="formData.clusterNodeId"
|
||||
placeholder="请选择集群节点"
|
||||
:options="clustersList"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="信令名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
|
@ -99,14 +99,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.name"
|
||||
placeholder="请输入信令名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
label="上级SIP ID"
|
||||
name="sipId"
|
||||
:rules="[
|
||||
|
@ -120,14 +120,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.sipId"
|
||||
placeholder="请输入上级SIP ID"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="上级SIP域"
|
||||
name="domain"
|
||||
:rules="[
|
||||
|
@ -141,14 +141,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.domain"
|
||||
placeholder="请输入上级平台SIP域"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="上级SIP 地址"
|
||||
name="remoteAddress"
|
||||
:rules="[
|
||||
|
@ -161,17 +161,17 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="14">
|
||||
<a-input
|
||||
<j-row :gutter="10">
|
||||
<j-col :span="14">
|
||||
<j-input
|
||||
v-model:value="
|
||||
formData.remoteAddress
|
||||
"
|
||||
placeholder="请输入IP地址"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<a-input-number
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<j-input-number
|
||||
:min="1"
|
||||
:max="65535"
|
||||
v-model:value="
|
||||
|
@ -180,13 +180,13 @@
|
|||
placeholder="请输入端口"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
label="本地SIP ID"
|
||||
name="localSipId"
|
||||
:rules="[
|
||||
|
@ -200,14 +200,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.localSipId"
|
||||
placeholder="网关侧的SIP ID"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
name="host"
|
||||
:rules="[
|
||||
{
|
||||
|
@ -222,36 +222,36 @@
|
|||
<template #label>
|
||||
<span>
|
||||
SIP本地地址
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="使用指定的网卡和端口进行请求"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="14">
|
||||
<a-select
|
||||
<j-row :gutter="10">
|
||||
<j-col :span="14">
|
||||
<j-select
|
||||
v-model:value="formData.host"
|
||||
placeholder="请选择IP地址"
|
||||
:options="allList"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<a-select
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<j-select
|
||||
v-model:value="formData.port"
|
||||
placeholder="请选择端口"
|
||||
:options="allListPorts"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="SIP远程地址"
|
||||
name="publicHost"
|
||||
:rules="[
|
||||
|
@ -264,17 +264,17 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="14">
|
||||
<a-input
|
||||
<j-row :gutter="10">
|
||||
<j-col :span="14">
|
||||
<j-input
|
||||
v-model:value="
|
||||
formData.publicHost
|
||||
"
|
||||
placeholder="请输入IP地址"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
<a-input-number
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<j-input-number
|
||||
:min="1"
|
||||
:max="65535"
|
||||
v-model:value="
|
||||
|
@ -283,12 +283,12 @@
|
|||
placeholder="请输入端口"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item
|
||||
label="传输协议"
|
||||
name="transport"
|
||||
:rules="[
|
||||
|
@ -298,22 +298,22 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-radio-group
|
||||
<j-radio-group
|
||||
button-style="solid"
|
||||
v-model:value="formData.transport"
|
||||
@change="setPorts"
|
||||
>
|
||||
<a-radio-button value="UDP">
|
||||
<j-radio-button value="UDP">
|
||||
UDP
|
||||
</a-radio-button>
|
||||
<a-radio-button value="TCP">
|
||||
</j-radio-button>
|
||||
<j-radio-button value="TCP">
|
||||
TCP
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-radio-button>
|
||||
</j-radio-group>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="用户"
|
||||
name="user"
|
||||
:rules="[
|
||||
|
@ -327,14 +327,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.user"
|
||||
placeholder="请输入用户"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="接入密码"
|
||||
name="password"
|
||||
:rules="[
|
||||
|
@ -348,14 +348,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
<j-input-password
|
||||
v-model:value="formData.password"
|
||||
placeholder="请输入接入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="厂商"
|
||||
name="manufacturer"
|
||||
:rules="[
|
||||
|
@ -369,14 +369,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.manufacturer"
|
||||
placeholder="请输入厂商"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="型号"
|
||||
name="model"
|
||||
:rules="[
|
||||
|
@ -390,14 +390,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.model"
|
||||
placeholder="请输入型号"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="版本号"
|
||||
name="firmware"
|
||||
:rules="[
|
||||
|
@ -411,14 +411,14 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.firmware"
|
||||
placeholder="请输入版本号"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="心跳周期(秒)"
|
||||
name="keepaliveInterval"
|
||||
:rules="[
|
||||
|
@ -428,7 +428,7 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input-number
|
||||
<j-input-number
|
||||
:min="1"
|
||||
:max="10000"
|
||||
v-model:value="
|
||||
|
@ -437,10 +437,10 @@
|
|||
placeholder="请输入心跳周期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="注册间隔(秒)"
|
||||
name="registerInterval"
|
||||
:rules="[
|
||||
|
@ -450,7 +450,7 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<a-input-number
|
||||
<j-input-number
|
||||
:min="1"
|
||||
:max="10000"
|
||||
v-model:value="
|
||||
|
@ -459,29 +459,29 @@
|
|||
placeholder="请输入注册间隔"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
<j-form-item>
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
:loading="btnLoading"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-button>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="doc">
|
||||
<h1>1.概述</h1>
|
||||
<div>
|
||||
配置国标级联,平台可以将已经接入到自身的摄像头共享给第三方调用播放。
|
||||
</div>
|
||||
<div>
|
||||
<a-alert
|
||||
<j-alert
|
||||
message="注:该配置只用于将本平台向上级联至第三方平台,如需第三方平台向上级联至本平台,请在“视频设备”页面新增设备时选择“GB/T28181”接入方式。"
|
||||
type="info"
|
||||
show-icon
|
||||
|
@ -494,7 +494,7 @@
|
|||
<h2>1、上级SIP ID</h2>
|
||||
<div>请填写第三方平台中配置的<b>SIP ID</b>。</div>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/northbound/doc2.png')"
|
||||
/>
|
||||
|
@ -502,7 +502,7 @@
|
|||
<h2>2、上级SIP 域</h2>
|
||||
<div>请填写第三方平台中配置的<b>SIP ID域</b>。</div>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/northbound/doc1.png')"
|
||||
/>
|
||||
|
@ -510,7 +510,7 @@
|
|||
<h2>3、上级SIP 地址</h2>
|
||||
<div>请填写第三方平台中配置的<b>SIP ID地址</b>。</div>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/northbound/doc3.png')"
|
||||
/>
|
||||
|
@ -549,9 +549,9 @@
|
|||
SIP代理与 SIP服务器出现1s误 差所经过的运行时间。
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
@ -652,7 +652,7 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
const regDomain =
|
||||
/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?/;
|
||||
/[j-zA-Z0-9][-j-zA-Z0-9]{0,62}(\.[j-zA-Z0-9][-j-zA-Z0-9]{0,62})+\.?/;
|
||||
/**
|
||||
* 上级SIP地址 字段验证
|
||||
* @param _
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
</h3>
|
||||
<p>通道数量:{{ slotProps.count }}</p>
|
||||
<Ellipsis>
|
||||
<a-badge
|
||||
<j-badge
|
||||
:text="`sip:${slotProps.sipConfigs[0]?.sipId}@${slotProps.sipConfigs[0]?.hostAndPort}`"
|
||||
:status="
|
||||
slotProps.status?.value === 'enabled'
|
||||
|
@ -92,7 +92,7 @@
|
|||
{{ slotProps.sipConfigs[0]?.publicHost }}
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<a-badge
|
||||
<j-badge
|
||||
:text="slotProps.status?.text"
|
||||
:status="
|
||||
slotProps.status?.value === 'enabled'
|
||||
|
@ -102,7 +102,7 @@
|
|||
/>
|
||||
</template>
|
||||
<template #onlineStatus="slotProps">
|
||||
<a-badge
|
||||
<j-badge
|
||||
:text="slotProps.onlineStatus?.text"
|
||||
:status="
|
||||
slotProps.onlineStatus?.value === 'online'
|
||||
|
@ -112,7 +112,7 @@
|
|||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<j-space :size="16">
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
|
@ -131,7 +131,7 @@
|
|||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
|
||||
|
|
|
@ -3,26 +3,26 @@
|
|||
<div class="card-header">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div class="tools">
|
||||
<a-space>
|
||||
<a-radio-group
|
||||
<j-space>
|
||||
<j-radio-group
|
||||
v-model:value="dimension"
|
||||
button-style="solid"
|
||||
>
|
||||
<a-radio-button value="today">今日</a-radio-button>
|
||||
<a-radio-button value="week">近一周</a-radio-button>
|
||||
<a-radio-button value="month">近一月</a-radio-button>
|
||||
<a-radio-button value="year">近一年</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-range-picker
|
||||
<j-radio-button value="today">今日</j-radio-button>
|
||||
<j-radio-button value="week">近一周</j-radio-button>
|
||||
<j-radio-button value="month">近一月</j-radio-button>
|
||||
<j-radio-button value="year">近一年</j-radio-button>
|
||||
</j-radio-group>
|
||||
<j-range-picker
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
valueFormat="x"
|
||||
v-model:value="dateRange"
|
||||
/>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="chartData.length" class="chart" ref="chartRef"></div>
|
||||
<a-empty v-else class="no-data" description="暂无数据"></a-empty>
|
||||
<j-empty v-else class="no-data" description="暂无数据"></j-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -56,52 +56,49 @@ const chartRef = ref();
|
|||
const createChart = () => {
|
||||
nextTick(() => {
|
||||
const myChart = echarts.init(chartRef.value as HTMLElement);
|
||||
|
||||
const sData: number[] = props.chartData.map(
|
||||
(m: any) => m.value && m.value.toFixed(2),
|
||||
);
|
||||
const maxY = Math.max.apply(null, sData.length ? sData : [0]);
|
||||
const options = {
|
||||
grid: {
|
||||
left: '7%',
|
||||
left: maxY > 100000 ? 90 : 50,
|
||||
right: '5%',
|
||||
top: '5%',
|
||||
bottom: '5%',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// formatter: '{a}<br>{b}: {c}',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
formatter: '{b0}<br />{a0}: {c0}',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: props.chartData.map((m: any) => m.x),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
// minInterval: 1,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
data: props.chartData.map((m: any) => m.x),
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
show: false,
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '播放数量(人次)',
|
||||
data: sData,
|
||||
type: 'bar',
|
||||
barWidth: 16,
|
||||
itemStyle: {
|
||||
color: '#2f54eb',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '播放数量(人次)',
|
||||
type: 'line',
|
||||
symbol: 'circle',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
data: props.chartData.map(
|
||||
(m: any) => m.value && m.value.toFixed(2),
|
||||
),
|
||||
lineStyle: {
|
||||
color: '#a5fff9',
|
||||
},
|
||||
data: sData,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -116,8 +113,6 @@ const createChart = () => {
|
|||
watch(
|
||||
() => props.chartData,
|
||||
(val) => {
|
||||
console.log('createChart', val);
|
||||
|
||||
createChart();
|
||||
},
|
||||
{ deep: true },
|
||||
|
@ -138,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],
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
<div class="content-left">
|
||||
<div class="content-left-title">
|
||||
<span>{{ title }}</span>
|
||||
<a-tooltip placement="top" v-if="tooltip">
|
||||
<j-tooltip placement="top" v-if="tooltip">
|
||||
<template #title>
|
||||
<span>{{ tooltip }}</span>
|
||||
</template>
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</div>
|
||||
<div class="content-left-value">{{ value }}</div>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<div class="top-card-footer">
|
||||
<template v-for="(item, index) in footer" :key="index">
|
||||
<span v-if="!item.status">{{ item.title }}</span>
|
||||
<a-badge v-else :text="item.title" :status="item.status" />
|
||||
<j-badge v-else :text="item.title" :status="item.status" />
|
||||
<div class="footer-item-value">{{ item.value }}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="6">
|
||||
<TopCard
|
||||
title="设备数量"
|
||||
:img="getImage('/media/dashboard-1.png')"
|
||||
:footer="deviceFooter"
|
||||
:value="deviceTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
</j-col>
|
||||
<j-col :span="6">
|
||||
<TopCard
|
||||
title="通道数量"
|
||||
:img="getImage('/media/dashboard-2.png')"
|
||||
:footer="channelFooter"
|
||||
:value="channelTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
</j-col>
|
||||
<j-col :span="6">
|
||||
<TopCard
|
||||
title="录像数量"
|
||||
:img="getImage('/media/dashboard-3.png')"
|
||||
:footer="aggFooter"
|
||||
:value="aggTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
</j-col>
|
||||
<j-col :span="6">
|
||||
<TopCard
|
||||
title="播放中数量"
|
||||
tooltip="当前正在播放的通道数量之和"
|
||||
|
@ -33,15 +33,15 @@
|
|||
:footer="aggPlayingFooter"
|
||||
:value="aggPlayingTotal"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="24" class="dash-board-bottom">
|
||||
</j-col>
|
||||
<j-col :span="24" class="dash-board-bottom">
|
||||
<Card
|
||||
title="播放数量(人次)"
|
||||
:chartData="chartData"
|
||||
@change="getPlayCount"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
@ -123,6 +123,7 @@ const getAggData = () => {
|
|||
{
|
||||
title: '总时长',
|
||||
value: timestampFormat(res.result.duration),
|
||||
status: '',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
@ -139,6 +140,7 @@ const getAggPlayingData = () => {
|
|||
{
|
||||
title: '播放人数',
|
||||
value: res.result.playerTotal,
|
||||
status: '',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
@ -188,9 +190,11 @@ const getPlayCount = async (params: any) => {
|
|||
])
|
||||
.then((res) => {
|
||||
let result: any = [];
|
||||
res.result.forEach((item: any) => {
|
||||
result = [...result, ...item.data];
|
||||
});
|
||||
res.result
|
||||
.sort((a: any, b: any) => b.data.timestamp - a.data.timestamp)
|
||||
.forEach((item: any) => {
|
||||
result.push({ group: item.group, ...item.data });
|
||||
});
|
||||
chartData.value = result.map((m: any) => ({
|
||||
x: m.timeString,
|
||||
value: m.value,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 视频设备 - 播放 -->
|
||||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
v-model:visible="_vis"
|
||||
title="播放"
|
||||
cancelText="取消"
|
||||
|
@ -33,17 +33,17 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="media-live-tool">
|
||||
<a-radio-group
|
||||
<j-radio-group
|
||||
v-model:value="mediaType"
|
||||
button-style="solid"
|
||||
@change="mediaStart"
|
||||
>
|
||||
<a-radio-button value="mp4">MP4</a-radio-button>
|
||||
<a-radio-button value="flv">FLV</a-radio-button>
|
||||
<a-radio-button value="m3u8">HLS</a-radio-button>
|
||||
</a-radio-group>
|
||||
<j-radio-button value="mp4">MP4</j-radio-button>
|
||||
<j-radio-button value="flv">FLV</j-radio-button>
|
||||
<j-radio-button value="m3u8">HLS</j-radio-button>
|
||||
</j-radio-group>
|
||||
</div>
|
||||
</a-modal>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Modal 弹窗,用于新增、修改数据 -->
|
||||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
v-model:visible="_vis"
|
||||
:title="!!formData.id ? '编辑' : '新增'"
|
||||
width="650px"
|
||||
|
@ -9,10 +9,10 @@
|
|||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form ref="formRef" :model="formData" layout="vertical">
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
<j-form ref="formRef" :model="formData" layout="vertical">
|
||||
<j-row :gutter="10">
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
name="channelId"
|
||||
:rules="[
|
||||
{
|
||||
|
@ -26,22 +26,22 @@
|
|||
>
|
||||
<template #label>
|
||||
通道ID
|
||||
<a-tooltip title="若不填写,系统将自动生成唯一ID">
|
||||
<j-tooltip title="若不填写,系统将自动生成唯一ID">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.channelId"
|
||||
:disabled="!!formData.id"
|
||||
placeholder="请输入通道ID"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
name="name"
|
||||
label="通道名称"
|
||||
:rules="[
|
||||
|
@ -49,14 +49,29 @@
|
|||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.name"
|
||||
placeholder="请输入通道名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<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="[
|
||||
{ required: true, message: '请输入视频地址' },
|
||||
|
@ -65,66 +80,81 @@
|
|||
>
|
||||
<template #label>
|
||||
视频地址
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
title="不同厂家的RTSP固定地址规则不同,请按对应厂家的规则填写"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.media_url"
|
||||
placeholder="请输入视频地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
name="media_username"
|
||||
label="用户名"
|
||||
:rules="{ max: 64, message: '最多可输入64个字符' }"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.media_username"
|
||||
placeholder="请输入用户名"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
name="media_password"
|
||||
label="密码"
|
||||
:rules="{ max: 64, message: '最多可输入64个字符' }"
|
||||
>
|
||||
<a-input-password
|
||||
<j-input-password
|
||||
v-model:value="formData.media_password"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item name="address" label="安装地址">
|
||||
<a-input
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<j-form-item name="address" label="安装地址">
|
||||
<j-input
|
||||
v-model:value="formData.address"
|
||||
placeholder="请输入安装地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item name="description" label="说明">
|
||||
<a-textarea
|
||||
</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
|
||||
v-model:value="formData.description"
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
showCount
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-form>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div class="channel-tree">
|
||||
<div class="channel-tree-query">
|
||||
<j-input @change="queryTree" placeholder="请输入目录名称">
|
||||
<template #suffix>
|
||||
<AIcon type="SearchOutlined" />
|
||||
</template>
|
||||
</j-input>
|
||||
</div>
|
||||
<div class="channel-tree-content">
|
||||
<j-tree
|
||||
:height="500"
|
||||
:selectedKeys="selectedKeys"
|
||||
:treeData="treeData"
|
||||
:onSelect="(keys:any) => {
|
||||
if (keys.length) {
|
||||
selectedKeys = keys
|
||||
if (props.onSelect) {
|
||||
props.onSelect(keys[0]);
|
||||
}
|
||||
}
|
||||
}"
|
||||
:fieldNames="{ key: 'id', title: 'name' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { debounce } from 'lodash';
|
||||
import ChannelApi from '@/api/media/channel';
|
||||
import DeviceApi from '@/api/media/device';
|
||||
|
||||
interface TreeProps {
|
||||
deviceId: string;
|
||||
onSelect: (id: string) => void;
|
||||
onTreeLoad: (type: boolean) => void;
|
||||
}
|
||||
|
||||
const props = defineProps<TreeProps>();
|
||||
|
||||
const treeData = ref<any[]>([]);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
|
||||
const getTreeData = async (id: string, data?: any) => {
|
||||
const { result } = await ChannelApi.queryTree(id, data);
|
||||
treeData.value[0].children = result || [];
|
||||
props.onTreeLoad(treeData.value[0].children.length > 10);
|
||||
treeData.value = treeData.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取设备详情
|
||||
* @param id
|
||||
*/
|
||||
const getDeviceDetail = async (id: string) => {
|
||||
const deviceResp = await DeviceApi.detail(id);
|
||||
if (deviceResp.status === 200) {
|
||||
treeData.value = [
|
||||
{
|
||||
id,
|
||||
name: deviceResp.result.name,
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
selectedKeys.value = [id];
|
||||
getTreeData(props.deviceId, {});
|
||||
}
|
||||
};
|
||||
|
||||
const queryTree = debounce((e: any) => {
|
||||
getTreeData(props.deviceId, {
|
||||
terms: [
|
||||
{ column: 'name', termType: 'like', value: `%${e.target.value}%` },
|
||||
],
|
||||
});
|
||||
}, 300);
|
||||
|
||||
watchEffect(() => {
|
||||
getDeviceDetail(props.deviceId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.channel-tree {
|
||||
height: 100%;
|
||||
|
||||
.channel-tree-query {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.channel-tree-content {
|
||||
min-height: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,48 @@
|
|||
.device-channel-warp {
|
||||
display: flex;
|
||||
|
||||
.left-warp {
|
||||
position: relative;
|
||||
margin-right: 16px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
|
||||
.left-content {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&.active {
|
||||
width: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
.left-warp--btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
padding: 20px 4px;
|
||||
color: rgba(#000, 0.3);
|
||||
background-color: rgba(#f0f0f0, 6);
|
||||
border-radius: ~'100% 0 0 100% / 50% 0 0 50%';
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: rgba(#000, 0.5);
|
||||
background-color: rgba(#f0f0f0, 8);
|
||||
}
|
||||
|
||||
&.active {
|
||||
right: 50%;
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
transform: translateX(50%) rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
|
@ -1,82 +1,102 @@
|
|||
<!-- 视频设备-通道列表 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<j-advanced-search
|
||||
type="simple"
|
||||
:columns="columns"
|
||||
target="product"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<JProTable
|
||||
ref="listRef"
|
||||
:columns="columns"
|
||||
:request="(e:any) => ChannelApi.list(e, route?.query.id as string)"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'notifyTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
model="table"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-tooltip
|
||||
v-if="route?.query.type === 'gb28181-2016'"
|
||||
title="接入方式为GB/T28281时,不支持新增"
|
||||
<div class="device-channel-warp">
|
||||
<div class="left-warp" v-if="route.query.type === 'gb28181-2016'">
|
||||
<div class="left-content" :class="{ active: show }">
|
||||
<Tree
|
||||
:deviceId="deviceId"
|
||||
:on-tree-load="(e) => (show = e)"
|
||||
:on-select="handleSelect"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="left-warp--btn"
|
||||
:class="{ active: !show }"
|
||||
@click="show = !show"
|
||||
>
|
||||
<a-button type="primary" disabled> 新增 </a-button>
|
||||
</a-tooltip>
|
||||
<a-button type="primary" @click="handleAdd" v-else>
|
||||
新增
|
||||
</a-button>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<a-space>
|
||||
<a-badge
|
||||
:status="
|
||||
slotProps.status.value === 'online'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="slotProps.status.text"
|
||||
></a-badge>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
:disabled="i.disabled"
|
||||
<AIcon type="LeftOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<j-advanced-search
|
||||
type="simple"
|
||||
:columns="columns"
|
||||
target="product"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<JProTable
|
||||
ref="listRef"
|
||||
:columns="columns"
|
||||
:request="(e:any) => ChannelApi.list(e, route?.query.id as string)"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'notifyTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
model="table"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-tooltip
|
||||
v-if="route?.query.type === 'gb28181-2016'"
|
||||
title="接入方式为GB/T28281时,不支持新增"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<j-button type="primary" disabled> 新增 </j-button>
|
||||
</j-tooltip>
|
||||
<j-button type="primary" @click="handleAdd" v-else>
|
||||
新增
|
||||
</j-button>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<j-space>
|
||||
<j-badge
|
||||
:status="
|
||||
slotProps.status.value === 'online'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="slotProps.status.text"
|
||||
></j-badge>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space :size="16">
|
||||
<j-tooltip
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<j-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<j-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></j-button>
|
||||
</j-popconfirm>
|
||||
<j-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<j-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></j-button>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Save
|
||||
v-model:visible="saveVis"
|
||||
|
@ -94,7 +114,9 @@ import { useMenuStore } from 'store/menu';
|
|||
import { message } from 'ant-design-vue';
|
||||
import Save from './Save.vue';
|
||||
import Live from './Live/index.vue';
|
||||
import Tree from './Tree/index.vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
const route = useRoute();
|
||||
|
@ -253,4 +275,25 @@ const getActions = (
|
|||
? actions.filter((f) => f.key !== 'delete')
|
||||
: actions;
|
||||
};
|
||||
|
||||
// 左侧树
|
||||
const show = ref(false);
|
||||
const deviceId = computed(() => route.query.id as string);
|
||||
const handleSelect = (key: string) => {
|
||||
if (key === deviceId.value && listRef.value?.reload) {
|
||||
listRef.value?.reload();
|
||||
} else {
|
||||
params.value = {
|
||||
terms: [
|
||||
{
|
||||
column: 'parentId',
|
||||
value: key,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import './index.less';
|
||||
</style>
|
||||
|
|
|
@ -53,14 +53,14 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="playback-right">
|
||||
<a-spin :spinning="loading">
|
||||
<a-tooltip placement="topLeft">
|
||||
<j-spin :spinning="loading">
|
||||
<j-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>云端:存储在服务器中</div>
|
||||
<div>本地:存储在设备本地</div>
|
||||
</template>
|
||||
<div>类型: <AIcon type="QuestionCircleOutlined" /></div>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
<RadioCard
|
||||
layout="horizontal"
|
||||
:options="[
|
||||
|
@ -80,11 +80,11 @@
|
|||
v-model="type"
|
||||
/>
|
||||
<div class="playback-calendar">
|
||||
<a-calendar
|
||||
<j-calendar
|
||||
v-model:value="time"
|
||||
:fullscreen="false"
|
||||
:disabledDate="
|
||||
(currentDate) => currentDate > dayjs(new Date())
|
||||
(currentDate: Dayjs) => currentDate > dayjs(new Date())
|
||||
"
|
||||
@change="handlePanelChange"
|
||||
/>
|
||||
|
@ -93,20 +93,20 @@
|
|||
class="playback-list"
|
||||
:class="{ 'no-list': !historyList.length }"
|
||||
>
|
||||
<a-empty
|
||||
<j-empty
|
||||
v-if="!historyList.length"
|
||||
description="暂无数据"
|
||||
/>
|
||||
<a-list
|
||||
<j-list
|
||||
v-else
|
||||
class="playback-list-items"
|
||||
itemLayout="horizontal"
|
||||
:dataSource="historyList"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<j-list-item>
|
||||
<template #actions>
|
||||
<a-tooltip
|
||||
<j-tooltip
|
||||
key="play-btn"
|
||||
:title="
|
||||
(item.startTime ||
|
||||
|
@ -136,8 +136,8 @@
|
|||
"
|
||||
/>
|
||||
</a>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
</j-tooltip>
|
||||
<j-tooltip
|
||||
key="download"
|
||||
:title="
|
||||
type !== 'local'
|
||||
|
@ -155,7 +155,7 @@
|
|||
() => downloadClick(item)
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</j-tooltip>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
|
@ -173,12 +173,12 @@
|
|||
).format('HH:mm:ss')
|
||||
}}
|
||||
</div>
|
||||
</a-list-item>
|
||||
</j-list-item>
|
||||
</template>
|
||||
<div></div>
|
||||
</a-list>
|
||||
</j-list>
|
||||
</div>
|
||||
</a-spin>
|
||||
</j-spin>
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-modal
|
||||
<j-modal
|
||||
v-model:visible="_vis"
|
||||
title="快速添加"
|
||||
cancelText="取消"
|
||||
|
@ -9,38 +9,38 @@
|
|||
:confirmLoading="btnLoading"
|
||||
width="660px"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="产品名称" v-bind="validateInfos.name">
|
||||
<a-input
|
||||
<j-form layout="vertical">
|
||||
<j-form-item label="产品名称" v-bind="validateInfos.name">
|
||||
<j-input
|
||||
v-model:value="formData.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</j-form-item>
|
||||
<template v-if="channel === 'gb28181-2016' && formData.accessId">
|
||||
<a-form-item
|
||||
<j-form-item
|
||||
label="接入密码"
|
||||
v-bind="validateInfos['configuration.access_pwd']"
|
||||
>
|
||||
<a-input-password
|
||||
<j-input-password
|
||||
v-model:value="formData.configuration.access_pwd"
|
||||
placeholder="请输入接入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="流传输模式">
|
||||
<a-select
|
||||
</j-form-item>
|
||||
<j-form-item label="流传输模式">
|
||||
<j-select
|
||||
v-model:value="formData.configuration.stream_mode"
|
||||
placeholder="请选择流传输模式"
|
||||
:options="streamMode"
|
||||
/>
|
||||
</a-form-item>
|
||||
</j-form-item>
|
||||
</template>
|
||||
<a-form-item label="接入网关" v-bind="validateInfos.accessId">
|
||||
<j-form-item label="接入网关" v-bind="validateInfos.accessId">
|
||||
<div class="gateway-box">
|
||||
<div v-if="!gatewayList.length">
|
||||
暂无数据,请先
|
||||
<a-button type="link">
|
||||
<j-button type="link">
|
||||
添加{{ providerType[props.channel] }} 接入网关
|
||||
</a-button>
|
||||
</j-button>
|
||||
</div>
|
||||
<div
|
||||
class="gateway-item"
|
||||
|
@ -71,32 +71,34 @@
|
|||
{{ item.name }}
|
||||
</h3>
|
||||
<div class="desc">{{ item.description }}</div>
|
||||
<a-row v-if="props.channel === 'gb28181-2016'">
|
||||
<a-col :span="12">
|
||||
<j-row v-if="props.channel === 'gb28181-2016'">
|
||||
<j-col :span="12">
|
||||
{{ item.channelInfo?.name }}
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
{{ item.protocolDetail.name }}
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<p
|
||||
v-for="(i, idx) in item.channelInfo
|
||||
?.addresses"
|
||||
:key="`${i.address}_address${idx}`"
|
||||
>
|
||||
<a-badge
|
||||
:text="i.address"
|
||||
:color="
|
||||
i.health === -1
|
||||
? 'red'
|
||||
: 'green'
|
||||
"
|
||||
/>
|
||||
<Ellipsis>
|
||||
<j-badge
|
||||
:text="i.address"
|
||||
:color="
|
||||
i.health === -1
|
||||
? 'red'
|
||||
: 'green'
|
||||
"
|
||||
/>
|
||||
</Ellipsis>
|
||||
</p>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-else>
|
||||
<a-col :span="24">
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row v-else>
|
||||
<j-col :span="24">
|
||||
<div class="subtitle">
|
||||
{{ item.protocolDetail.name }}
|
||||
</div>
|
||||
|
@ -105,15 +107,15 @@
|
|||
item.protocolDetail.description
|
||||
}}
|
||||
</p>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -263,6 +265,7 @@ const handleCancel = () => {
|
|||
text-align: center;
|
||||
.gateway-item {
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
.card-item-content-title,
|
||||
.desc,
|
||||
.subtitle {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<!-- 视频设备新增/编辑 -->
|
||||
<template>
|
||||
<page-container>
|
||||
<a-card>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
<j-card>
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<j-form layout="vertical">
|
||||
<j-form-item
|
||||
label="接入方式"
|
||||
v-bind="validateInfos.channel"
|
||||
>
|
||||
|
@ -16,134 +16,134 @@
|
|||
:disabled="!!route.query.id"
|
||||
v-model="formData.channel"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
</j-form-item>
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="8">
|
||||
<JUpload
|
||||
v-model:modelValue="formData.photoUrl"
|
||||
:bgImage="formData.photoUrl"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-form-item
|
||||
</j-col>
|
||||
<j-col :span="16">
|
||||
<j-form-item
|
||||
label="ID"
|
||||
v-bind="validateInfos.id"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.id"
|
||||
placeholder="请输入"
|
||||
:disabled="!!route.query.id"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="设备名称"
|
||||
v-bind="validateInfos.name"
|
||||
>
|
||||
<a-input
|
||||
<j-input
|
||||
v-model:value="formData.name"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item
|
||||
label="所属产品"
|
||||
v-bind="validateInfos.productId"
|
||||
>
|
||||
<a-row :gutter="[0, 10]">
|
||||
<a-col :span="!!route.query.id ? 24 : 22">
|
||||
<a-select
|
||||
<j-row :gutter="[0, 10]">
|
||||
<j-col :span="!!route.query.id ? 24 : 22">
|
||||
<j-select
|
||||
v-model:value="formData.productId"
|
||||
placeholder="请选择所属产品"
|
||||
:disabled="!!route.query.id"
|
||||
>
|
||||
<a-select-option
|
||||
<j-select-option
|
||||
v-for="(item, index) in productList"
|
||||
:key="index"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col :span="2" v-if="!route.query.id">
|
||||
<a-button
|
||||
</j-select-option>
|
||||
</j-select>
|
||||
</j-col>
|
||||
<j-col :span="2" v-if="!route.query.id">
|
||||
<j-button
|
||||
type="link"
|
||||
@click="saveProductVis = true"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
</j-button>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="接入密码"
|
||||
v-bind="validateInfos['others.access_pwd']"
|
||||
v-if="formData.channel === 'gb28181-2016'"
|
||||
>
|
||||
<a-input-password
|
||||
<j-input-password
|
||||
v-model:value="formData.others.access_pwd"
|
||||
placeholder="请输入接入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</j-form-item>
|
||||
<template v-if="!!route.query.id">
|
||||
<a-form-item
|
||||
<j-form-item
|
||||
label="流传输模式"
|
||||
v-bind="validateInfos.streamMode"
|
||||
>
|
||||
<a-radio-group
|
||||
<j-radio-group
|
||||
button-style="solid"
|
||||
v-model:value="formData.streamMode"
|
||||
>
|
||||
<a-radio-button value="UDP">
|
||||
<j-radio-button value="UDP">
|
||||
UDP
|
||||
</a-radio-button>
|
||||
<a-radio-button value="TCP_PASSIVE">
|
||||
</j-radio-button>
|
||||
<j-radio-button value="TCP_PASSIVE">
|
||||
TCP被动
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="设备厂商">
|
||||
<a-input
|
||||
</j-radio-button>
|
||||
</j-radio-group>
|
||||
</j-form-item>
|
||||
<j-form-item label="设备厂商">
|
||||
<j-input
|
||||
v-model:value="formData.manufacturer"
|
||||
placeholder="请输入设备厂商"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="设备型号">
|
||||
<a-input
|
||||
</j-form-item>
|
||||
<j-form-item label="设备型号">
|
||||
<j-input
|
||||
v-model:value="formData.model"
|
||||
placeholder="请输入设备型号"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="固件版本">
|
||||
<a-input
|
||||
</j-form-item>
|
||||
<j-form-item label="固件版本">
|
||||
<j-input
|
||||
v-model:value="formData.firmware"
|
||||
placeholder="请输入固件版本"
|
||||
/>
|
||||
</a-form-item>
|
||||
</j-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item label="说明">
|
||||
<a-textarea
|
||||
<j-form-item label="说明">
|
||||
<j-textarea
|
||||
v-model:value="formData.description"
|
||||
show-count
|
||||
:maxlength="200"
|
||||
:rows="5"
|
||||
placeholder="请输入说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
</j-form-item>
|
||||
<j-form-item>
|
||||
<j-button
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
:loading="btnLoading"
|
||||
>
|
||||
保存
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-button>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div v-if="1" class="doc" style="height: 800">
|
||||
<h1>1.概述</h1>
|
||||
<div>
|
||||
|
@ -166,7 +166,7 @@
|
|||
各个厂家、不同设备型号的设备端配置页面布局存在差异,但配置项基本大同小异,此处以大华摄像头为例作为接入配置示例
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/media/doc1.png')"
|
||||
/>
|
||||
|
@ -177,7 +177,7 @@
|
|||
SIP域通常为SIP服务器编号的前10位。
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/media/doc2.png')"
|
||||
/>
|
||||
|
@ -187,7 +187,7 @@
|
|||
SIP服务器IP/端口填入该设备所属产品-接入方式页面中“连接信息”的IP/端口。
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/media/doc3.png')"
|
||||
/>
|
||||
|
@ -201,7 +201,7 @@
|
|||
填入该设备所属产品-接入方式页面中“GB28281配置”处的接入密码
|
||||
</div>
|
||||
<div class="image">
|
||||
<a-image
|
||||
<j-image
|
||||
width="100%"
|
||||
:src="getImage('/media/doc4.png')"
|
||||
/>
|
||||
|
@ -230,9 +230,9 @@
|
|||
只能选择接入方式为固定地址的产品,若当前无对应产品,可点击右侧快速添加按钮,填写产品名称和选择固定地址类型的网关完成产品创建。
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</j-card>
|
||||
|
||||
<SaveProduct
|
||||
v-model:visible="saveProductVis"
|
||||
|
@ -285,7 +285,7 @@ const formRules = ref({
|
|||
},
|
||||
{ max: 64, message: '最多输入64个字符' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
pattern: /^[j-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -44,30 +44,30 @@
|
|||
<h3 class="card-item-content-title">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<j-row>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">厂商</div>
|
||||
<div>{{ slotProps.manufacturer }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
通道数量
|
||||
</div>
|
||||
<div>{{ slotProps.channelNumber }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">型号</div>
|
||||
<div>{{ slotProps.model }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
接入方式
|
||||
</div>
|
||||
<div>
|
||||
{{ providerType[slotProps.provider] }}
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<PermissionButton
|
||||
|
@ -92,7 +92,7 @@
|
|||
</CardBox>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<j-space :size="16">
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
|
@ -111,7 +111,7 @@
|
|||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
</page-container>
|
||||
|
@ -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();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
<template>
|
||||
<a-card class="device-count-container">
|
||||
<template #title>
|
||||
<h5 class="title">基础统计</h5>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span style="color: #1d39c4; cursor: pointer" @click="jumpPage"
|
||||
>详情</span
|
||||
>
|
||||
</template>
|
||||
|
||||
<div class="device-count-container">
|
||||
<h5 class="title">
|
||||
<span>基础统计</span>
|
||||
<a style="font-size: 12px;" @click="jumpPage">
|
||||
详情
|
||||
</a>
|
||||
</h5>
|
||||
<div class="box-list">
|
||||
<div class="box-item">
|
||||
<div class="label">设备数量</div>
|
||||
|
@ -21,7 +18,7 @@
|
|||
<img :src="getImage('/home/top-2.png')" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -56,6 +53,8 @@ const jumpPage = () => {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.device-count-container {
|
||||
background-color: #fff;
|
||||
padding: 24px 14px;
|
||||
:deep(.ant-card-body) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
|
|
@ -1,26 +1,76 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="14">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="14">
|
||||
<BootCard
|
||||
:cardData="deviceBootConfig"
|
||||
cardTitle="视频中心引导"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="10">
|
||||
</j-col>
|
||||
<j-col :span="10">
|
||||
<BasicCountCard />
|
||||
</a-col>
|
||||
<a-col :span="24" style="margin: 20px 0">
|
||||
</j-col>
|
||||
<j-col :span="24" style="margin: 20px 0">
|
||||
<PlatformPicCard />
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
</j-col>
|
||||
<j-col :span="24">
|
||||
<StepCard
|
||||
cardTitle="设备接入推荐步骤"
|
||||
tooltip="不同的设备因为通信协议的不同,存在接入步骤的差异"
|
||||
:dataList="deviceStepDetails"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-col>
|
||||
</j-row>
|
||||
|
||||
<!-- 选择设备 -->
|
||||
<j-modal
|
||||
title="选择设备"
|
||||
width="800px"
|
||||
v-model:visible="visible"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
@cancel="visible = false"
|
||||
@ok="handleSubmit"
|
||||
>
|
||||
<j-advanced-search
|
||||
type="simple"
|
||||
:columns="columns"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JProTable
|
||||
ref="tableRef"
|
||||
model="table"
|
||||
rowKey="id"
|
||||
:columns="columns"
|
||||
:request="deviceApi.list"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
:rowSelection="{
|
||||
type: 'radio',
|
||||
selectedRowKeys: deviceItem?.id
|
||||
? [deviceItem.id]
|
||||
: undefined,
|
||||
onSelect: (record: any) => {
|
||||
deviceItem = record;
|
||||
}
|
||||
}"
|
||||
>
|
||||
<template #state="slotProps">
|
||||
<a-space>
|
||||
<a-badge
|
||||
:status="
|
||||
slotProps.state.value === 'online'
|
||||
? 'success'
|
||||
: 'error'
|
||||
"
|
||||
:text="slotProps.state.text"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
</j-modal>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
@ -33,6 +83,13 @@ import BasicCountCard from '@/views/media/Home/components/BasicCountCard.vue';
|
|||
import { usePermissionStore } from '@/store/permission';
|
||||
import type { bootConfig, recommendList } from '@/views/home/typing';
|
||||
|
||||
import deviceApi from '@/api/media/device';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useMenuStore } from 'store/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
// 权限控制
|
||||
const hasPermission = usePermissionStore().hasPermission;
|
||||
|
||||
|
@ -40,59 +97,116 @@ const deviceBootConfig: bootConfig[] = [
|
|||
{
|
||||
english: 'STEP1',
|
||||
label: '添加视频设备',
|
||||
link: '/media/device/Save',
|
||||
auth: hasPermission('/media/device/Save'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
link: 'media/Device/Save',
|
||||
auth: hasPermission('media/Device:add'),
|
||||
},
|
||||
{
|
||||
english: 'STEP2',
|
||||
label: '分屏展示',
|
||||
link: '/media/SplitScreen',
|
||||
auth: hasPermission('/media/SplitScreen'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
link: 'media/SplitScreen',
|
||||
},
|
||||
{
|
||||
english: 'STEP3',
|
||||
label: '国标级联',
|
||||
link: '/media/Cascade',
|
||||
auth: hasPermission('/media/Cascade'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
link: 'media/Cascade',
|
||||
},
|
||||
];
|
||||
|
||||
const deviceStepDetails: recommendList[] = [
|
||||
{
|
||||
title: '添加视频设备',
|
||||
details: '根据视频设备的传输协议,在已创建的产品下添加对应的设备。',
|
||||
iconUrl: '/images/home/bottom-6.png',
|
||||
linkUrl: '/media/device/Save',
|
||||
auth: hasPermission('/media/device/Save'),
|
||||
params: {
|
||||
save: true,
|
||||
},
|
||||
linkUrl: 'media/Device/Save',
|
||||
auth: hasPermission('media/Device:add'),
|
||||
},
|
||||
{
|
||||
title: '查看通道',
|
||||
details: '查看设备下的通道数据,可以进行直播、录制等操作。',
|
||||
iconUrl: '/images/home/bottom-7.png',
|
||||
linkUrl: '/media/device/Channel',
|
||||
auth: hasPermission('/media/device/Save'),
|
||||
dialogTag: 'accessMethod',
|
||||
// linkUrl: 'media/Device/Channel',
|
||||
linkUrl: '',
|
||||
auth: hasPermission('media/Device:view'),
|
||||
onClick: (row: any) => {
|
||||
if (hasPermission('media/Device:view')) {
|
||||
visible.value = true;
|
||||
} else {
|
||||
message.warning('暂无权限,请联系管理员');
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '分屏展示',
|
||||
details: '对多个通道的视频流数据进行分屏展示。',
|
||||
iconUrl: '/images/home/bottom-8.png',
|
||||
linkUrl: '/media/SplitScreen',
|
||||
auth: hasPermission('/media/SplitScreen'),
|
||||
params: {
|
||||
save: true,
|
||||
linkUrl: 'media/SplitScreen',
|
||||
},
|
||||
];
|
||||
|
||||
// 选择设备
|
||||
const visible = ref(false);
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '通道数量',
|
||||
dataIndex: 'channelNumber',
|
||||
key: 'channelNumber',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '在线', value: 'online' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
],
|
||||
handleValue: (v: any) => {
|
||||
return v;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
const deviceItem = ref();
|
||||
const handleSubmit = () => {
|
||||
if (deviceItem.value && deviceItem.value.id) {
|
||||
menuStory.jumpPage(
|
||||
'media/Device/Channel',
|
||||
{},
|
||||
{
|
||||
id: deviceItem.value.id,
|
||||
type: deviceItem.value.provider,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
message.warning('请选择设备');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card class="splitScreen">
|
||||
<j-card class="splitScreen">
|
||||
<div class="split-screen">
|
||||
<LeftTree @onSelect="mediaStart" />
|
||||
<div class="right-content">
|
||||
|
@ -17,7 +17,7 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</j-card>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="left-content">
|
||||
<a-tree
|
||||
<j-tree
|
||||
:height="700"
|
||||
:show-line="{ showLeafIcon: false }"
|
||||
:show-icon="true"
|
||||
|
@ -15,7 +15,7 @@
|
|||
v-if="!treeData.find((f: any) => f.id === id)"
|
||||
/>
|
||||
</template>
|
||||
</a-tree>
|
||||
</j-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue