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

This commit is contained in:
JiangQiming 2023-03-15 09:50:28 +08:00
commit 81590a0af3
35 changed files with 641 additions and 478 deletions

View File

@ -37,16 +37,15 @@
:name="['configuration', 'host']" :name="['configuration', 'host']"
:rules="FormValidate.host" :rules="FormValidate.host"
> >
<div class="form-label"> <template #label>
Modbus主机IP Modbus主机IP
<span class="form-label-required">*</span> <j-tooltip title="支持ipv4、ipv6、域名">
<j-tooltip> <AIcon
<template #title> type="QuestionCircleOutlined"
<p>支持ipv4ipv6域名</p> style="margin-left: 2px"
</template> />
<AIcon type="QuestionCircleOutlined" />
</j-tooltip> </j-tooltip>
</div> </template>
<j-input <j-input
placeholder="请输入Modbus主机IP" placeholder="请输入Modbus主机IP"
v-model:value="formData.configuration.host" v-model:value="formData.configuration.host"
@ -62,7 +61,7 @@
style="width: 100%" style="width: 100%"
placeholder="请输入端口" placeholder="请输入端口"
v-model:value="formData.configuration.port" v-model:value="formData.configuration.port"
:min="1" :min="0"
:max="65535" :max="65535"
/> />
</j-form-item> </j-form-item>
@ -80,7 +79,7 @@
<j-form-item <j-form-item
v-if="formData.provider === 'OPC_UA'" v-if="formData.provider === 'OPC_UA'"
label="安全策略" label="安全策略"
:name="['configuration.securityPolicy']" :name="['configuration', 'securityPolicy']"
:rules="FormValidate.securityPolicy" :rules="FormValidate.securityPolicy"
> >
<j-select <j-select
@ -96,7 +95,7 @@
<j-form-item <j-form-item
v-if="formData.provider === 'OPC_UA'" v-if="formData.provider === 'OPC_UA'"
label="安全模式" label="安全模式"
:name="['configuration.securityMode']" :name="['configuration', 'securityMode']"
:rules="FormValidate.securityMode" :rules="FormValidate.securityMode"
> >
<j-select <j-select
@ -115,7 +114,7 @@
formData.configuration.securityMode === 'Sign' formData.configuration.securityMode === 'Sign'
" "
label="证书" label="证书"
:name="['configuration.certificate']" :name="['configuration', 'certificate']"
:rules="FormValidate.certificate" :rules="FormValidate.certificate"
> >
<j-select <j-select
@ -131,20 +130,20 @@
<j-form-item <j-form-item
v-if="formData.provider === 'OPC_UA'" v-if="formData.provider === 'OPC_UA'"
label="权限认证" label="权限认证"
:name="['configuration.authType']" :name="['configuration', 'authType']"
:rules="FormValidate.authType" :rules="FormValidate.authType"
> >
<RadioCard <j-card-select
layout="horizontal" :showImage="false"
:checkStyle="true" v-model:value="formData.configuration.authType"
:options="Options['auth-types']" :options="Options['auth-types']"
v-model="formData.configuration.authType" @change="changeAuthType"
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
v-if="formData.configuration.authType === 'username'" v-if="formData.configuration.authType === 'username'"
label="用户名" label="用户名"
:name="['configuration.username']" :name="['configuration', 'username']"
:rules="FormValidate.username" :rules="FormValidate.username"
> >
<j-input <j-input
@ -153,9 +152,12 @@
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
v-if="formData.configuration.authType === 'username'" v-if="
formData.configuration.authType === 'username' ||
formData.configuration.authType === ['username']
"
label="密码" label="密码"
:name="['configuration.password']" :name="['configuration', 'password']"
:rules="FormValidate.password" :rules="FormValidate.password"
> >
<j-input-password <j-input-password
@ -226,11 +228,9 @@ const handleOk = async () => {
const params = await formRef.value?.validate(); const params = await formRef.value?.validate();
loading.value = true; loading.value = true;
const response = !id const response = !id
? await save(params) ? await save(params).catch(() => {})
: await update(id, { ...props.data, ...params }); : await update(id, { ...props.data, ...params }).catch(() => {});
if (response.status === 200) { emit('change', response?.status === 200);
emit('change', true);
}
loading.value = false; loading.value = false;
formRef.value?.resetFields(); formRef.value?.resetFields();
}; };
@ -240,29 +240,33 @@ const handleCancel = () => {
formRef.value?.resetFields(); formRef.value?.resetFields();
}; };
const changeAuthType = (value: Array<string>) => {
formData.value.configuration.authType = value[0];
};
const filterOption = (input: string, option: any) => { const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0; return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
}; };
const getOptionsList = async () => { const getOptionsList = async () => {
for (let key in Options.value) { for (let key in Options.value) {
const res = await queryOptionsList(key); const res: any = await queryOptionsList(key);
Options.value[key] = res.result.map((item) => ({ Options.value[key] = res.result.map((item: any) => ({
label: item?.text || item, label: item?.text || item,
value: item?.value || item, value: item?.value || item,
})); }));
} }
}; };
const getCertificateList = async () => { const getCertificateList = async () => {
const res = await queryCertificateList(); const res: any = await queryCertificateList();
certificateList.value = res.result.map((item) => ({ certificateList.value = res.result.map((item: any) => ({
value: item.id, value: item.id,
label: item.name, label: item.name,
})); }));
}; };
const getProvidersList = async () => { const getProvidersList = async () => {
const resp = await getProviders(); const resp: any = await getProviders();
if (resp.status === 200) { if (resp.status === 200) {
const list = [ const list = [
{ label: 'OPC UA', value: 'OPC_UA' }, { label: 'OPC UA', value: 'OPC_UA' },
@ -273,7 +277,9 @@ const getProvidersList = async () => {
(item: any) => item.id === 'modbus-tcp' || item.id === 'opc-ua', (item: any) => item.id === 'modbus-tcp' || item.id === 'opc-ua',
) )
.map((it: any) => (it?.id === 'opc-ua' ? 'OPC_UA' : 'MODBUS_TCP')); .map((it: any) => (it?.id === 'opc-ua' ? 'OPC_UA' : 'MODBUS_TCP'));
const providers = list.filter((item: any) => arr.includes(item.value)); const providers: any = list.filter((item: any) =>
arr.includes(item.value),
);
providersList.value = providers; providersList.value = providers;
if (arr.includes('OPC_UA')) { if (arr.includes('OPC_UA')) {
getOptionsList(); getOptionsList();
@ -286,30 +292,10 @@ getCertificateList();
watch( watch(
() => props.data, () => props.data,
(value) => { (value) => {
if (value.id) formData.value = value; if (value.id) formData.value = value as FormDataType;
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },
); );
</script> </script>
<style lang="less" scoped> <style lang="less" scoped></style>
.form {
.form-radio-button {
width: 148px;
height: 80px;
padding: 0;
img {
width: 100%;
height: 100%;
}
}
}
.form-label {
height: 30px;
padding-bottom: 8px;
.form-label-required {
color: red;
margin: 0 4px 0 -2px;
}
}
</style>

View File

@ -1,5 +1,6 @@
import { validateField } from '@/api/data-collect/channel'; import { validateField } from '@/api/data-collect/channel';
import { FormDataType } from './type.d'; import { FormDataType } from './type.d';
import type { Rule } from 'ant-design-vue/lib/form';
export const FormState: FormDataType = { export const FormState: FormDataType = {
name: '', name: '',
@ -44,7 +45,7 @@ export const TiTlePermissionButtonStyle = {
overflow: 'hidden', overflow: 'hidden',
'text-overflow': 'ellipsis', 'text-overflow': 'ellipsis',
'white-space': 'nowrap', 'white-space': 'nowrap',
width: 'calc(100%-100px)', width: 'calc(100%-150px)',
// width: '60%', // width: '60%',
}; };
@ -61,10 +62,8 @@ export const regDomain = new RegExp(
); );
export const checkEndpoint = (_rule: Rule, value: string): Promise<any> => export const checkEndpoint = (_rule: Rule, value: string): Promise<any> =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
if (value) {
const res = await validateField(value); const res = await validateField(value);
return res.result.passed ? resolve('') : reject(res.result.reason); return res.result.passed ? resolve('') : reject(res.result.reason);
}
}); });
export const FormValidate = { export const FormValidate = {
name: [ name: [
@ -89,7 +88,7 @@ export const FormValidate = {
}, },
{ {
pattern: regOnlyNumber, pattern: regOnlyNumber,
message: '请输入1-65535之间的正整数', message: '请输入0-65535之间的正整数',
}, },
], ],
@ -100,7 +99,7 @@ export const FormValidate = {
}, },
{ {
validator: checkEndpoint, validator: checkEndpoint,
trigger: 'blur', // trigger: 'blur',
}, },
], ],
@ -139,3 +138,70 @@ export const FormValidate = {
description: [{ max: 200, message: '最多可输入200个字符' }], description: [{ max: 200, message: '最多可输入200个字符' }],
}; };
export const columns = [
{
title: '通道名称',
dataIndex: 'name',
key: 'name',
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '通讯协议',
dataIndex: 'provider',
key: 'provider',
ellipsis: true,
search: {
type: 'select',
options: [
{ label: 'OPC_UA', value: 'OPC_UA' },
{ label: 'MODBUS_TCP', value: 'MODBUS_TCP' },
],
},
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
ellipsis: true,
scopedSlots: true,
search: {
type: 'select',
options: [
{ label: '正常', value: 'enabled' },
{ label: '禁用', value: 'disabled' },
],
},
},
{
title: '运行状态',
dataIndex: 'runningState',
key: 'runningState',
ellipsis: true,
scopedSlots: true,
search: {
type: 'select',
options: [
{ label: '运行中', value: 'running' },
{ label: '部分错误', value: 'partialError' },
{ label: '错误', value: 'failed' },
],
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
ellipsis: true,
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 200,
scopedSlots: true,
},
];

View File

@ -1,7 +1,11 @@
<template> <template>
<page-container> <page-container>
<div> <div>
<Search :columns="columns" target="search" @search="handleSearch" /> <pro-search
:columns="columns"
target="search"
@search="handleSearch"
/>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
@ -134,7 +138,6 @@ import _ from 'lodash';
const menuStory = useMenuStore(); const menuStory = useMenuStore();
const tableRef = ref<Record<string, any>>({}); const tableRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({}); const params = ref<Record<string, any>>({});
const options = ref([]);
const visible = ref(false); const visible = ref(false);
const current = ref({}); const current = ref({});

View File

@ -37,6 +37,7 @@
allowClear allowClear
show-search show-search
:filter-option="filterOption" :filter-option="filterOption"
@change="changeFunction"
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
@ -69,6 +70,7 @@
v-model:value="formData.configuration.parameter.quantity" v-model:value="formData.configuration.parameter.quantity"
:min="1" :min="1"
:max="255" :max="255"
@blur="changeQuantity"
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
@ -112,16 +114,9 @@
/> />
</j-form-item> </j-form-item>
<j-form-item label="访问类型" name="accessModes"> <j-form-item label="访问类型" name="accessModes">
<!-- <RadioCard <j-card-select
layout="horizontal" multiple
:checkStyle="true" :showImage="false"
:options="[
{ label: '读', value: 'read' },
{ label: '写', value: 'write' },
]"
v-model="formData.accessModes"
/> -->
<j-checkbox-group
v-model:value="formData.accessModes" v-model:value="formData.accessModes"
:options="[ :options="[
{ label: '读', value: 'read' }, { label: '读', value: 'read' },
@ -129,7 +124,6 @@
]" ]"
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
:name="['nspwc']" :name="['nspwc']"
v-if=" v-if="
@ -140,7 +134,6 @@
<span style="margin-right: 10px">非标准协议写入配置</span> <span style="margin-right: 10px">非标准协议写入配置</span>
<j-switch v-model:checked="formData.nspwc" /> <j-switch v-model:checked="formData.nspwc" />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
v-if=" v-if="
!!formData.nspwc && !!formData.nspwc &&
@ -151,14 +144,16 @@
:name="['configuration', 'parameter', 'writeByteCount']" :name="['configuration', 'parameter', 'writeByteCount']"
:rules="ModBusRules.writeByteCount" :rules="ModBusRules.writeByteCount"
> >
<RadioCard <j-card-select
layout="horizontal" :showImage="false"
:checkStyle="true" v-model:value="
formData.configuration.parameter.writeByteCount
"
:options="[ :options="[
{ label: '是', value: true }, { label: '是', value: true },
{ label: '否', value: false }, { label: '否', value: false },
]" ]"
v-model="formData.configuration.parameter.writeByteCount" @change="changeWriteByteCount"
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
@ -187,11 +182,10 @@
}, },
]" ]"
> >
<j-input-number <j-input
style="width: 100%" style="width: 100%"
placeholder="请输入采集频率" placeholder="请输入采集频率"
v-model:value="formData.configuration.interval" v-model:value="formData.configuration.interval"
:min="1"
addon-after="ms" addon-after="ms"
/> />
</j-form-item> </j-form-item>
@ -240,8 +234,8 @@ import {
} from '@/api/data-collect/collector'; } from '@/api/data-collect/collector';
import { ModBusRules, checkProviderData } from '../../data.ts'; import { ModBusRules, checkProviderData } from '../../data.ts';
import type { FormInstance } from 'ant-design-vue'; import type { FormInstance } from 'ant-design-vue';
import { Rule } from 'ant-design-vue/lib/form'; import type { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep, isArray } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
const props = defineProps({ const props = defineProps({
data: { data: {
@ -285,9 +279,8 @@ const formData = ref({
description: '', description: '',
}); });
const onSubmit = async () => { const handleOk = async () => {
const data = await formRef.value?.validate(); const data = await formRef.value?.validate();
delete data?.nspwc; delete data?.nspwc;
const { codec } = data?.configuration; const { codec } = data?.configuration;
@ -309,21 +302,29 @@ const onSubmit = async () => {
loading.value = true; loading.value = true;
const response = !id const response = !id
? await savePointBatch(params) ? await savePointBatch(params).catch(() => {})
: await updatePoint(id, { ...props.data, ...params }); : await updatePoint(id, { ...props.data, ...params }).catch(() => {});
if (response.status === 200) { emit('change', response?.status === 200);
emit('change', true);
}
loading.value = false; loading.value = false;
}; };
const handleOk = () => {
onSubmit();
};
const handleCancel = () => { const handleCancel = () => {
emit('change', false); emit('change', false);
}; };
const changeQuantity = () => {
if (formData.value.configuration.function === 'HoldingRegisters') {
formRef.value?.validate();
}
};
const changeWriteByteCount = (value: Array<string>) => {
formData.value.configuration.parameter.writeByteCount = value[0];
};
const changeFunction = (value: string) => {
formData.value.accessModes =
value === 'DiscreteInputs' ? ['read'] : ['read', 'write'];
};
const checkLength = (_rule: Rule, value: string): Promise<any> => const checkLength = (_rule: Rule, value: string): Promise<any> =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
if (value) { if (value) {
@ -347,10 +348,10 @@ const checkPointKey = (_rule: Rule, value: string): Promise<any> =>
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
if (value) { if (value) {
if (Number(oldPointKey) === Number(value)) return resolve(''); if (Number(oldPointKey) === Number(value)) return resolve('');
const res = await _validateField(collectorId, { const res: any = await _validateField(collectorId, {
pointKey: value, pointKey: value,
}); });
return res.result.passed ? resolve('') : reject(res.result.reason); return res.result?.passed ? resolve('') : reject(res.result.reason);
} }
}); });
@ -359,10 +360,10 @@ const filterOption = (input: string, option: any) => {
}; };
const getProviderList = async () => { const getProviderList = async () => {
const res = await queryCodecProvider(); const res: any = await queryCodecProvider();
providerList.value = res.result providerList.value = res.result
.filter((i) => i.id !== 'property') .filter((i: any) => i.id !== 'property')
.map((item) => ({ .map((item: any) => ({
value: item.id, value: item.id,
label: item.name, label: item.name,
})); }));
@ -386,11 +387,13 @@ watch(
formData.value = _value; formData.value = _value;
if (!!_value.accessModes[0]?.value) { if (!!_value.accessModes[0]?.value) {
formData.value.accessModes = value.accessModes.map( formData.value.accessModes = value.accessModes.map(
(i) => i.value, (i: any) => i.value,
); );
} }
if (!!_value.features[0]?.value) { if (!!_value.features[0]?.value) {
formData.value.features = value.features.map((i) => i.value); formData.value.features = value.features.map(
(i: any) => i.value,
);
} }
formData.value.nspwc = !!writeByteCount || !!byteCount; formData.value.nspwc = !!writeByteCount || !!byteCount;
} }
@ -399,22 +402,4 @@ watch(
); );
</script> </script>
<style lang="less" scoped> <style lang="less" scoped></style>
.form {
.form-radio-button {
width: 148px;
height: 80px;
padding: 0;
img {
width: 100%;
height: 100%;
}
}
.form-upload-button {
margin-top: 10px;
}
.form-submit {
background-color: @primary-color !important;
}
}
</style>

View File

@ -34,7 +34,9 @@
</j-form-item> </j-form-item>
<j-form-item label="访问类型" name="accessModes"> <j-form-item label="访问类型" name="accessModes">
<j-checkbox-group <j-card-select
multiple
:showImage="false"
v-model:value="formData.accessModes" v-model:value="formData.accessModes"
:options="[ :options="[
{ label: '读', value: 'read' }, { label: '读', value: 'read' },
@ -54,15 +56,13 @@
}, },
]" ]"
> >
<j-input-number <j-input
style="width: 100%" style="width: 100%"
placeholder="请输入采集频率" placeholder="请输入采集频率"
v-model:value="formData.configuration.interval" v-model:value="formData.configuration.interval"
:min="1"
addon-after="ms" addon-after="ms"
/> />
</j-form-item> </j-form-item>
<a-form-item label="" :name="['features']"> <a-form-item label="" :name="['features']">
<a-checkbox-group v-model:value="formData.features"> <a-checkbox-group v-model:value="formData.features">
<a-checkbox value="changedOnly" name="type" <a-checkbox value="changedOnly" name="type"
@ -70,7 +70,6 @@
> >
</a-checkbox-group> </a-checkbox-group>
</a-form-item> </a-form-item>
<j-form-item label="说明" :name="['description']"> <j-form-item label="说明" :name="['description']">
<j-textarea <j-textarea
placeholder="请输入说明" placeholder="请输入说明"
@ -104,7 +103,7 @@ import {
updatePoint, updatePoint,
_validateField, _validateField,
} from '@/api/data-collect/collector'; } from '@/api/data-collect/collector';
import { OPCUARules, checkProviderData } from '../../data.ts'; import { OPCUARules } from '../../data.ts';
import type { FormInstance } from 'ant-design-vue'; import type { FormInstance } from 'ant-design-vue';
import { Rule } from 'ant-design-vue/lib/form'; import { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
@ -118,7 +117,6 @@ const props = defineProps({
const emit = defineEmits(['change']); const emit = defineEmits(['change']);
const loading = ref(false); const loading = ref(false);
const providerList = ref([]);
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const id = props.data.id; const id = props.data.id;
@ -136,9 +134,8 @@ const formData = ref({
description: '', description: '',
}); });
const onSubmit = async () => { const handleOk = async () => {
const data = await formRef.value?.validate(); const data = await formRef.value?.validate();
const params = { const params = {
...props.data, ...props.data,
...data, ...data,
@ -148,17 +145,12 @@ const onSubmit = async () => {
loading.value = true; loading.value = true;
const response = !id const response = !id
? await savePoint(params) ? await savePoint(params).catch(() => {})
: await updatePoint(id, { ...props.data, ...params }); : await updatePoint(id, { ...props.data, ...params }).catch(() => {});
if (response.status === 200) { emit('change', response?.status === 200);
emit('change', true);
}
loading.value = false; loading.value = false;
}; };
const handleOk = () => {
onSubmit();
};
const handleCancel = () => { const handleCancel = () => {
emit('change', false); emit('change', false);
}; };
@ -180,15 +172,17 @@ watch(
() => props.data, () => props.data,
(value) => { (value) => {
if (value.id && value.provider === 'OPC_UA') { if (value.id && value.provider === 'OPC_UA') {
const _value = cloneDeep(value); const _value: any = cloneDeep(value);
formData.value = _value; formData.value = _value;
if (!!_value.accessModes[0]?.value) { if (!!_value.accessModes[0]?.value) {
formData.value.accessModes = value.accessModes.map( formData.value.accessModes = value.accessModes.map(
(i) => i.value, (i: any) => i.value,
); );
} }
if (!!_value.features[0]?.value) { if (!!_value.features[0]?.value) {
formData.value.features = value.features.map((i) => i.value); formData.value.features = value.features.map(
(i: any) => i.value,
);
} }
} }
}, },

View File

@ -1,9 +1,9 @@
<template> <template>
<j-form style="width: 80%" ref="formTableRef" :model="modelRef"> <j-form class="table" ref="formTableRef" :model="modelRef">
<j-table <j-table
:dataSource="modelRef.dataSource" :dataSource="modelRef.dataSource"
:columns="FormTableColumns" :columns="FormTableColumns"
:scroll="{ x: 1100, y: 500 }" :scroll="{ x: 1000, y: 550 }"
> >
<template #bodyCell="{ column: { dataIndex }, record, index }"> <template #bodyCell="{ column: { dataIndex }, record, index }">
<template v-if="dataIndex === 'name'"> <template v-if="dataIndex === 'name'">
@ -25,8 +25,11 @@
</template> </template>
<template v-if="dataIndex === 'id'"> <template v-if="dataIndex === 'id'">
<a-form-item :name="['dataSource', index, 'id']"> <a-form-item :name="['dataSource', index, 'id']">
<j-input v-model:value="record[dataIndex]" disabled> <j-input
</j-input> v-model:value="record[dataIndex]"
disabled
:bordered="false"
></j-input>
</a-form-item> </a-form-item>
</template> </template>
@ -78,8 +81,12 @@
]" ]"
:rules="[ :rules="[
{ {
required: true, pattern: regOnlyNumber,
message: '请输入', message: '请输入0或者正整数',
},
{
validator: checkLength,
trigger: 'change',
}, },
]" ]"
> >
@ -98,7 +105,7 @@
@blur="changeValue(index, dataIndex)" @blur="changeValue(index, dataIndex)"
></j-input> ></j-input>
<j-checkbox <j-checkbox
style="margin-left: 5px" style="margin-left: 5px; margin-top: 5px"
v-show="index !== 0" v-show="index !== 0"
v-model:checked=" v-model:checked="
record.configuration[dataIndex].check record.configuration[dataIndex].check
@ -151,14 +158,14 @@
</template> </template>
<template v-if="dataIndex === 'action'"> <template v-if="dataIndex === 'action'">
<a-tooltip title="删除"> <j-tooltip title="删除">
<a-popconfirm <j-popconfirm
title="确认删除" title="确认删除"
@confirm="clickDelete(record.id)" @confirm="clickDelete(record.id)"
> >
<AIcon type="DeleteOutlined" /> <a><AIcon type="DeleteOutlined" /></a>
</a-popconfirm> </j-popconfirm>
</a-tooltip> </j-tooltip>
</template> </template>
</template> </template>
</j-table> </j-table>
@ -166,7 +173,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { FormTableColumns } from '../../data'; import { FormTableColumns, regOnlyNumber } from '../../data';
import { Rule } from 'ant-design-vue/lib/form';
const props = defineProps({ const props = defineProps({
data: { data: {
type: Array, type: Array,
@ -177,10 +186,19 @@ const emits = defineEmits(['change']);
const formTableRef = ref(); const formTableRef = ref();
const defaultType = ['accessModes', 'interval', 'features']; const defaultType = ['accessModes', 'interval', 'features'];
const modelRef = reactive({ const modelRef: any = reactive({
dataSource: [], dataSource: [],
}); });
const checkLength = (_rule: Rule, value: string): Promise<any> =>
new Promise(async (resolve, reject) => {
if (value) {
return String(value).length > 64
? reject('最多可输入64个字符')
: resolve('');
}
});
const filterOption = (input: string, option: any) => { const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0; return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
}; };
@ -268,6 +286,7 @@ watch(
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.table {}
.form-item { .form-item {
display: flex; display: flex;
} }

View File

@ -6,20 +6,14 @@
</div> </div>
<j-spin :spinning="spinning"> <j-spin :spinning="spinning">
<a-tree <a-tree
v-model:checkedKeys="checkedKeys" v-if="!!treeData"
:tree-data="treeData"
default-expand-all
checkable
@check="onCheck"
:height="600"
>
<!-- <a-tree
:load-data="onLoadData" :load-data="onLoadData"
:tree-data="treeData" :tree-data="treeData"
v-model:checkedKeys="checkedKeys" v-model:checkedKeys="checkedKeys"
checkable checkable
@check="onCheck" @check="onCheck"
> --> :height="600"
>
<template #title="{ name, key }"> <template #title="{ name, key }">
<span <span
:class="[ :class="[
@ -32,6 +26,7 @@
</span> </span>
</template> </template>
</a-tree> </a-tree>
<j-empty v-else />
</j-spin> </j-spin>
</div> </div>
</template> </template>
@ -56,7 +51,6 @@ const props = defineProps({
}); });
const emits = defineEmits(['change']); const emits = defineEmits(['change']);
// const channelId = '1610517801347788800'; //
const channelId = props.data?.channelId; const channelId = props.data?.channelId;
const checkedKeys = ref<string[]>([]); const checkedKeys = ref<string[]>([]);
@ -73,12 +67,12 @@ const onLoadData = (node: any) =>
resolve(); resolve();
return; return;
} }
const resp = await scanOpcUAList({ const resp: any = await scanOpcUAList({
id: channelId, id: channelId,
nodeId: node.key, nodeId: node.key,
}); });
if (resp.status === 200) { if (resp.status === 200) {
const list = resp.result.map((item: any) => { const list: any = resp.result.map((item: any) => {
return { return {
...item, ...item,
key: item.id, key: item.id,
@ -97,13 +91,13 @@ const onLoadData = (node: any) =>
resolve(); resolve();
}); });
const handleData = (arr: any[]): any[] => { const handleData = (arr: any): any[] => {
const data = arr.filter((item) => { const data = arr.filter((item: any) => {
return ( return (
(isSelected && !selectKeys.value.includes(item.id)) || !isSelected (isSelected && !selectKeys.value.includes(item.id)) || !isSelected
); );
}); });
return data.map((item) => { return data.map((item: any) => {
if (item.children && item.children?.length) { if (item.children && item.children?.length) {
return { return {
...item, ...item,
@ -115,7 +109,7 @@ const handleData = (arr: any[]): any[] => {
}); });
}; };
const onCheck = (checkedKeys, info) => { const onCheck = (checkedKeys: any, info: any) => {
const one: any = { ...info.node }; const one: any = { ...info.node };
const list: any = []; const list: any = [];
const last: any = list.length ? list[list.length - 1] : undefined; const last: any = list.length ? list[list.length - 1] : undefined;
@ -149,8 +143,8 @@ const onCheck = (checkedKeys, info) => {
emits('change', item, info.checked); emits('change', item, info.checked);
}; };
const updateTreeData = (list: any[], key: string, children: any[]): any[] => { const updateTreeData = (list: any, key: string, children: any[]): any[] => {
const arr = list.map((node) => { const arr = list.map((node: any) => {
if (node.key === key) { if (node.key === key) {
return { return {
...node, ...node,
@ -170,44 +164,41 @@ const updateTreeData = (list: any[], key: string, children: any[]): any[] => {
const getPoint = async () => { const getPoint = async () => {
spinning.value = true; spinning.value = true;
const res = await queryPointNoPaging(); const res: any = await queryPointNoPaging();
if (res.status === 200) { if (res.status === 200) {
selectKeys.value = res.result.map((item: any) => item.pointKey); selectKeys.value = res.result.map((item: any) => item.pointKey);
} }
getScanOpcUAList();
spinning.value = false; spinning.value = false;
}; };
getPoint(); getPoint();
const getScanOpcUAList = async () => { const getScanOpcUAList = async () => {
const res = await scanOpcUAList({ id: channelId }); spinning.value = true;
const res: any = await scanOpcUAList({ id: channelId });
treeAllData.value = res.result.map((item: any) => ({ treeAllData.value = res.result.map((item: any) => ({
...item, ...item,
key: item.id, key: item.id,
title: item.name, title: item.name,
disabled: item?.folder || false, disabled: item?.folder || false,
})); }));
spinning.value = false;
}; };
getScanOpcUAList(); // getScanOpcUAList();
watch( watch(
() => isSelected.value, () => isSelected.value,
(value) => { (value) => {
if (value) { treeData.value = value
treeData.value = handleData(treeAllData.value); ? handleData(treeAllData.value)
} else { : treeAllData.value;
treeData.value = treeAllData.value;
}
}, },
{ deep: true }, { deep: true },
); );
watch( watch(
() => treeAllData.value, () => treeAllData.value,
(value) => { (value) => {
if (isSelected.value) { treeData.value = isSelected.value ? handleData(value) : value;
treeData.value = handleData(value);
} else {
treeData.value = value;
}
}, },
{ deep: true }, { deep: true },
); );

View File

@ -33,7 +33,6 @@
import type { FormInstance } from 'ant-design-vue'; import type { FormInstance } from 'ant-design-vue';
import { savePointBatch } from '@/api/data-collect/collector'; import { savePointBatch } from '@/api/data-collect/collector';
import { Rule } from 'ant-design-vue/lib/form'; import { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash';
import Table from './Table.vue'; import Table from './Table.vue';
import Tree from './Tree.vue'; import Tree from './Tree.vue';
@ -55,8 +54,8 @@ const tableDataMap = new Map();
const unSelectKeys = ref(); const unSelectKeys = ref();
const handleOk = async () => { const handleOk = async () => {
loading.value = true; const data: any = await formTableRef.value?.validate().catch(() => {});
const data = await formTableRef.value?.validate(); if (!data) return;
const list = data.map((item: any) => { const list = data.map((item: any) => {
return { return {
name: item.name, name: item.name,
@ -71,11 +70,9 @@ const handleOk = async () => {
accessModes: item.accessModes?.value || [], accessModes: item.accessModes?.value || [],
}; };
}); });
console.log(1112, props.data, data, list); loading.value = true;
const resp = await savePointBatch([...list]); const resp = await savePointBatch([...list]).catch(() => {});
if (resp.status === 200) { emit('change', resp?.status === 200);
emit('change', true);
}
loading.value = false; loading.value = false;
}; };
const handleCancel = () => { const handleCancel = () => {

View File

@ -19,7 +19,9 @@
ref="formRef" ref="formRef"
> >
<j-form-item label="访问类型" name="accessModes"> <j-form-item label="访问类型" name="accessModes">
<j-checkbox-group <j-card-select
multiple
:showImage="false"
v-model:value="formData.accessModes" v-model:value="formData.accessModes"
:options="[ :options="[
{ label: '读', value: 'read' }, { label: '读', value: 'read' },
@ -28,7 +30,19 @@
]" ]"
/> />
</j-form-item> </j-form-item>
<j-form-item :name="['interval']"> <j-form-item
:name="['interval']"
:rules="[
{
pattern: regOnlyNumber,
message: '请输入0或者正整数',
},
{
validator: checkLength,
trigger: 'change',
},
]"
>
<template #label> <template #label>
<span> <span>
采集频率 采集频率
@ -40,11 +54,10 @@
</j-tooltip> </j-tooltip>
</span> </span>
</template> </template>
<j-input-number <j-input
style="width: 100%" style="width: 100%"
placeholder="请输入采集频率" placeholder="请输入采集频率"
v-model:value="formData.interval" v-model:value="formData.interval"
:min="1"
addon-after="ms" addon-after="ms"
/> />
</j-form-item> </j-form-item>
@ -77,6 +90,7 @@ import type { FormInstance } from 'ant-design-vue';
import { savePointBatch } from '@/api/data-collect/collector'; import { savePointBatch } from '@/api/data-collect/collector';
import { Rule } from 'ant-design-vue/lib/form'; import { Rule } from 'ant-design-vue/lib/form';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { regOnlyNumber } from '../../../data';
const props = defineProps({ const props = defineProps({
data: { data: {
@ -104,8 +118,8 @@ const checkLength = (_rule: Rule, value: string): Promise<any> =>
} }
}); });
const onSubmit = async () => { const handleOk = async () => {
const data = await formRef.value?.validate(); const data: any = await formRef.value?.validate();
const { accessModes, features, interval } = data; const { accessModes, features, interval } = data;
const ischange = const ischange =
accessModes.length !== 0 || features.length !== 0 || !!interval; accessModes.length !== 0 || features.length !== 0 || !!interval;
@ -123,19 +137,14 @@ const onSubmit = async () => {
} }
}); });
loading.value = true; loading.value = true;
const response = await savePointBatch(params); const response = await savePointBatch(params).catch(() => {});
if (response.status === 200) { emit('change', response?.status === 200);
emit('change', true);
}
loading.value = false; loading.value = false;
} else { } else {
emit('change', true); emit('change', true);
} }
}; };
const handleOk = () => {
onSubmit();
};
const handleCancel = () => { const handleCancel = () => {
emit('change', false); emit('change', false);
}; };

View File

@ -78,8 +78,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import BadgeStatus from '@/components/BadgeStatus/index.vue'; import type { ActionsType } from '@/components/Table/index';
import type { ActionsType } from '@/components/Table/index.vue';
import { PropType } from 'vue'; import { PropType } from 'vue';
type EmitProps = { type EmitProps = {
@ -146,19 +145,20 @@ const handleClick = () => {
width: 44px; width: 44px;
height: 44px; height: 44px;
color: #fff; color: #fff;
background-color: red;
background-color: #2f54eb; background-color: #2f54eb;
transform: rotate(-45deg); transform: rotate(-45deg);
> div { > div {
position: relative; position: relative;
height: 100%; height: 100%;
right: -2px;
bottom: -4px;
transform: rotate(45deg); transform: rotate(45deg);
> span { > span {
position: absolute; position: absolute;
top: 6px; // top: 6px;
left: 6px; // left: 6px;
font-size: 12px; font-size: 12px;
} }
} }

View File

@ -130,7 +130,7 @@ const loading = ref(false);
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const collectorId = props.data.collectorId; const collectorId = props.data.collectorId;
const pointId = props.data.id; const pointId: string = props.data.id;
const formData = ref({ const formData = ref({
value: '', value: '',
@ -140,23 +140,18 @@ const onChange = (value: Dayjs, dateString: string) => {
formData.value.value = dateString; formData.value.value = dateString;
}; };
const onSubmit = async () => { const handleOk = async () => {
const data = await formRef.value?.validate(); const data = await formRef.value?.validate();
const params = { const params: any = {
...data, ...data,
pointId, pointId,
}; };
loading.value = true; loading.value = true;
const response = await writePoint(collectorId, [params]); const response = await writePoint(collectorId, [params]).catch(() => {});
if (response.status === 200) { emit('change', response?.status === 200);
emit('change', true);
}
loading.value = false; loading.value = false;
}; };
const handleOk = () => {
onSubmit();
};
const handleCancel = () => { const handleCancel = () => {
emit('change', false); emit('change', false);
}; };

View File

@ -1,11 +1,6 @@
<template> <template>
<j-spin :spinning="spinning"> <j-spin :spinning="spinning">
<j-advanced-search <pro-search :columns="columns" target="search" @search="handleSearch" />
:columns="columns"
target="search"
@search="handleSearch"
/>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
model="CARD" model="CARD"
@ -77,6 +72,16 @@
</template> </template>
</j-dropdown> </j-dropdown>
</j-space> </j-space>
<div
v-if="data?.provider === 'OPC_UA'"
style="margin-top: 15px"
>
<j-checkbox
v-model:checked="checkAll"
@change="onCheckAllChange"
>全选</j-checkbox
>
</div>
</template> </template>
<template #card="slotProps"> <template #card="slotProps">
<PointCardBox <PointCardBox
@ -98,18 +103,14 @@
</template> </template>
<template #action> <template #action>
<div class="card-box-action"> <div class="card-box-action">
<a>
<j-popconfirm <j-popconfirm
title="确定删除?" title="确定删除?"
@confirm="handlDelete(slotProps.id)" @confirm="handlDelete(slotProps.id)"
> >
<AIcon type="DeleteOutlined" /> <a><AIcon type="DeleteOutlined" /></a>
</j-popconfirm> </j-popconfirm>
</a> <a @click="handlEdit(slotProps)"
<a ><AIcon type="FormOutlined"
><AIcon
@click="handlEdit(slotProps)"
type="FormOutlined"
/></a> /></a>
</div> </div>
</template> </template>
@ -125,16 +126,30 @@
<template #content> <template #content>
<div class="card-box-content"> <div class="card-box-content">
<div class="card-box-content-left"> <div class="card-box-content-left">
<span>--</span> <div class="card-box-content-left-1">
<div
class="ard-box-content-left-1-title"
v-if="propertyValue.has(slotProps.id)"
>
<j-ellipsis style="max-width: 150px">
{{
propertyValue.get(slotProps.id)
?.parseData[0] || 0
}}({{
propertyValue.get(slotProps.id)
?.dataType
}})
</j-ellipsis>
</div>
<span v-else>--</span>
<a <a
v-if=" v-if="
getAccessModes(slotProps).includes( getAccessModes(slotProps).includes(
'write', 'write',
) )
" "
><AIcon
@click.stop="clickEdit(slotProps)" @click.stop="clickEdit(slotProps)"
type="EditOutlined" ><AIcon type="EditOutlined"
/></a> /></a>
<a <a
v-if=" v-if="
@ -142,11 +157,31 @@
'read', 'read',
) )
" "
><AIcon
@click.stop="clickRedo(slotProps)" @click.stop="clickRedo(slotProps)"
type="RedoOutlined" ><AIcon type="RedoOutlined"
/></a> /></a>
</div> </div>
<div
v-if="propertyValue.has(slotProps.id)"
class="card-box-content-right-2"
>
<p>
{{
propertyValue.get(slotProps.id)
?.hex || ''
}}
</p>
<p>
{{
moment(
propertyValue.get(slotProps.id)
?.timestamp,
).format('YYYY-MM-DD HH:mm:ss')
}}
</p>
</div>
</div>
<div class="card-box-content-right"> <div class="card-box-content-right">
<div <div
v-if="getRight1(slotProps)" v-if="getRight1(slotProps)"
@ -198,7 +233,6 @@ import {
readPoint, readPoint,
} from '@/api/data-collect/collector'; } from '@/api/data-collect/collector';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useMenuStore } from 'store/menu';
import PointCardBox from './components/PointCardBox/index.vue'; import PointCardBox from './components/PointCardBox/index.vue';
import WritePoint from './components/WritePoint/index.vue'; import WritePoint from './components/WritePoint/index.vue';
import BatchUpdate from './components/BatchUpdate/index.vue'; import BatchUpdate from './components/BatchUpdate/index.vue';
@ -207,6 +241,9 @@ import SaveOPCUA from './Save/SaveOPCUA.vue';
import Scan from './Scan/index.vue'; import Scan from './Scan/index.vue';
import { colorMap, getState } from '../data.ts'; import { colorMap, getState } from '../data.ts';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { getWebSocket } from '@/utils/websocket';
import { map } from 'rxjs/operators';
import moment from 'moment';
const props = defineProps({ const props = defineProps({
data: { data: {
@ -215,7 +252,6 @@ const props = defineProps({
}, },
}); });
const menuStory = useMenuStore();
const tableRef = ref<Record<string, any>>({}); const tableRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({}); const params = ref<Record<string, any>>({});
const opcImage = getImage('/DataCollect/device-opcua.png'); const opcImage = getImage('/DataCollect/device-opcua.png');
@ -227,10 +263,10 @@ const visible = reactive({
batchUpdate: false, batchUpdate: false,
scan: false, scan: false,
}); });
const current = ref({}); const current: any = ref({});
const accessModesOption = ref(); const accessModesOption = ref();
const _selectedRowKeys = ref<string[]>([]); const _selectedRowKeys = ref<string[]>([]);
const checkAll = ref(false);
const spinning = ref(false); const spinning = ref(false);
const collectorId = ref(props.data.id); const collectorId = ref(props.data.id);
@ -242,7 +278,6 @@ const defaultParams = ref({
{ {
column: 'collectorId', column: 'collectorId',
value: collectorId.value, value: collectorId.value,
// value: '1610517928766550016', //
}, },
], ],
}, },
@ -332,6 +367,9 @@ const columns = [
}, },
]; ];
const subRef = ref();
const propertyValue = ref(new Map());
const handlAdd = () => { const handlAdd = () => {
visible.saveModBus = true; visible.saveModBus = true;
current.value = { current.value = {
@ -339,7 +377,7 @@ const handlAdd = () => {
provider: props.data?.provider || 'MODBUS_TCP', provider: props.data?.provider || 'MODBUS_TCP',
}; };
}; };
const handlEdit = (data: Object) => { const handlEdit = (data: any) => {
if (data?.provider === 'OPC_UA') { if (data?.provider === 'OPC_UA') {
visible.saveOPCUA = true; visible.saveOPCUA = true;
} else { } else {
@ -347,12 +385,12 @@ const handlEdit = (data: Object) => {
} }
current.value = cloneDeep(data); current.value = cloneDeep(data);
}; };
const handlDelete = async (data: string | undefined = undefined) => { const handlDelete = async (id: string | undefined = undefined) => {
spinning.value = true; spinning.value = true;
const res = !data const res = !id
? await batchDeletePoint(_selectedRowKeys.value) ? await batchDeletePoint(_selectedRowKeys.value).catch(() => {})
: await removePoint(data as string); : await removePoint(id as string).catch(() => {});
if (res.status === 200) { if (res?.status === 200) {
cancelSelect(); cancelSelect();
tableRef.value?.reload(); tableRef.value?.reload();
message.success('操作成功'); message.success('操作成功');
@ -360,9 +398,13 @@ const handlDelete = async (data: string | undefined = undefined) => {
spinning.value = false; spinning.value = false;
}; };
const handlBatchUpdate = () => { const handlBatchUpdate = () => {
if (_selectedRowKeys.value.length === 0) {
message.warn('请先选择');
return;
}
const dataSet = new Set(_selectedRowKeys.value); const dataSet = new Set(_selectedRowKeys.value);
const dataMap = new Map(); const dataMap = new Map();
tableRef?.value?._dataSource.forEach((i) => { tableRef?.value?._dataSource.forEach((i: any) => {
dataSet.has(i.id) && dataMap.set(i.id, i); dataSet.has(i.id) && dataMap.set(i.id, i);
}); });
current.value = [...dataMap.values()]; current.value = [...dataMap.values()];
@ -376,7 +418,7 @@ const clickEdit = async (data: object) => {
visible.writePoint = true; visible.writePoint = true;
current.value = cloneDeep(data); current.value = cloneDeep(data);
}; };
const clickRedo = async (data: object) => { const clickRedo = async (data: any) => {
const res = await readPoint(data?.collectorId, [data?.id]); const res = await readPoint(data?.collectorId, [data?.id]);
if (res.status === 200) { if (res.status === 200) {
cancelSelect(); cancelSelect();
@ -401,14 +443,14 @@ const getRight1 = (item: Partial<Record<string, any>>) => {
return !!getQuantity(item) || getAddress(item) || getScaleFactor(item); return !!getQuantity(item) || getAddress(item) || getScaleFactor(item);
}; };
const getText = (item: Partial<Record<string, any>>) => { const getText = (item: Partial<Record<string, any>>) => {
return (item?.accessModes || []).map((i) => i?.text).join(','); return (item?.accessModes || []).map((i: any) => i?.text).join(',');
}; };
const getInterval = (item: Partial<Record<string, any>>) => { const getInterval = (item: Partial<Record<string, any>>) => {
const { interval } = item.configuration || ''; const { interval } = item.configuration || '';
return !!interval ? '采集频率' + interval + 'ms' : ''; return !!interval ? '采集频率' + interval + 'ms' : '';
}; };
const getAccessModes = (item: Partial<Record<string, any>>) => { const getAccessModes = (item: Partial<Record<string, any>>) => {
return item?.accessModes?.map((i) => i?.value); return item?.accessModes?.map((i: any) => i?.value);
}; };
const saveChange = (value: object) => { const saveChange = (value: object) => {
@ -431,33 +473,94 @@ const cancelSelect = () => {
}; };
const handleClick = (dt: any) => { const handleClick = (dt: any) => {
if (props.data?.provider !== 'OPC_UA') return;
if (_selectedRowKeys.value.includes(dt.id)) { if (_selectedRowKeys.value.includes(dt.id)) {
const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id); const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
_selectedRowKeys.value.splice(_index, 1); _selectedRowKeys.value.splice(_index, 1);
checkAll.value = false;
} else { } else {
_selectedRowKeys.value = [..._selectedRowKeys.value, dt.id]; _selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
if (
_selectedRowKeys.value.length === tableRef.value?._dataSource.length
) {
checkAll.value = true;
}
} }
}; };
const subscribeProperty = (value: any) => {
const list = value.map((item: any) => item.id);
const id = `collector-${props.data?.channelId || 'channel'}-${
props.data?.id || 'point'
}-data-${list.join('-')}`;
const topic = `/collector/${props.data?.channelId || '*'}/${
props.data?.id || '*'
}/data`;
subRef.value = getWebSocket(id, topic, {
pointId: list.join(','),
})
?.pipe(map((res: any) => res.payload))
.subscribe((payload: any) => {
propertyValue.value.set(payload.pointId, payload);
});
};
const onCheckAllChange = (e: any) => {
if (e.target.checked) {
_selectedRowKeys.value = [
...tableRef.value?._dataSource.map((i: any) => i.id),
];
} else {
cancelSelect();
checkAll.value = false;
}
};
watch(
() => tableRef?.value?._dataSource,
(value) => {
if (value.length !== 0) {
subscribeProperty(value);
}
cancelSelect();
checkAll.value = false;
},
);
watch(
() => _selectedRowKeys.value,
(value) => {
if (value.length === 0) {
checkAll.value = false;
}
},
);
watch( watch(
() => props.data, () => props.data,
(value) => { (value) => {
if (!!value) { if (!!value) {
accessModesOption.value = accessModesOption.value =
value.provider === 'MODBUS_TCP' value?.provider === 'MODBUS_TCP'
? accessModesMODBUS_TCP ? accessModesMODBUS_TCP
: accessModesMODBUS_TCP.concat({ : accessModesMODBUS_TCP.concat({
label: '订阅', label: '订阅',
value: 'subscribe', value: 'subscribe',
}); });
defaultParams.value.terms[0].terms[0].value = value.id; defaultParams.value.terms[0].terms[0].value = value.id;
// defaultParams.value.terms[0].terms[0].value = '1610517928766550016'; //
tableRef?.value?.reload && tableRef?.value?.reload(); tableRef?.value?.reload && tableRef?.value?.reload();
cancelSelect();
checkAll.value = false;
} }
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },
); );
onUnmounted(() => {
if (subRef.value) {
subRef.value?.unsubscribe();
}
});
/** /**
* 搜索 * 搜索
* @param params * @param params
@ -489,15 +592,22 @@ const handleSearch = (e: any) => {
font-size: 20px; font-size: 20px;
} }
.card-box-content { .card-box-content {
margin-top: 10px; margin-top: 20px;
display: flex; display: flex;
.card-box-content-left { .card-box-content-left {
flex: 0.2; max-width: 220px;
border-right: 1px solid #e0e4e8; border-right: 1px solid #e0e4e8;
height: 68px; height: 68px;
padding-right: 20px; padding-right: 10px;
.card-box-content-left-1 {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
.card-box-content-left-1-title {
color: #000;
font-size: 20px;
opacity: 0.85;
}
}
a { a {
margin-left: 10px; margin-left: 10px;
} }

View File

@ -11,8 +11,13 @@
:model="formData" :model="formData"
name="basic" name="basic"
autocomplete="off" autocomplete="off"
ref="formRef"
>
<j-form-item
label="所属通道"
name="channelId"
:rules="LeftTreeRules.channelId"
> >
<j-form-item label="所属通道" v-bind="validateInfos.channelId">
<j-select <j-select
style="width: 100%" style="width: 100%"
v-model:value="formData.channelId" v-model:value="formData.channelId"
@ -24,7 +29,11 @@
:disabled="!!id" :disabled="!!id"
/> />
</j-form-item> </j-form-item>
<j-form-item label="采集器名称" v-bind="validateInfos.name"> <j-form-item
label="采集器名称"
name="name"
:rules="LeftTreeRules.name"
>
<j-input <j-input
placeholder="请输入采集器名称" placeholder="请输入采集器名称"
v-model:value="formData.name" v-model:value="formData.name"
@ -32,8 +41,9 @@
</j-form-item> </j-form-item>
<j-form-item <j-form-item
label="从机地址" label="从机地址"
v-bind="validateInfos['configuration.unitId']" :name="['configuration', 'unitId']"
v-if="visibleUnitId" v-if="visibleUnitId"
:rules="LeftTreeRules.unitId"
> >
<j-input-number <j-input-number
style="width: 100%" style="width: 100%"
@ -43,8 +53,10 @@
:max="255" :max="255"
/> />
</j-form-item> </j-form-item>
<j-form-item
<j-form-item v-bind="validateInfos['circuitBreaker.type']"> :name="['circuitBreaker', 'type']"
:rules="LeftTreeRules.type"
>
<template #label> <template #label>
<span> <span>
故障处理 故障处理
@ -60,20 +72,21 @@
</j-tooltip> </j-tooltip>
</span> </span>
</template> </template>
<RadioCard <j-card-select
layout="horizontal" :showImage="false"
:checkStyle="true" v-model:value="formData.circuitBreaker.type"
:options="[ :options="[
{ label: '降频', value: 'LowerFrequency' }, { label: '降频', value: 'LowerFrequency' },
{ label: '熔断', value: 'Break' }, { label: '熔断', value: 'Break' },
{ label: '忽略', value: 'Ignore' }, { label: '忽略', value: 'Ignore' },
]" ]"
v-model="formData.circuitBreaker.type" @change="changeCardSelectType"
/> />
</j-form-item> </j-form-item>
<j-form-item <j-form-item
v-bind="validateInfos['configuration.endian']" :name="['configuration', 'endian']"
v-if="visibleEndian" v-if="visibleEndian"
:rules="LeftTreeRules.endian"
> >
<template #label> <template #label>
<span> <span>
@ -86,17 +99,17 @@
</j-tooltip> </j-tooltip>
</span> </span>
</template> </template>
<RadioCard <j-card-select
layout="horizontal" :showImage="false"
:checkStyle="true" v-model:value="formData.configuration.endian"
:options="[ :options="[
{ label: 'AB', value: 'BIG' }, { label: 'AB', value: 'BIG' },
{ label: 'BA', value: 'LITTLE' }, { label: 'BA', value: 'LITTLE' },
]" ]"
v-model="formData.configuration.endian" @change="changeCardSelectEndian"
/> />
</j-form-item> </j-form-item>
<j-form-item label="说明" v-bind="validateInfos.description"> <j-form-item label="说明" name="description">
<j-textarea <j-textarea
placeholder="请输入说明" placeholder="请输入说明"
v-model:value="formData.description" v-model:value="formData.description"
@ -127,6 +140,9 @@
import { Form } from 'ant-design-vue'; import { Form } from 'ant-design-vue';
import { save, update } from '@/api/data-collect/collector'; import { save, update } from '@/api/data-collect/collector';
import { Store } from 'jetlinks-store'; import { Store } from 'jetlinks-store';
import { cloneDeep } from 'lodash';
import { LeftTreeRules } from '../../data';
import type { FormInstance } from 'ant-design-vue';
const loading = ref(false); const loading = ref(false);
const useForm = Form.useForm; const useForm = Form.useForm;
@ -145,6 +161,7 @@ const props = defineProps({
const emit = defineEmits(['change']); const emit = defineEmits(['change']);
const id = props.data.id; const id = props.data.id;
const formRef = ref<FormInstance>();
const formData = ref({ const formData = ref({
channelId: undefined, channelId: undefined,
@ -160,42 +177,14 @@ const formData = ref({
description: '', description: '',
}); });
const regOnlyNumber = new RegExp(/^\d+$/); const handleOk = async () => {
const data = await formRef.value?.validate();
const { resetFields, validate, validateInfos } = useForm(
formData,
reactive({
channelId: [
{ required: true, message: '请选择所属通道', trigger: 'blur' },
],
name: [
{ required: true, message: '请输入采集器名称', trigger: 'blur' },
{ max: 64, message: '最多可输入64个字符' },
],
'configuration.unitId': [
{ required: true, message: '请输入从机地址', trigger: 'blur' },
{
pattern: regOnlyNumber,
message: '请输入0-255之间的正整数',
},
],
'circuitBreaker.type': [
{ required: true, message: '请选择处理方式', trigger: 'blur' },
],
'configuration.endian': [
{ required: true, message: '请选择高低位切换', trigger: 'blur' },
],
description: [{ max: 200, message: '最多可输入200个字符' }],
}),
);
const onSubmit = () => {
validate()
.then(async (res) => {
const { provider, name } = channelListAll.value.find( const { provider, name } = channelListAll.value.find(
(item) => item.id === formData.value.channelId, (item: any) => item.id === formData.value.channelId,
); );
const params = { const params = {
...toRaw(formData.value), ...data,
provider, provider,
channelName: name, channelName: name,
}; };
@ -208,10 +197,6 @@ const onSubmit = () => {
emit('change', true); emit('change', true);
} }
loading.value = false; loading.value = false;
})
.catch((err) => {
loading.value = false;
});
}; };
const getTypeTooltip = (value: string) => const getTypeTooltip = (value: string) =>
@ -221,13 +206,16 @@ const getTypeTooltip = (value: string) =>
? '连续10分钟异常停止采集数据进入熔断状态设备重新启用后恢复采集状态' ? '连续10分钟异常停止采集数据进入熔断状态设备重新启用后恢复采集状态'
: '忽略异常保持原采集频率超时时间为5s'; : '忽略异常保持原采集频率超时时间为5s';
const handleOk = () => {
onSubmit();
};
const handleCancel = () => { const handleCancel = () => {
emit('change', false); emit('change', false);
}; };
const changeCardSelectType = (value: Array<string>) => {
formData.value.circuitBreaker.type = value[0];
};
const changeCardSelectEndian = (value: Array<string>) => {
formData.value.configuration.endian = value[0];
};
const getChannelNoPaging = async () => { const getChannelNoPaging = async () => {
channelListAll.value = Store.get('channelListAll'); channelListAll.value = Store.get('channelListAll');
channelList.value = channelListAll.value.map((item) => ({ channelList.value = channelListAll.value.map((item) => ({

View File

@ -20,12 +20,13 @@
</PermissionButton> </PermissionButton>
</div> </div>
<j-spin :spinning="spinning"> <j-spin :spinning="spinning">
<a-tree <j-tree
:tree-data="defualtDataSource" :tree-data="defualtDataSource"
v-model:selected-keys="selectedKeys" v-model:selected-keys="selectedKeys"
:fieldNames="{ key: 'id' }" :fieldNames="{ key: 'id' }"
v-if="defualtDataSource[0].children.length !== 0" v-if="defualtDataSource[0].children.length !== 0"
:height="600" :height="600"
defaultExpandAll
> >
<template #title="{ name, data }"> <template #title="{ name, data }">
<Ellipsis class="tree-left-title"> <Ellipsis class="tree-left-title">
@ -90,7 +91,7 @@
</PermissionButton> </PermissionButton>
</span> </span>
</template> </template>
</a-tree> </j-tree>
<j-empty v-else description="暂无数据" /> <j-empty v-else description="暂无数据" />
</j-spin> </j-spin>
<Save v-if="visible" :data="current" @change="saveChange" /> <Save v-if="visible" :data="current" @change="saveChange" />
@ -121,13 +122,13 @@ const emits = defineEmits(['change']);
const route = useRoute(); const route = useRoute();
const channelId = route.query?.channelId; const channelId = route.query?.channelId;
const spinning = ref(false); const spinning = ref(false);
const selectedKeys = ref([]); const selectedKeys: any = ref([]);
const searchValue = ref(); const searchValue = ref();
const visible = ref(false); const visible = ref(false);
const current = ref({}); const current = ref({});
const collectorAll = ref(); const collectorAll = ref();
const defualtDataSource = ref([ const defualtDataSource: any = ref([
{ {
id: '*', id: '*',
name: '全部', name: '全部',
@ -164,7 +165,7 @@ const handlEdit = (data: object) => {
visible.value = true; visible.value = true;
}; };
const handlUpdate = async (data: object) => { const handlUpdate = async (data: any) => {
const state = data?.state?.value; const state = data?.state?.value;
const resp = await update(data?.id, { const resp = await update(data?.id, {
state: state !== 'disabled' ? 'disabled' : 'enabled', state: state !== 'disabled' ? 'disabled' : 'enabled',
@ -210,7 +211,7 @@ const handleSearch = async (value: string) => {
!!value && (params.value = value); !!value && (params.value = value);
} }
spinning.value = true; spinning.value = true;
const res = await queryCollector(params.value); const res: any = await queryCollector(params.value);
if (res.status === 200) { if (res.status === 200) {
defualtDataSource.value[0].children = res.result; defualtDataSource.value[0].children = res.result;
collectorAll.value = res.result; collectorAll.value = res.result;
@ -232,11 +233,14 @@ onMounted(() => {
getChannelNoPaging(); getChannelNoPaging();
}); });
watch(selectedKeys, (n) => { watch(
() => selectedKeys.value,
(n) => {
const key = _.isArray(n) ? n[0] : n; const key = _.isArray(n) ? n[0] : n;
const row = collectorAll.value.find((i) => i.id === key); const row = collectorAll.value.find((i: any) => i.id === key);
emits('change', row); emits('change', row);
}); },
);
watch( watch(
() => searchValue.value, () => searchValue.value,
@ -249,7 +253,7 @@ watch(
<style lang="less" scoped> <style lang="less" scoped>
.tree-container { .tree-container {
padding-right: 24px; padding-right: 24px;
width: 300px;
.add-btn { .add-btn {
margin: 10px 0; margin: 10px 0;

View File

@ -17,7 +17,7 @@ export const getState = (record: any) => {
} }
}; };
const regOnlyNumber = new RegExp(/^\d+$/); export const regOnlyNumber = new RegExp(/^\d+$/);
export const checkProviderData = { export const checkProviderData = {
int8: 1, int8: 1,
@ -93,7 +93,7 @@ export const ModBusRules = {
byteCount: [ byteCount: [
{ {
required: true, required: true,
message: '请输入自定义数据区长度byte', message: '请输入自定义数据区长度(byte)',
}, },
], ],
interval: [ interval: [
@ -101,6 +101,10 @@ export const ModBusRules = {
required: true, required: true,
message: '请输入采集频率', message: '请输入采集频率',
}, },
{
pattern: regOnlyNumber,
message: '请输入0或者正整数',
},
], ],
description: [{ max: 200, message: '最多可输入200个字符' }], description: [{ max: 200, message: '最多可输入200个字符' }],
@ -134,10 +138,31 @@ export const OPCUARules = {
required: true, required: true,
message: '请输入采集频率', message: '请输入采集频率',
}, },
{
pattern: regOnlyNumber,
message: '请输入0或者正整数',
},
], ],
description: [{ max: 200, message: '最多可输入200个字符' }], description: [{ max: 200, message: '最多可输入200个字符' }],
}; };
export const LeftTreeRules = {
channelId: [{ required: true, message: '请选择所属通道', trigger: 'blur' }],
name: [
{ required: true, message: '请输入采集器名称', trigger: 'blur' },
{ max: 64, message: '最多可输入64个字符' },
],
unitId: [
{ required: true, message: '请输入从机地址', trigger: 'blur' },
{
pattern: regOnlyNumber,
message: '请输入0-255之间的正整数',
},
],
type: [{ required: true, message: '请选择处理方式', trigger: 'blur' }],
endian: [{ required: true, message: '请选择高低位切换', trigger: 'blur' }],
};
export const FormTableColumns = [ export const FormTableColumns = [
{ {
title: '名称', title: '名称',
@ -162,7 +187,7 @@ export const FormTableColumns = [
title: '采集频率', title: '采集频率',
key: 'interval', key: 'interval',
dataIndex: 'interval', dataIndex: 'interval',
width: 280, width: 260,
}, },
{ {
title: '只推送变化的数据', title: '只推送变化的数据',
@ -174,7 +199,6 @@ export const FormTableColumns = [
title: '操作', title: '操作',
key: 'action', key: 'action',
dataIndex: 'action', dataIndex: 'action',
fixed: 'right', fixed: 'right',
width: 80, width: 80,
}, },

View File

@ -5,7 +5,10 @@
<Tree @change="changeTree" /> <Tree @change="changeTree" />
</div> </div>
<div class="right"> <div class="right">
<j-spin :spinning="spinning">
<Point v-if="!!data" :data="data" /> <Point v-if="!!data" :data="data" />
<j-empty style="margin-top: 20%" v-else />
</j-spin>
</div> </div>
</div> </div>
</page-container> </page-container>
@ -16,9 +19,11 @@ import Tree from './Tree/index.vue';
import Point from './Point/index.vue'; import Point from './Point/index.vue';
const data = ref(); const data = ref();
const spinning = ref(true);
const changeTree = (row: any) => { const changeTree = (row: any) => {
data.value = row; data.value = row;
spinning.value = false;
}; };
</script> </script>
@ -29,6 +34,7 @@ const changeTree = (row: any) => {
padding: 14px; padding: 14px;
display: flex; display: flex;
min-height: calc(100vh - 180px); min-height: calc(100vh - 180px);
width: 100%;
.left { .left {
width: 300px; width: 300px;
border-right: 1px #eeeeee solid; border-right: 1px #eeeeee solid;

View File

@ -7,7 +7,6 @@
</div> </div>
<div class="right"> <div class="right">
<j-radio-group <j-radio-group
default-value="a"
button-style="solid" button-style="solid"
style="margin-right: 10px" style="margin-right: 10px"
v-model:value="data.time.type" v-model:value="data.time.type"
@ -31,10 +30,8 @@
</j-range-picker> </j-range-picker>
</div> </div>
</div> </div>
<div>
<div ref="chartRef" style="width: 100%; height: 350px"></div> <div ref="chartRef" style="width: 100%; height: 350px"></div>
</div> </div>
</div>
</j-spin> </j-spin>
</template> </template>
@ -63,9 +60,9 @@ const pickerTimeChange = (
data.value.time.type = undefined; data.value.time.type = undefined;
}; };
const getEcharts = async (val) => { const getEcharts = async (val: any) => {
loading.value = true; loading.value = true;
const resp = await dashboard(pointParams(val)); const resp: any = await dashboard(pointParams(val));
if (resp.success) { if (resp.success) {
const x = resp.result const x = resp.result
.map((item: any) => item.data.timeString) .map((item: any) => item.data.timeString)
@ -79,7 +76,7 @@ const getEcharts = async (val) => {
}; };
const handleOptions = (x = [], y = []) => { const handleOptions = (x = [], y = []) => {
const chart = chartRef.value; const chart: any = chartRef.value;
if (chart) { if (chart) {
const myChart = echarts.init(chart); const myChart = echarts.init(chart);
const options = { const options = {
@ -117,7 +114,7 @@ const handleOptions = (x = [], y = []) => {
watch( watch(
() => data.value.time.type, () => data.value.time.type,
(value) => { (value) => {
data.value.time.end = Date.parse(new Date()); data.value.time.end = Date.parse(Date());
data.value.time.start = Date.parse(getTimeByType(value)); data.value.time.start = Date.parse(getTimeByType(value));
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },

View File

@ -44,7 +44,6 @@ const props = defineProps({
.top-card { .top-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
// height: 200px;
padding: 24px; padding: 24px;
background-color: #fff; background-color: #fff;
border: 1px solid #e0e4e8; border: 1px solid #e0e4e8;

View File

@ -28,11 +28,11 @@ import { queryCount } from '@/api/data-collect/dashboard';
import { defaultParams, statusData } from './tool'; import { defaultParams, statusData } from './tool';
const getNumberData = () => { const getNumberData = () => {
statusData.value.forEach(async (item) => { statusData.forEach(async (item: any) => {
const res = await queryCount(item[0].type, {}); const res = await queryCount(item[0].type, {});
const resp = await queryCount(item[0].type, defaultParams); const resp = await queryCount(item[0].type, defaultParams);
item[0].total = res.result || 0; item[0].total = res?.result || 0;
item[0].value = resp.result || 0; item[0].value = resp?.result || 0;
}); });
}; };
getNumberData(); getNumberData();

View File

@ -1,5 +1,4 @@
import moment from 'moment'; import moment from 'moment';
import * as echarts from 'echarts';
const getParams = (dt: any) => { const getParams = (dt: any) => {
switch (dt.type) { switch (dt.type) {
@ -54,7 +53,7 @@ const getParams = (dt: any) => {
} }
}; };
export const getTimeByType = (type) => { export const getTimeByType = (type: string) => {
switch (type) { switch (type) {
case 'hour': case 'hour':
return moment().subtract(1, 'hours'); return moment().subtract(1, 'hours');
@ -69,7 +68,7 @@ export const getTimeByType = (type) => {
} }
}; };
export const pointParams = (data) => [ export const pointParams = (data: any) => [
{ {
dashboard: 'collector', dashboard: 'collector',
object: 'pointData', object: 'pointData',
@ -121,7 +120,7 @@ export const defaultParams = {
], ],
}; };
export const statusData = ref([ export const statusData = [
[ [
{ {
type: 'channel', type: 'channel',
@ -152,4 +151,4 @@ export const statusData = ref([
total: 0, total: 0,
}, },
], ],
]); ];

View File

@ -1,13 +1,3 @@
export type Agg = {
duration: number;
total: number;
};
export type AggPlaying = {
playerTotal: number;
playingTotal: number;
};
export type Footer = { export type Footer = {
title: string; title: string;
value: number | string; value: number | string;

View File

@ -1,21 +1,21 @@
<template> <template>
<div> <div>
<a-radio-group <j-radio-group
v-if="quickBtn" v-if="quickBtn"
default-value="today" default-value="today"
button-style="solid" button-style="solid"
v-model:value="radioValue" v-model:value="radioValue"
@change="(e) => handleBtnChange(e.target.value)" @change="(e) => handleBtnChange(e.target.value)"
> >
<a-radio-button <j-radio-button
v-for="item in quickBtnList" v-for="item in quickBtnList"
:key="item.value" :key="item.value"
:value="item.value" :value="item.value"
> >
{{ item.label }} {{ item.label }}
</a-radio-button> </j-radio-button>
</a-radio-group> </j-radio-group>
<a-range-picker <j-range-picker
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
valueFormat="YYYY-MM-DD HH:mm:ss" valueFormat="YYYY-MM-DD HH:mm:ss"
style="margin-left: 12px" style="margin-left: 12px"
@ -23,7 +23,7 @@
v-model:value="rangeVal" v-model:value="rangeVal"
:allowClear="false" :allowClear="false"
> >
</a-range-picker> </j-range-picker>
</div> </div>
</template> </template>

View File

@ -4,12 +4,12 @@
<div class="content-left"> <div class="content-left">
<div class="content-left-title"> <div class="content-left-title">
<span>{{ title }}</span> <span>{{ title }}</span>
<a-tooltip placement="top" v-if="tooltip"> <j-tooltip placement="top" v-if="tooltip">
<template #title> <template #title>
<span>{{ tooltip }}</span> <span>{{ tooltip }}</span>
</template> </template>
<AIcon type="QuestionCircleOutlined" /> <AIcon type="QuestionCircleOutlined" />
</a-tooltip> </j-tooltip>
</div> </div>
<div class="content-left-value">{{ value }}</div> <div class="content-left-value">{{ value }}</div>
</div> </div>
@ -23,7 +23,7 @@
<div class="top-card-footer"> <div class="top-card-footer">
<template v-for="(item, index) in footer" :key="index"> <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>
<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> <div class="footer-item-value">{{ item.value }}</div>
</template> </template>
</div> </div>

View File

@ -1,24 +1,24 @@
<template> <template>
<page-container> <page-container>
<div class="DashBoardBox"> <div class="DashBoardBox">
<a-row :gutter="24"> <j-row :gutter="24">
<a-col :span="6"> <j-col :span="6">
<TopCard <TopCard
title="产品数量" title="产品数量"
:img="getImage('/device/device-product.png')" :img="getImage('/device/device-product.png')"
:footer="productFooter" :footer="productFooter"
:value="productTotal" :value="productTotal"
></TopCard> ></TopCard>
</a-col> </j-col>
<a-col :span="6"> <j-col :span="6">
<TopCard <TopCard
title="设备数量" title="设备数量"
:img="getImage('/device/device-number.png')" :img="getImage('/device/device-number.png')"
:footer="deviceFooter" :footer="deviceFooter"
:value="deviceTotal" :value="deviceTotal"
></TopCard ></TopCard
></a-col> ></j-col>
<a-col :span="6" <j-col :span="6"
><TopCard ><TopCard
title="当前在线" title="当前在线"
:footer="onlineFooter" :footer="onlineFooter"
@ -29,18 +29,18 @@
:chartYData="barChartYData" :chartYData="barChartYData"
></BarChart> --> ></BarChart> -->
<Charts :options="onlineOptions"></Charts> </TopCard <Charts :options="onlineOptions"></Charts> </TopCard
></a-col> ></j-col>
<a-col :span="6" <j-col :span="6"
><TopCard ><TopCard
title="今日设备信息量" title="今日设备信息量"
:footer="messageFooter" :footer="messageFooter"
:value="dayMessage" :value="dayMessage"
> >
<Charts :options="TodayDevOptions"></Charts> </TopCard <Charts :options="TodayDevOptions"></Charts> </TopCard
></a-col> ></j-col>
</a-row> </j-row>
<a-row :gutter="24"> <j-row :gutter="24">
<a-col :span="24"> <j-col :span="24">
<div class="message-card"> <div class="message-card">
<Guide title="设备消息"> <Guide title="设备消息">
<template #extra> <template #extra>
@ -56,18 +56,18 @@
<Charts :options="devMegOptions"></Charts> <Charts :options="devMegOptions"></Charts>
</div> </div>
</div> </div>
</a-col> </j-col>
</a-row> </j-row>
<a-row :span="24"> <j-row :span="24">
<a-col :span="24"> <j-col :span="24">
<div class="device-position"> <div class="device-position">
<Guide title="设备分布"></Guide> <Guide title="设备分布"></Guide>
<div class="device-map"> <div class="device-map">
<Amap></Amap> <Amap></Amap>
</div> </div>
</div> </div>
</a-col> </j-col>
</a-row> </j-row>
</div> </div>
</page-container> </page-container>
</template> </template>

View File

@ -62,7 +62,7 @@
</page-container> </page-container>
</template> </template>
<script lang="ts" setup name="FirmwarePage"> <script lang="ts" setup name="FirmwarePage">
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index';
import { query, queryProduct, remove } from '@/api/device/firmware'; import { query, queryProduct, remove } from '@/api/device/firmware';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import moment from 'moment'; import moment from 'moment';

View File

@ -6,18 +6,18 @@
@click="handleClick" @click="handleClick"
> >
<div class="card-content"> <div class="card-content">
<a-row :gutter="20"> <j-row :gutter="20">
<a-col :span="10"> <j-col :span="10">
<!-- 图片 --> <!-- 图片 -->
<div class="card-item-avatar"> <div class="card-item-avatar">
<slot name="img"> </slot> <slot name="img"> </slot>
</div> </div>
</a-col> </j-col>
<a-col :span="14"> <j-col :span="14">
<!-- 内容 --> <!-- 内容 -->
<slot name="content"></slot> <slot name="content"></slot>
</a-col> </j-col>
</a-row> </j-row>
<!-- 勾选 --> <!-- 勾选 -->
<div v-if="active" class="checked-icon"> <div v-if="active" class="checked-icon">

View File

@ -1,7 +1,7 @@
<!-- 配置信息 --> <!-- 配置信息 -->
<template> <template>
<a-card style="min-height: 100%"> <j-card style="min-height: 100%">
<a-descriptions bordered> <j-descriptions bordered>
<template #title> <template #title>
<div style="display: flex"> <div style="display: flex">
<h3>配置信息</h3> <h3>配置信息</h3>
@ -11,35 +11,35 @@
</div> </div>
</template> </template>
<a-descriptions-item label="ID">{{ <j-descriptions-item label="ID">{{
productStore.current.id productStore.current.id
}}</a-descriptions-item> }}</j-descriptions-item>
<a-descriptions-item label="产品分类">{{ <j-descriptions-item label="产品分类">{{
productStore.current.classifiedName productStore.current.classifiedName
}}</a-descriptions-item> }}</j-descriptions-item>
<a-descriptions-item label="设备类型">{{ <j-descriptions-item label="设备类型">{{
productStore.current.deviceType?.text productStore.current.deviceType?.text
}}</a-descriptions-item> }}</j-descriptions-item>
<a-descriptions-item label="接入方式"> <j-descriptions-item label="接入方式">
<a-button type="link" @click="changeTables">{{ <j-button type="link" @click="changeTables">{{
productStore.current.accessName productStore.current.accessName
? productStore.current.accessName ? productStore.current.accessName
: '配置接入方式' : '配置接入方式'
}}</a-button> }}</j-button>
</a-descriptions-item> </j-descriptions-item>
<a-descriptions-item label="创建时间">{{ <j-descriptions-item label="创建时间">{{
moment(productStore.current.createTime).format('YYYY-MM-DD HH:mm:ss') moment(productStore.current.createTime).format('YYYY-MM-DD HH:mm:ss')
}}</a-descriptions-item> }}</j-descriptions-item>
<a-descriptions-item label="更新时间">{{ <j-descriptions-item label="更新时间">{{
moment(productStore.current.modifyTime).format('YYYY-MM-DD HH:mm:ss') moment(productStore.current.modifyTime).format('YYYY-MM-DD HH:mm:ss')
}}</a-descriptions-item> }}</j-descriptions-item>
<a-descriptions-item label="说明" :span="3"> <j-descriptions-item label="说明" :span="3">
{{ productStore.current.describe }} {{ productStore.current.describe }}
</a-descriptions-item> </j-descriptions-item>
</a-descriptions> </j-descriptions>
</a-card> </j-card>
<!-- 编辑 --> <!-- 编辑 -->
<Save ref="saveRef" :isAdd="isAdd" :title="title" /> <Save ref="saveRef" :isAdd="isAdd" :title="title" />
</template> </template>

View File

@ -1,6 +1,6 @@
<!-- 产品保存成功后的提示框 --> <!-- 产品保存成功后的提示框 -->
<template> <template>
<a-modal <j-modal
:maskClosable="false" :maskClosable="false"
destroy-on-close destroy-on-close
v-model:visible="visible" v-model:visible="visible"
@ -12,7 +12,7 @@
<span>产品创建成功</span> <span>产品创建成功</span>
</template> </template>
<template #footer> <template #footer>
<a-button @click="cancel">关闭</a-button> <j-button @click="cancel">关闭</j-button>
</template> </template>
<div class="product-tips"> <div class="product-tips">
<div style="display: flex"> <div style="display: flex">
@ -43,7 +43,7 @@
进入设备列表页面点击批量导入设备批量添加同一产品下的设备 进入设备列表页面点击批量导入设备批量添加同一产品下的设备
</div> </div>
</div> </div>
</a-modal> </j-modal>
</template> </template>
<script lang="ts" setup name="DialogTips"> <script lang="ts" setup name="DialogTips">
import { getImage } from '@/utils/comm.ts'; import { getImage } from '@/utils/comm.ts';

View File

@ -24,7 +24,7 @@
<template #icon><AIcon type="PlusOutlined" /></template> <template #icon><AIcon type="PlusOutlined" /></template>
新增 新增
</PermissionButton> </PermissionButton>
<a-upload <j-upload
name="file" name="file"
accept=".json" accept=".json"
:showUploadList="false" :showUploadList="false"
@ -33,7 +33,7 @@
<PermissionButton hasPermission="device/Product:import" <PermissionButton hasPermission="device/Product:import"
>导入</PermissionButton >导入</PermissionButton
> >
</a-upload> </j-upload>
</j-space> </j-space>
</template> </template>
<template #deviceType="slotProps"> <template #deviceType="slotProps">

View File

@ -59,7 +59,7 @@
<j-form-item label="系统logo"> <j-form-item label="系统logo">
<div class="upload-image-warp-logo"> <div class="upload-image-warp-logo">
<div class="upload-image-border-logo"> <div class="upload-image-border-logo">
<a-upload <j-upload
name="file" name="file"
:action="FILE_UPLOAD" :action="FILE_UPLOAD"
:headers="headers" :headers="headers"
@ -109,7 +109,7 @@
</div> </div>
</div> </div>
</div> </div>
</a-upload> </j-upload>
<div v-if="logoLoading"> <div v-if="logoLoading">
<div class="upload-loading-mask"> <div class="upload-loading-mask">
<LoadingOutlined <LoadingOutlined
@ -138,7 +138,7 @@
</template> </template>
<div class="upload-image-warp-logo"> <div class="upload-image-warp-logo">
<div class="upload-image-border-logo"> <div class="upload-image-border-logo">
<a-upload <j-upload
name="file" name="file"
:action="FILE_UPLOAD" :action="FILE_UPLOAD"
:headers="headers" :headers="headers"
@ -183,7 +183,7 @@
</div> </div>
</div> </div>
</div> </div>
</a-upload> </j-upload>
</div> </div>
</div> </div>
@ -197,7 +197,7 @@
<j-form-item label="登录背景图"> <j-form-item label="登录背景图">
<div class="upload-image-warp-back"> <div class="upload-image-warp-back">
<div class="upload-image-border-back"> <div class="upload-image-border-back">
<a-upload <j-upload
name="file" name="file"
:action="FILE_UPLOAD" :action="FILE_UPLOAD"
:headers="headers" :headers="headers"
@ -242,7 +242,7 @@
</div> </div>
</div> </div>
</div> </div>
</a-upload> </j-upload>
</div> </div>
</div> </div>
<div class="upload-tips">支持4M以内的图片:支持jpgpng</div> <div class="upload-tips">支持4M以内的图片:支持jpgpng</div>

View File

@ -137,7 +137,7 @@
</page-container> </page-container>
</template> </template>
<script lang="ts" setup name="StreamPage"> <script lang="ts" setup name="StreamPage">
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { query, remove, disable, enalbe } from '@/api/media/stream'; import { query, remove, disable, enalbe } from '@/api/media/stream';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';

View File

@ -9,7 +9,7 @@
visible visible
> >
<h5 class="row"> <h5 class="row">
<exclamation-circle-outlined style="margin-right: 6px" /> <AIcon type="ExclamationCircleOutlined" style="margin-right: 6px" />
只能分配有共享权限的资产数据 只能分配有共享权限的资产数据
</h5> </h5>
@ -132,7 +132,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { uniq, intersection } from 'lodash-es'; import { uniq, intersection } from 'lodash-es';
import { import {

View File

@ -7,7 +7,7 @@
class="search-input" class="search-input"
> >
<template #suffix> <template #suffix>
<search-outlined /> <AIcon type="SearchOutlined" />
</template> </template>
</j-input> </j-input>
<div class="add-btn"> <div class="add-btn">
@ -89,7 +89,6 @@ import { debounce, cloneDeep, omit } from 'lodash-es';
import { ArrayToTree } from '@/utils/utils'; import { ArrayToTree } from '@/utils/utils';
import EditDepartmentDialog from './EditDepartmentDialog.vue'; import EditDepartmentDialog from './EditDepartmentDialog.vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
const permission = 'system/Department'; const permission = 'system/Department';

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="setting-container"> <div class="setting-container">
<h5 class="top"> <h5 class="top">
<exclamation-circle-outlined /> <AIcon type="ExclamationCircleOutlined" />
<span style="padding-left: 12px" <span style="padding-left: 12px"
>基于系统源代码中的菜单数据配置系统菜单</span >基于系统源代码中的菜单数据配置系统菜单</span
> >
@ -17,7 +17,7 @@
<template #title <template #title
>根据系统代码自动读取的菜单数据</template >根据系统代码自动读取的菜单数据</template
> >
<question-circle-outlined /> <AIcon type="QuestionCircleOutlined" />
</j-tooltip> </j-tooltip>
</div> </div>
<div class="title-func"> <div class="title-func">
@ -36,7 +36,10 @@
placeholder="请输入菜单名称" placeholder="请输入菜单名称"
> >
<template #prefix> <template #prefix>
<search-outlined style="color: #b3b3b3" /> <AIcon
type="SearchOutlined"
style="color: #b3b3b3"
/>
</template> </template>
</j-input> </j-input>
<j-tree <j-tree
@ -63,7 +66,7 @@
<template #title <template #title
>菜单管理页面配置的菜单数据</template >菜单管理页面配置的菜单数据</template
> >
<question-circle-outlined /> <AIcon type="QuestionCircleOutlined" />
</j-tooltip> </j-tooltip>
</div> </div>
</div> </div>
@ -74,7 +77,10 @@
placeholder="请输入菜单名称" placeholder="请输入菜单名称"
> >
<template #prefix> <template #prefix>
<search-outlined style="color: #b3b3b3" /> <AIcon
type="SearchOutlined"
style="color: #b3b3b3"
/>
</template> </template>
</j-input> </j-input>
<j-tree <j-tree
@ -103,7 +109,7 @@
style="padding: 0" style="padding: 0"
type="link" type="link"
> >
<close-outlined /> <AIcon type="CloseOutlined" />
</j-button> </j-button>
</j-tooltip> </j-tooltip>
</j-popconfirm> </j-popconfirm>
@ -114,7 +120,7 @@
</div> </div>
</div> </div>
<j-button type="primary" style="margin-top: 24px;">保存</j-button> <j-button type="primary" style="margin-top: 24px">保存</j-button>
<div class="dialogs"> <div class="dialogs">
<j-modal <j-modal
@ -131,13 +137,6 @@
</template> </template>
<script setup lang="ts" name="MenuSetting"> <script setup lang="ts" name="MenuSetting">
import {
ExclamationCircleOutlined,
QuestionCircleOutlined,
SearchOutlined,
CloseOutlined,
} from '@ant-design/icons-vue';
import { getMenuTree_api } from '@/api/system/menu'; import { getMenuTree_api } from '@/api/system/menu';
import { getSystemPermission as getSystemPermission_api } from '@/api/initHome'; import { getSystemPermission as getSystemPermission_api } from '@/api/initHome';
import { filterMenu, getKeys, loop } from './utils'; import { filterMenu, getKeys, loop } from './utils';

View File

@ -64,8 +64,13 @@
onConfirm: () => table.changeStatus(slotProps), onConfirm: () => table.changeStatus(slotProps),
}" }"
> >
<stop-outlined v-if="slotProps.status" /> <AIcon
<play-circle-outlined v-else /> :type="
slotProps.status
? 'StopOutlined'
: 'PlayCircleOutlined'
"
/>
</PermissionButton> </PermissionButton>
<PermissionButton <PermissionButton
:hasPermission="`${permission}:update`" :hasPermission="`${permission}:update`"
@ -117,7 +122,6 @@ import {
changeUserStatus_api, changeUserStatus_api,
deleteUser_api, deleteUser_api,
} from '@/api/system/user'; } from '@/api/system/user';
import { StopOutlined, PlayCircleOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
const permission = 'system/User'; const permission = 'system/User';