fix: 视频中心仪表盘兼容数据处理;

* fix: bug#28509【设备】设备物模型属性类型为地理位置时,运行状态查看详情,选择图标展示筛选条件错误

* fix: bug#28513 【设备】设备详情界面,物联网卡和远程升级需做权限控制,当没有权限时,隐藏当前tab

* fix: 视频中心仪表盘兼容数据处理

* chore: add dayjs

* update: docker tag
This commit is contained in:
XieYongHong 2024-08-07 15:52:46 +08:00 committed by GitHub
parent 984fd9c115
commit 3da54bb59b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 171 additions and 110 deletions

View File

@ -1,3 +1,3 @@
#!/usr/bin/env bash
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.2.0-TEST .
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.2.0-TEST
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.3.0-SNAPSHOT .
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.3.0-SNAPSHOT

View File

@ -1,7 +1,7 @@
{
"name": "jetlinks-vue",
"private": true,
"version": "2.2.0",
"version": "2.2.1",
"scripts": {
"dev": "vite --mode develop",
"dev:force": "vite --force --mode develop",
@ -29,6 +29,7 @@
"axios": "^1.2.1",
"colorpicker-v3": "^2.10.2",
"cronstrue": "^2.50.0",
"dayjs": "^1.11.12",
"driver.js": "^0.9.8",
"echarts": "^5.4.1",
"event-source-polyfill": "^1.0.31",

View File

@ -2,7 +2,7 @@
<j-pro-layout
v-bind="layoutConf"
v-model:collapsed="basicLayout.collapsed"
v-model:openKeys="basicLayout.openKeys"
:openKeys="basicLayout.collapsed ? [] : basicLayout.openKeys"
:selectedKeys="basicLayout.selectedKeys"
:breadcrumb="basicLayout.pure ? undefined : { routes: breadcrumbs }"
:headerHeight='basicLayout.pure ? 1 : layout.headerHeight'

View File

@ -257,7 +257,10 @@ defineExpose({
//position: absolute;
transition: top .2s, height .2s, background-color .1s;
border-bottom: 1px solid #ebebeb;
.metadata-edit-table-cell{
position:absolute;
min-width: 0;;
}
&:hover {
background-color: rgb(248, 248, 248);
}

View File

@ -233,7 +233,7 @@ watch(
(val) => {
const diffInSeconds = dayjs(val[1]).diff(dayjs(val[0]), 'minute');
if (diffInSeconds < 60) {
periodOptions.value = [
periodOptions.value = _type.value ? [
{
label: '实际值',
value: '*',
@ -242,10 +242,15 @@ watch(
label: '按分钟统计',
value: '1m',
},
];
cycle.value = '*';
] : [
{
label: '按分钟统计',
value: '1m',
},
]
cycle.value = _type.value ? '*' : '1m';
} else if (diffInSeconds < 1440) {
periodOptions.value = [
periodOptions.value = _type.value ? [
{
label: '实际值',
value: '*',
@ -258,8 +263,17 @@ watch(
label: '按小时统计',
value: '1h',
},
];
cycle.value = '*';
] : [
{
label: '按分钟统计',
value: '1m',
},
{
label: '按小时统计',
value: '1h',
},
]
cycle.value = _type.value ? '*' : '1m';
} else if (diffInSeconds < 43200) {
periodOptions.value = [
{

View File

@ -170,14 +170,6 @@ const initList = [
key: 'Log',
tab: '日志管理',
},
{
key: 'CardManagement',
tab: '物联网卡',
},
{
key: 'Firmware',
tab: '远程升级',
},
];
const list = ref([...initList]);
@ -226,6 +218,18 @@ const getDetail = () => {
tab: '预处理数据',
});
}
if (permissionStore.hasPermission('iot-card/CardManagement:view')) {
list.value.push({
key: 'CardManagement',
tab: '物联网卡',
});
}
if (permissionStore.hasPermission('device/Firmware:view')) {
list.value.push({
key: 'Firmware',
tab: '远程升级',
});
}
if (
instanceStore.current?.protocol &&
!['modbus-tcp', 'opc-ua'].includes(instanceStore.current?.protocol) &&
@ -330,16 +334,11 @@ const getDetailFn = async () => {
list.value = [...initList];
getDetail();
instanceStore.tabActiveKey = routerParams.params.value.tab || 'Info';
}else{
} else {
instanceStore.tabActiveKey = routerParams.params.value.tab || 'Info';
}
};
onMounted(() => {
getDetailFn();
});
const onTabChange = (e: string) => {
if (instanceStore.tabActiveKey === 'Metadata') {
EventEmitter.emit('MetadataTabs', () => {
@ -389,6 +388,10 @@ const jumpProduct = () => {
});
};
onMounted(() => {
getDetailFn();
});
onUnmounted(() => {
instanceStore.current = {} as any;
statusRef.value && statusRef.value.unsubscribe();

View File

@ -193,10 +193,6 @@ const list = ref([
key: 'Device',
tab: '设备接入',
},
{
key: 'Firmware',
tab: '远程升级',
},
]);
const tabs = {
@ -300,10 +296,6 @@ const getProtocol = async () => {
key: 'DataAnalysis',
tab: '数据解析',
},
{
key: 'Firmware',
tab: '远程升级',
},
];
} else {
list.value = [
@ -320,10 +312,6 @@ const getProtocol = async () => {
key: 'Device',
tab: '设备接入',
},
{
key: 'Firmware',
tab: '远程升级',
},
];
}
}
@ -348,6 +336,12 @@ const getProtocol = async () => {
tab: '预处理数据',
});
}
if (permissionStore.hasPermission('device/Firmware:view')) {
list.value.push({
key: 'Firmware',
tab: '远程升级',
});
}
}
};
/**

View File

@ -1,19 +1,19 @@
<!-- 国标级联-绑定通道 -->
<template>
<j-modal
v-model:visible="_vis"
visible
title="绑定通道"
cancelText="取消"
okText="确定"
width="80%"
@ok="handleSave"
@cancel="_vis = false"
@cancel="$emit('cancel')"
:confirmLoading="loading"
>
<pro-search
:columns="columns"
target="media-bind"
@search="handleSearch"
type="simple"
/>
<JProTable
@ -68,37 +68,15 @@
<script setup lang="ts">
import CascadeApi from '@/api/media/cascade';
import { onlyMessage } from '@/utils/comm';
import { PropType } from 'vue';
const route = useRoute();
type Emits = {
(e: 'update:visible', data: boolean): void;
(e: 'cancel'): void;
(e: 'submit'): void;
};
const emit = defineEmits<Emits>();
const props = defineProps({
visible: { type: Boolean, default: false },
data: {
type: Object as PropType<Partial<Record<string, any>>>,
default: () => ({}),
},
});
const _vis = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val),
});
watch(
() => _vis.value,
(val) => {
if (val) handleSearch({ terms: [] });
else _selectedRowKeys.value = [];
},
);
const columns = [
{
title: '设备名称',
@ -211,10 +189,13 @@ const handleSave = async () => {
})
if (resp.success) {
onlyMessage('操作成功!');
_vis.value = false;
emit('submit');
} else {
onlyMessage('操作失败!', 'error');
}
};
onMounted(()=>{
handleSearch({ terms: [] })
})
</script>

View File

@ -138,7 +138,7 @@
</JProTable>
</FullPage>
<BindChannel v-model:visible="bindVis" @submit="listRef.reload()" />
<BindChannel v-if="bindVis" @submit="submitData" @cancel="cancel"/>
</page-container>
</template>
@ -420,6 +420,14 @@ const handleClose = (data: any) => {
valid.value = undefined;
gbID.value = '';
};
const cancel = () =>{
bindVis.value = false
}
const submitData = () =>{
bindVis.value = false
listRef.value?.reload()
}
</script>
<style lang="less" scoped>
.header {

View File

@ -617,7 +617,7 @@ const getClustersList = async () => {
value: m.id,
}));
};
getClustersList();
/**
* SIP本地地址
*/
@ -631,7 +631,7 @@ const getAllList = async () => {
}));
setPorts();
};
getAllList();
const handleTransportChange = () => {
formData.value.host = undefined;
@ -671,9 +671,6 @@ const getDetail = async () => {
// console.log('formData.value: ', formData.value);
};
onMounted(() => {
getDetail();
});
const regDomain = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.|$)){4}$/
// /[j-zA-Z0-9][-j-zA-Z0-9]{0,62}(\.[j-zA-Z0-9][-j-zA-Z0-9]{0,62})+\.?/;
@ -778,6 +775,12 @@ const handleSubmit = () => {
console.log('err: ', err);
});
};
onMounted(async() => {
await getDetail();
getClustersList();
getAllList();
});
</script>
<style lang="less" scoped>

View File

@ -21,9 +21,9 @@
</div>
<div class="top-card-footer">
<template v-for="(item, index) in footer" :key="index">
<span v-if="!item.status">{{ item.title }}</span>
<span v-if="!item?.status">{{ item?.title }}</span>
<j-badge v-else :text="item.title" :status="item.status" />
<div class="footer-item-value">{{ item.value }}</div>
<div class="footer-item-value">{{ item?.value }}</div>
</template>
</div>
</div>

View File

@ -16,7 +16,7 @@
.media-live-video {
position: relative;
flex: 1;
min-height: 490px;
.media-tool {
position: absolute;
top: 4px;

View File

@ -5,7 +5,7 @@
<div class="actions-item" v-if="item.executor === 'alarm'">
<div class="item-options-warp">
<div class="item-options-type">
<img
<!-- <img
style="width: 18px"
:src="
iconMap.get(
@ -14,7 +14,8 @@
: item.executor,
)
"
/>
/> -->
<AIcon :type="iconMap.get(item.executor === 'alarm' ? item.alarm.mode : item.executor)"/>
</div>
<div class="item-options-content">
<template v-if="item.executor === 'alarm'">
@ -75,6 +76,7 @@ const props = defineProps({
background-color: #f0f0f0;
border-radius: 6px 0 0 6px;
cursor: pointer;
font-size: 22px;
}
.item-options-content {

View File

@ -257,11 +257,12 @@ const columns = [
},
{
title: '关联场景联动',
dataIndex: 'scene',
dataIndex: 'id',
hideInTable:true,
key: 'scene',
key: 'id',
search: {
type: 'select',
termOptions: ['in'],
options: async () => {
const allData = await queryList({
paging: false,
@ -355,6 +356,14 @@ const map = {
other: '其他',
};
const handleSearch = (e: any) => {
e.terms.map((i:any)=>{
i.terms.forEach((item:any)=>{
if(item.column === 'id'){
item.termType = 'rule-bind-alarm'
}
})
})
console.log(e,'e')
params.value = e;
};

View File

@ -287,7 +287,7 @@
.clientId
"
placeholder="请输入appId"
:disabled="!!form.data.id"
:disabled="!!form.data.id && !!form.data.apiClient.authConfig.oauth2.clientId"
/>
</j-form-item>
<j-form-item

View File

@ -241,11 +241,13 @@
</j-form-item>
<j-form-item label="权限">
<PermissChoose
v-if="showPermissionChoose"
:first-width="3"
max-height="350px"
v-model:value="form.data.permissions"
:key="form.data.id || ''"
/>
<a-spin v-else/>
</j-form-item>
</j-form>
</div>
@ -291,6 +293,7 @@ const permission = 'system/Menu';
//
const route = useRoute();
const router = useRouter();
const showPermissionChoose = ref(false)
const routeParams = {
id: route.params.id === ':id' ? undefined : (route.params.id as string),
...route.query,
@ -334,6 +337,7 @@ const form = reactive({
resp.result?.accessSupport?.value || 'unsupported',
};
form.sourceCode = resp.result.code;
showPermissionChoose.value = true
});
if (isNoCommunity) {

View File

@ -26,7 +26,7 @@
<h5>请求示例</h5>
<JsonViewer :value="requestCard.codeText" copyable />
</div>
<div class="api-card">
<div class="api-card" v-if="requestCard.tableData.length">
<h5>请求参数</h5>
<div class="content">
<j-pro-table
@ -91,7 +91,7 @@ import 'vue3-json-viewer/dist/index.css';
import type { apiDetailsType } from '../typing';
import InputCard from './InputCard.vue';
import { PropType } from 'vue';
import { findData, getCodeText } from '../utils';
import { findData, getCodeText, dealNoRef } from '../utils';
type cardType = {
columns: object[];
@ -101,7 +101,6 @@ type cardType = {
getData: Function;
};
const props = defineProps({
selectApi: {
type: Object as PropType<apiDetailsType>,
@ -114,7 +113,6 @@ const props = defineProps({
});
const { selectApi } = toRefs(props);
const requestCard = reactive<cardType>({
columns: [
{
@ -153,31 +151,34 @@ const requestCard = reactive<cardType>({
const schema =
props.selectApi.requestBody.content['application/json'].schema;
const _ref = schema.$ref || schema?.items?.$ref;
if (!_ref) return; // schemaJava
const schemaName = _ref?.split('/').pop();
const type = schema.type || '';
const tableData = findData(props.schemas, schemaName);
requestCard.codeText =
type === 'array'
? [getCodeText(props.schemas, tableData, 3)]
: getCodeText(props.schemas, tableData, 3);
requestCard.tableData = [
{
name: schemaName[0].toLowerCase() + schemaName.substring(1),
description: schemaName,
in: 'body',
required: true,
schema: { type: type || schemaName },
children: tableData.map((item) => ({
name: item.paramsName,
description: item.desc,
required: false,
schema: { type: item.paramsType },
})),
},
];
// schemaJava
if (!_ref) {
const type = schema.type || '';
requestCard.codeText = dealNoRef(type, schema);
} else {
const schemaName = _ref?.split('/').pop();
const type = schema.type || '';
const tableData = findData(props.schemas, schemaName);
requestCard.codeText =
type === 'array'
? [getCodeText(props.schemas, tableData, 3)]
: getCodeText(props.schemas, tableData, 3);
requestCard.tableData = [
{
name: schemaName[0].toLowerCase() + schemaName.substring(1),
description: schemaName,
in: 'body',
required: true,
schema: { type: type || schemaName },
children: tableData.map((item) => ({
name: item.paramsName,
description: item.desc,
required: false,
schema: { type: item.paramsType },
})),
},
];
}
},
});
const responseStatusCard = reactive<cardType>({

View File

@ -108,7 +108,7 @@
</j-button>
</div>
<j-monaco-editor
v-if=" method !=='get' && method !=='patch'"
v-if="showRequestBody"
ref="editorRef"
language="json"
style="height: 100% ; min-height: 200px;"
@ -144,6 +144,7 @@ const responsesContent = ref({});
const editorRef = ref();
const formRef = ref<FormInstance>();
const method = ref()
const showRequestBody = ref(!!props.selectApi?.requestBody)
const requestBody = reactive({
tableColumns: [
{

View File

@ -7,7 +7,7 @@ import { schemaObjType } from "./typing";
* @param schemaName
*/
export function findData(schemas: object, schemaName: string) {
const basicType = ['string', 'integer', 'boolean'];
const basicType = ['string', 'integer', 'boolean','number'];
if (!schemaName || !schemas[schemaName]) {
return [];
@ -62,6 +62,9 @@ export function getCodeText(
case 'object':
result[item.paramsName] = '';
break;
case 'number':
result[item.paramsName] = 0;
break;
default: {
const properties = schemas[item.paramsType]?.properties as object || {};
const newArr = Object.entries(properties).map(
@ -87,3 +90,32 @@ export function getCodeText(
return result;
}
/**
* $ref的情况
*/
export function dealNoRef(type:string,schema?:any):any{
let result = undefined;
switch (type) {
case 'string':
result = '';
break;
case 'integer':
result = 0;
break;
case 'boolean':
result = true;
break;
case 'array':
const itemType = schema?.items?.type
const item = dealNoRef(itemType)
result = [item];
break;
case 'number':
result = 0;
break;
default:
return;
}
return result
}

View File

@ -2293,6 +2293,11 @@ dayjs@^1.10.5:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e"
integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==
dayjs@^1.11.12:
version "1.11.12"
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d"
integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"