Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
afd27100cd
|
@ -15,7 +15,7 @@ export const unBind_api = (appId: string) => server.post(`/application/sso/${app
|
|||
* @param type 类型
|
||||
* @param name 值
|
||||
*/
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`,name,{
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`,name,{},{
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export const validateField_api = (type: 'username' | 'password', name: string)
|
|||
* 校验旧密码是否正确
|
||||
* @param password 旧密码
|
||||
*/
|
||||
export const checkOldPassword_api = (password:string) => server.post(`/user/me/password/_validate`,password,{
|
||||
export const checkOldPassword_api = (password:string) => server.post(`/user/me/password/_validate`,password,{},{
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
|
|
@ -21,3 +21,34 @@ export const _validateField = (id: string, data?: any) =>
|
|||
server.get(`/data-collect/point/${id}/_validate`, data);
|
||||
|
||||
export const queryCodecProvider = () => server.get(`/things/collector/codecs`);
|
||||
|
||||
export const updatePoint = (id: string, data: any) =>
|
||||
server.put(`/data-collect/point/${id}`, data);
|
||||
|
||||
export const savePointBatch = (data: any) =>
|
||||
server.patch(`/data-collect/point`, data);
|
||||
|
||||
export const savePoint = (data: any) =>
|
||||
server.post(`/data-collect/point`, data);
|
||||
|
||||
export const batchDeletePoint = (data: any) =>
|
||||
server.post(`/data-collect/point/batch/_delete`, data);
|
||||
|
||||
export const removePoint = (id: string) =>
|
||||
server.remove(`/data-collect/point/${id}`);
|
||||
|
||||
export const readPoint = (collectorId: string, data: string[]) =>
|
||||
server.post(`/data-collect/collector/${collectorId}/points/_read`, data);
|
||||
|
||||
export const writePoint = (collectorId: string, data: string[]) =>
|
||||
server.post(`/data-collect/collector/${collectorId}/points/_write`, data);
|
||||
|
||||
export const queryPointNoPaging = () =>
|
||||
server.post(`/data-collect/point/_query/no-paging`, { paging: false });
|
||||
|
||||
export const scanOpcUAList = (data: any) =>
|
||||
server.get(
|
||||
`/data-collect/opc/channel/${data.id}/nodes?nodeId=${
|
||||
data?.nodeId || ''
|
||||
}`,
|
||||
);
|
||||
|
|
|
@ -100,7 +100,7 @@ export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boo
|
|||
* @param type 文件类型
|
||||
* @returns
|
||||
*/
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}`
|
||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? `/${productId}` : ''}/export.${type}`
|
||||
|
||||
/**
|
||||
* 验证设备ID是否重复
|
||||
|
|
|
@ -7,7 +7,7 @@ export const getUserType_api = () => server.get(`/user/detail/types`);
|
|||
export const getUserList_api = (data: object) => server.post(`/user/detail/_query`, data);
|
||||
|
||||
// 校验字段合法性
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`, name, {
|
||||
export const validateField_api = (type: 'username' | 'password', name: string) => server.post(`/user/${type}/_validate`, name,{}, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export const addUser_api = (data: object) => server.post(`/user/detail/_create`,
|
|||
// 更新用户
|
||||
export const updateUser_api = (data: any) => server.put(`/user/detail/${data.id}/_update`, data);
|
||||
// 更新密码
|
||||
export const updatePassword_api = (data: { id: string, password: string }) => server.post(`/user/${data.id}/password/_reset`, data.password, {
|
||||
export const updatePassword_api = (data: { id: string, password: string }) => server.post(`/user/${data.id}/password/_reset`, data.password,{}, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #title>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置元素</div>
|
||||
<AIcon type="CloseOutlined" @click="visible = false" />
|
||||
<div @click="visible = false"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -55,7 +55,7 @@ const _value = computed({
|
|||
const visible = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
emit('update:value', { extends: {}, ...props.value })
|
||||
emit('update:value', { expands: {}, ...props.value })
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -9,18 +9,20 @@
|
|||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">枚举项配置</div>
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
<div @click="handleClose"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="ant-form-vertical">
|
||||
<j-form-item label="Value" :name="name.concat([index, 'value'])" :rules="[
|
||||
{ required: true, message: '请输入Value' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<j-input v-model:value="_value[index].value" size="small"></j-input>
|
||||
</j-form-item>
|
||||
<j-form-item label="Text" :name="name.concat([index, 'text'])" :rules="[
|
||||
{ required: true, message: '请输入Text' },
|
||||
{ max: 64, message: '最多可输入64个字符' },
|
||||
]">
|
||||
<j-input v-model:value="_value[index].text" size="small"></j-input>
|
||||
</j-form-item>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<template #title>
|
||||
<div class="edit-title" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="width: 150px;">配置参数</div>
|
||||
<AIcon type="CloseOutlined" @click="handleClose" />
|
||||
<div @click="handleClose"><AIcon type="CloseOutlined" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -40,8 +40,8 @@
|
|||
</div>
|
||||
</j-popover>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<AIcon type="DeleteOutlined" @click="handleDelete(index)" />
|
||||
<div class="item-right" @click="handleDelete(index)">
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</div>
|
||||
</div>
|
||||
<j-button type="dashed" block @click="handleAdd">
|
||||
|
@ -96,6 +96,7 @@ const handleDelete = (index: number) => {
|
|||
_value.value.splice(index, 1)
|
||||
}
|
||||
const handleClose = () => {
|
||||
console.log(editIndex.value)
|
||||
editIndex.value = -1
|
||||
}
|
||||
const handleAdd = () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
name="file"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="{
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY)
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
accept=".xlsx,.csv"
|
||||
:maxCount="1"
|
||||
|
@ -26,19 +26,23 @@
|
|||
</a-space>
|
||||
<div style="margin-top: 20px" v-if="importLoading">
|
||||
<a-badge v-if="flag" status="processing" text="进行中" />
|
||||
<a-badge v-else status="success" text="已完成" />
|
||||
<span>总数量:{{count}}</span>
|
||||
<p style="color: red">{{errMessage}}</p>
|
||||
<a-badge v-else status="success" text="已完成" />
|
||||
<span>总数量:{{ count }}</span>
|
||||
<p style="color: red">{{ errMessage }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FILE_UPLOAD } from '@/api/comm'
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { downloadFile, downloadFileByUrl } from '@/utils/utils';
|
||||
import { deviceImport, deviceTemplateDownload ,templateDownload} from '@/api/device/instance'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
import {
|
||||
deviceImport,
|
||||
deviceTemplateDownload,
|
||||
templateDownload,
|
||||
} from '@/api/device/instance';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
type Emits = {
|
||||
|
@ -50,11 +54,11 @@ const props = defineProps({
|
|||
// 组件双向绑定的值
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
product: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
file: {
|
||||
type: Object,
|
||||
|
@ -62,67 +66,62 @@ const props = defineProps({
|
|||
return {
|
||||
fileType: 'xlsx',
|
||||
autoDeploy: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const importLoading = ref<boolean>(false)
|
||||
const flag = ref<boolean>(false)
|
||||
const count = ref<number>(0)
|
||||
const errMessage = ref<string>('')
|
||||
const importLoading = ref<boolean>(false);
|
||||
const flag = ref<boolean>(false);
|
||||
const count = ref<number>(0);
|
||||
const errMessage = ref<string>('');
|
||||
|
||||
const downFile =async (type: string) => {
|
||||
const downFile = async (type: string) => {
|
||||
// downloadFile(deviceTemplateDownload(props.product, type));
|
||||
const res:any =await templateDownload(props.product, type)
|
||||
if(res){
|
||||
const res: any = await templateDownload(props.product, type);
|
||||
if (res) {
|
||||
const blob = new Blob([res], { type: type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
console.log(url);
|
||||
downloadFileByUrl(
|
||||
url,
|
||||
`设备导入模版`,
|
||||
type,
|
||||
);
|
||||
const url = URL.createObjectURL(blob);
|
||||
downloadFileByUrl(url, `设备导入模版`, type);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const submitData = async (fileUrl: string) => {
|
||||
if (!!fileUrl) {
|
||||
count.value = 0
|
||||
errMessage.value = ''
|
||||
flag.value = true
|
||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||
importLoading.value = true
|
||||
let dt = 0;
|
||||
const source = new EventSourcePolyfill(deviceImport(props.product, fileUrl, autoDeploy));
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message || '失败'
|
||||
}
|
||||
};
|
||||
source.onerror = (e: { status: number; }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员'
|
||||
flag.value = false
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
count.value = 0;
|
||||
errMessage.value = '';
|
||||
flag.value = true;
|
||||
const autoDeploy = !!props?.file?.autoDeploy || false;
|
||||
importLoading.value = true;
|
||||
let dt = 0;
|
||||
const source = new EventSourcePolyfill(
|
||||
deviceImport(props.product, fileUrl, autoDeploy),
|
||||
);
|
||||
source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt;
|
||||
} else {
|
||||
errMessage.value = res.message || '失败';
|
||||
}
|
||||
};
|
||||
source.onerror = (e: { status: number }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
|
||||
flag.value = false;
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
} else {
|
||||
message.error('请先上传文件')
|
||||
message.error('请先上传文件');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadChange = async (info: Record<string, any>) => {
|
||||
if (info.file.status === 'done') {
|
||||
const resp: any = info.file.response || { result: '' };
|
||||
await submitData(resp?.result || '');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="title" :style='style'>
|
||||
<div class="title-before"></div>
|
||||
<span>{{ data }}</span>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
|
@ -23,23 +22,22 @@ const props = defineProps({
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
.title-before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: calc(100% - 2px);
|
||||
background-color: @primary-color;
|
||||
border-radius: 0 3px 3px 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background-color: @primary-color;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -11,21 +11,10 @@
|
|||
:model="formData"
|
||||
name="basic"
|
||||
autocomplete="off"
|
||||
:rules="ModBusRules"
|
||||
ref="formRef"
|
||||
>
|
||||
<j-form-item
|
||||
label="点位名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入点位名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-form-item label="点位名称" name="name">
|
||||
<j-input
|
||||
placeholder="请输入点位名称"
|
||||
v-model:value="formData.name"
|
||||
|
@ -34,12 +23,7 @@
|
|||
<j-form-item
|
||||
label="功能码"
|
||||
:name="['configuration', 'function']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择功能码',
|
||||
},
|
||||
]"
|
||||
:rules="ModBusRules.function"
|
||||
>
|
||||
<j-select
|
||||
style="width: 100%"
|
||||
|
@ -57,18 +41,11 @@
|
|||
</j-form-item>
|
||||
<j-form-item
|
||||
label="地址"
|
||||
:name="['configuration', 'parameter', 'address']"
|
||||
:name="['pointKey']"
|
||||
:rules="[
|
||||
...ModBusRules.pointKey,
|
||||
{
|
||||
required: true,
|
||||
message: '请输入地址',
|
||||
},
|
||||
{
|
||||
pattern: regOnlyNumber,
|
||||
message: '请输入0-255之间的正整数',
|
||||
},
|
||||
{
|
||||
validator: checkAddress,
|
||||
validator: checkPointKey,
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
|
@ -76,7 +53,7 @@
|
|||
<j-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入地址"
|
||||
v-model:value="formData.configuration.parameter.address"
|
||||
v-model:value="formData.pointKey"
|
||||
:min="0"
|
||||
:max="255"
|
||||
/>
|
||||
|
@ -84,16 +61,7 @@
|
|||
<j-form-item
|
||||
label="寄存器数量"
|
||||
:name="['configuration', 'parameter', 'quantity']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入寄存器数量',
|
||||
},
|
||||
{
|
||||
pattern: regOnlyNumber,
|
||||
message: '请输入1-255之间的正整数',
|
||||
},
|
||||
]"
|
||||
:rules="ModBusRules.quantity"
|
||||
>
|
||||
<j-input-number
|
||||
style="width: 100%"
|
||||
|
@ -104,12 +72,14 @@
|
|||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="formData.configuration.function === 'HoldingRegisters'"
|
||||
label="数据类型"
|
||||
:name="['configuration', 'codec', 'provider']"
|
||||
:rules="[
|
||||
...ModBusRules.provider,
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据类型',
|
||||
validator: checkProvider,
|
||||
trigger: 'change',
|
||||
},
|
||||
]"
|
||||
>
|
||||
|
@ -131,31 +101,18 @@
|
|||
'configuration',
|
||||
'scaleFactor',
|
||||
]"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入缩放因子',
|
||||
},
|
||||
]"
|
||||
:rules="ModBusRules.scaleFactor"
|
||||
>
|
||||
<j-input
|
||||
<j-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入缩放因子"
|
||||
v-model:value="
|
||||
formData.configuration.codec.configuration.scaleFactor
|
||||
"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="访问类型"
|
||||
:name="['accessModes']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择访问类型',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<RadioCard
|
||||
<j-form-item label="访问类型" name="accessModes">
|
||||
<!-- <RadioCard
|
||||
layout="horizontal"
|
||||
:checkStyle="true"
|
||||
:options="[
|
||||
|
@ -163,42 +120,36 @@
|
|||
{ label: '写', value: 'write' },
|
||||
]"
|
||||
v-model="formData.accessModes"
|
||||
/>
|
||||
<!-- <j-card-select
|
||||
/> -->
|
||||
<j-checkbox-group
|
||||
v-model:value="formData.accessModes"
|
||||
:options="[
|
||||
{
|
||||
label: '读',
|
||||
value: 'read',
|
||||
iconUrl:
|
||||
'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
|
||||
},
|
||||
{
|
||||
label: '写',
|
||||
value: 'write',
|
||||
iconUrl:
|
||||
'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
|
||||
},
|
||||
{ label: '读', value: 'read' },
|
||||
{ label: '写', value: 'write' },
|
||||
]"
|
||||
multiple
|
||||
/> -->
|
||||
/>
|
||||
</j-form-item>
|
||||
|
||||
<!-- <j-form-item label="非标准协议写入配置" :name="['nspwc']"> -->
|
||||
<j-form-item :name="['nspwc']">
|
||||
<span>非标准协议写入配置</span>
|
||||
<j-form-item
|
||||
:name="['nspwc']"
|
||||
v-if="
|
||||
formData.accessModes?.includes('write') &&
|
||||
formData.configuration.function === 'HoldingRegisters'
|
||||
"
|
||||
>
|
||||
<span style="margin-right: 10px">非标准协议写入配置</span>
|
||||
<j-switch v-model:checked="formData.nspwc" />
|
||||
</j-form-item>
|
||||
|
||||
<j-form-item
|
||||
v-if="
|
||||
!!formData.nspwc &&
|
||||
formData.accessModes?.includes('write') &&
|
||||
formData.configuration.function === 'HoldingRegisters'
|
||||
"
|
||||
label="是否写入数据区长度"
|
||||
:name="['configuration', 'parameter', 'writeByteCount']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择是否写入数据区长度',
|
||||
},
|
||||
]"
|
||||
:rules="ModBusRules.writeByteCount"
|
||||
>
|
||||
<RadioCard
|
||||
layout="horizontal"
|
||||
|
@ -211,14 +162,14 @@
|
|||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="
|
||||
!!formData.nspwc &&
|
||||
formData.accessModes?.includes('write') &&
|
||||
formData.configuration.function === 'HoldingRegisters'
|
||||
"
|
||||
label="自定义数据区长度(byte)"
|
||||
:name="['configuration', 'parameter', 'byteCount']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入自定义数据区长度(byte)',
|
||||
},
|
||||
]"
|
||||
:rules="ModBusRules.byteCount"
|
||||
>
|
||||
<j-input
|
||||
placeholder="请输入自定义数据区长度(byte)"
|
||||
|
@ -229,9 +180,10 @@
|
|||
label="采集频率"
|
||||
:name="['configuration', 'interval']"
|
||||
:rules="[
|
||||
...ModBusRules.interval,
|
||||
{
|
||||
required: true,
|
||||
message: '请输入采集频率',
|
||||
validator: checkLength,
|
||||
trigger: 'change',
|
||||
},
|
||||
]"
|
||||
>
|
||||
|
@ -240,6 +192,7 @@
|
|||
placeholder="请输入采集频率"
|
||||
v-model:value="formData.configuration.interval"
|
||||
:min="1"
|
||||
addon-after="ms"
|
||||
/>
|
||||
</j-form-item>
|
||||
|
||||
|
@ -279,22 +232,16 @@
|
|||
</j-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Form } from 'ant-design-vue';
|
||||
import {
|
||||
save,
|
||||
update,
|
||||
savePointBatch,
|
||||
updatePoint,
|
||||
_validateField,
|
||||
queryCodecProvider,
|
||||
} from '@/api/data-collect/collector';
|
||||
import { Store } from 'jetlinks-store';
|
||||
|
||||
const loading = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
const channelListAll = ref();
|
||||
const channelList = ref();
|
||||
const visibleEndian = ref(false);
|
||||
const visibleUnitId = ref(false);
|
||||
const providerList = ref([]);
|
||||
import { ModBusRules, checkProviderData } from '../../data.ts';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { Rule } from 'ant-design-vue/lib/form';
|
||||
import { cloneDeep, isArray } from 'lodash-es';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
@ -304,105 +251,72 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
const loading = ref(false);
|
||||
const providerList = ref([]);
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const id = props.data.id;
|
||||
const treeId = props.data.treeId;
|
||||
const collectorId = props.data.collectorId;
|
||||
const provider = props.data.provider;
|
||||
const oldPointKey = props.data.pointKey;
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
configuration: {
|
||||
function: undefined,
|
||||
interval: '',
|
||||
interval: 3000,
|
||||
parameter: {
|
||||
address: '',
|
||||
quantity: '',
|
||||
quantity: 1,
|
||||
writeByteCount: '',
|
||||
byteCount: '',
|
||||
byteCount: 2,
|
||||
address: '',
|
||||
},
|
||||
codec: {
|
||||
provider: '',
|
||||
provider: undefined,
|
||||
configuration: {
|
||||
scaleFactor: '',
|
||||
scaleFactor: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
accessModes: undefined,
|
||||
nspwc: '',
|
||||
byte: '',
|
||||
features: '',
|
||||
pointKey: '',
|
||||
accessModes: [],
|
||||
nspwc: false,
|
||||
features: [],
|
||||
description: '',
|
||||
});
|
||||
|
||||
const regOnlyNumber = new RegExp(/^\d+$/);
|
||||
const onSubmit = async () => {
|
||||
const data = await formRef.value?.validate();
|
||||
|
||||
const checkAddress = (_rule: Rule, value: string): Promise<any> =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
if (value) {
|
||||
const res = await _validateField(props.data.treeId, {
|
||||
pointKey: value,
|
||||
});
|
||||
return res.result.passed ? resolve('') : reject(res.result.reason);
|
||||
}
|
||||
});
|
||||
delete data?.nspwc;
|
||||
const { codec } = data?.configuration;
|
||||
|
||||
// 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(
|
||||
// (item) => item.id === formData.value.channelId,
|
||||
// );
|
||||
// const params = {
|
||||
// ...toRaw(formData.value),
|
||||
// provider,
|
||||
// channelName: name,
|
||||
// };
|
||||
// loading.value = true;
|
||||
// const response = !id
|
||||
// ? await save(params)
|
||||
// : await update(id, { ...props.data, ...params });
|
||||
// if (response.status === 200) {
|
||||
// emit('change', true);
|
||||
// }
|
||||
// loading.value = false;
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// loading.value = false;
|
||||
// });
|
||||
if (data?.configuration.function !== 'HoldingRegisters') {
|
||||
codec.provider = 'int8';
|
||||
}
|
||||
const params = {
|
||||
...props.data,
|
||||
...data,
|
||||
provider,
|
||||
collectorId,
|
||||
};
|
||||
|
||||
// address是多余字段,但是react版本上使用到了这个字段
|
||||
params.configuration.parameter = {
|
||||
...params.configuration.parameter,
|
||||
address: data?.pointKey,
|
||||
};
|
||||
|
||||
loading.value = true;
|
||||
const response = !id
|
||||
? await savePointBatch(params)
|
||||
: await updatePoint(id, { ...props.data, ...params });
|
||||
if (response.status === 200) {
|
||||
emit('change', true);
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const getTypeTooltip = (value: string) =>
|
||||
value === 'LowerFrequency'
|
||||
? '连续20次异常,降低连接频率至原有频率的1/10(重试间隔不超过1分钟),故障处理后自动恢复至设定连接频率'
|
||||
: value === 'Break'
|
||||
? '连续10分钟异常,停止采集数据进入熔断状态,设备重新启用后恢复采集状态'
|
||||
: '忽略异常,保持原采集频率超时时间为5s';
|
||||
|
||||
const handleOk = () => {
|
||||
onSubmit();
|
||||
};
|
||||
|
@ -410,19 +324,42 @@ const handleCancel = () => {
|
|||
emit('change', false);
|
||||
};
|
||||
|
||||
// const getChannelNoPaging = async () => {
|
||||
// channelListAll.value = Store.get('channelListAll');
|
||||
// channelList.value = channelListAll.value.map((item) => ({
|
||||
// value: item.id,
|
||||
// label: item.name,
|
||||
// }));
|
||||
// };
|
||||
// getChannelNoPaging();
|
||||
const checkLength = (_rule: Rule, value: string): Promise<any> =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
if (value) {
|
||||
return String(value).length > 64
|
||||
? reject('最多可输入64个字符')
|
||||
: resolve('');
|
||||
}
|
||||
});
|
||||
|
||||
const checkProvider = (_rule: Rule, value: string): Promise<any> =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
if (value) {
|
||||
const { quantity } = formData.value.configuration.parameter;
|
||||
return checkProviderData[value] > Number(quantity) * 2
|
||||
? reject('数据类型长度需 <= 寄存器数量 * 2')
|
||||
: resolve('');
|
||||
}
|
||||
});
|
||||
|
||||
const checkPointKey = (_rule: Rule, value: string): Promise<any> =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
if (value) {
|
||||
if (Number(oldPointKey) === Number(value)) return resolve('');
|
||||
const res = await _validateField(collectorId, {
|
||||
pointKey: value,
|
||||
});
|
||||
return res.result.passed ? resolve('') : reject(res.result.reason);
|
||||
}
|
||||
});
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const getProviderList = async () => {
|
||||
const res = await queryCodecProvider();
|
||||
console.log(222, res.result);
|
||||
|
||||
providerList.value = res.result
|
||||
.filter((i) => i.id !== 'property')
|
||||
.map((item) => ({
|
||||
|
@ -432,20 +369,31 @@ const getProviderList = async () => {
|
|||
};
|
||||
getProviderList();
|
||||
|
||||
// watch(
|
||||
// () => formData.value.channelId,
|
||||
// (value) => {
|
||||
// const dt = channelListAll.value.find((item) => item.id === value);
|
||||
// visibleUnitId.value = visibleEndian.value =
|
||||
// dt?.provider && dt?.provider === 'MODBUS_TCP';
|
||||
// },
|
||||
// { deep: true },
|
||||
// );
|
||||
|
||||
watch(
|
||||
() => formData.value.configuration.parameter.quantity,
|
||||
(value) => {
|
||||
formData.value.configuration.parameter.byteCount = Number(value) * 2;
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value.id) formData.value = value;
|
||||
if (value.id && value.provider === 'MODBUS_TCP') {
|
||||
const _value = cloneDeep(value);
|
||||
const { writeByteCount, byteCount } =
|
||||
_value.configuration.parameter;
|
||||
formData.value = _value;
|
||||
if (!!_value.accessModes[0]?.value) {
|
||||
formData.value.accessModes = value.accessModes.map(
|
||||
(i) => i.value,
|
||||
);
|
||||
}
|
||||
if (!!_value.features[0]?.value) {
|
||||
formData.value.features = value.features.map((i) => i.value);
|
||||
}
|
||||
formData.value.nspwc = !!writeByteCount || !!byteCount;
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
<template lang="">
|
||||
<j-modal title="编辑" :visible="true" width="700px" @cancel="handleCancel">
|
||||
<j-form
|
||||
class="form"
|
||||
layout="vertical"
|
||||
:model="formData"
|
||||
name="basic"
|
||||
autocomplete="off"
|
||||
:rules="OPCUARules"
|
||||
ref="formRef"
|
||||
>
|
||||
<j-form-item label="点位名称" name="name">
|
||||
<j-input
|
||||
placeholder="请输入点位名称"
|
||||
v-model:value="formData.name"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item label="数据类型" :name="['configuration', 'type']">
|
||||
<j-select
|
||||
style="width: 100%"
|
||||
v-model:value="formData.configuration.type"
|
||||
:options="[
|
||||
{ value: 'Number', label: '数值类型' },
|
||||
{ value: 'DateTime', label: '时间类型' },
|
||||
{ value: 'Array', label: '数组类型' },
|
||||
{ value: 'String', label: '文本类型' },
|
||||
{ value: 'Boolean', label: '布尔' },
|
||||
]"
|
||||
placeholder="请选择数据类型"
|
||||
allowClear
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
</j-form-item>
|
||||
|
||||
<j-form-item label="访问类型" name="accessModes">
|
||||
<j-checkbox-group
|
||||
v-model:value="formData.accessModes"
|
||||
:options="[
|
||||
{ label: '读', value: 'read' },
|
||||
{ label: '写', value: 'write' },
|
||||
{ label: '订阅', value: 'subscribe' },
|
||||
]"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="采集频率"
|
||||
:name="['configuration', 'interval']"
|
||||
:rules="[
|
||||
...OPCUARules.interval,
|
||||
{
|
||||
validator: checkLength,
|
||||
trigger: 'change',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入采集频率"
|
||||
v-model:value="formData.configuration.interval"
|
||||
:min="1"
|
||||
addon-after="ms"
|
||||
/>
|
||||
</j-form-item>
|
||||
|
||||
<a-form-item label="" :name="['features']">
|
||||
<a-checkbox-group v-model:value="formData.features">
|
||||
<a-checkbox value="changedOnly" name="type"
|
||||
>只推送变化的数据</a-checkbox
|
||||
>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<j-form-item label="说明" :name="['description']">
|
||||
<j-textarea
|
||||
placeholder="请输入说明"
|
||||
v-model:value="formData.description"
|
||||
:maxlength="200"
|
||||
:rows="3"
|
||||
showCount
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
<template #footer>
|
||||
<j-button key="back" @click="handleCancel">取消</j-button>
|
||||
<PermissionButton
|
||||
key="submit"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleOk"
|
||||
style="margin-left: 8px"
|
||||
:hasPermission="`DataCollect/Collector:${
|
||||
id ? 'update' : 'add'
|
||||
}`"
|
||||
>
|
||||
确认
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
savePoint,
|
||||
updatePoint,
|
||||
_validateField,
|
||||
} from '@/api/data-collect/collector';
|
||||
import { OPCUARules, checkProviderData } from '../../data.ts';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { Rule } from 'ant-design-vue/lib/form';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
const loading = ref(false);
|
||||
const providerList = ref([]);
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const id = props.data.id;
|
||||
const collectorId = props.data.collectorId;
|
||||
const provider = props.data.provider;
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
configuration: {
|
||||
type: undefined,
|
||||
interval: 3000,
|
||||
},
|
||||
accessModes: [],
|
||||
features: [],
|
||||
description: '',
|
||||
});
|
||||
|
||||
const onSubmit = async () => {
|
||||
const data = await formRef.value?.validate();
|
||||
|
||||
const params = {
|
||||
...props.data,
|
||||
...data,
|
||||
provider,
|
||||
collectorId,
|
||||
};
|
||||
|
||||
loading.value = true;
|
||||
const response = !id
|
||||
? await savePoint(params)
|
||||
: await updatePoint(id, { ...props.data, ...params });
|
||||
if (response.status === 200) {
|
||||
emit('change', true);
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
onSubmit();
|
||||
};
|
||||
const handleCancel = () => {
|
||||
emit('change', false);
|
||||
};
|
||||
|
||||
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) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value.id && value.provider === 'OPC_UA') {
|
||||
const _value = cloneDeep(value);
|
||||
formData.value = _value;
|
||||
if (!!_value.accessModes[0]?.value) {
|
||||
formData.value.accessModes = value.accessModes.map(
|
||||
(i) => i.value,
|
||||
);
|
||||
}
|
||||
if (!!_value.features[0]?.value) {
|
||||
formData.value.features = value.features.map((i) => i.value);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.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>
|
|
@ -0,0 +1,274 @@
|
|||
<template>
|
||||
<j-form style="width: 80%" ref="formTableRef" :model="modelRef">
|
||||
<j-table
|
||||
:dataSource="modelRef.dataSource"
|
||||
:columns="FormTableColumns"
|
||||
:scroll="{ x: 1100, y: 500 }"
|
||||
>
|
||||
<template #bodyCell="{ column: { dataIndex }, record, index }">
|
||||
<template v-if="dataIndex === 'name'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'name']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="record[dataIndex]"
|
||||
placeholder="请输入"
|
||||
allowClear
|
||||
></j-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="dataIndex === 'id'">
|
||||
<a-form-item :name="['dataSource', index, 'id']">
|
||||
<j-input v-model:value="record[dataIndex]" disabled>
|
||||
</j-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-if="dataIndex === 'accessModes'">
|
||||
<a-form-item
|
||||
class="form-item"
|
||||
:name="['dataSource', index, 'accessModes', 'value']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-select
|
||||
style="width: 75%"
|
||||
v-model:value="record[dataIndex].value"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
mode="multiple"
|
||||
:filter-option="filterOption"
|
||||
:options="[
|
||||
{ label: '读', value: 'read' },
|
||||
{ label: '写', value: 'write' },
|
||||
{ label: '订阅', value: 'subscribe' },
|
||||
]"
|
||||
:disabled="index !== 0 && record[dataIndex].check"
|
||||
@change="changeValue(index, dataIndex)"
|
||||
>
|
||||
</j-select>
|
||||
<j-checkbox
|
||||
style="margin-left: 5px"
|
||||
v-if="index !== 0"
|
||||
v-model:checked="record[dataIndex].check"
|
||||
@click="changeCheckbox(index, dataIndex)"
|
||||
>同上</j-checkbox
|
||||
>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="dataIndex === 'interval'">
|
||||
<a-form-item
|
||||
class="form-item"
|
||||
:name="[
|
||||
'dataSource',
|
||||
index,
|
||||
'configuration',
|
||||
'interval',
|
||||
'value',
|
||||
]"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
style="width: 70%"
|
||||
v-model:value="
|
||||
record.configuration[dataIndex].value
|
||||
"
|
||||
placeholder="请输入"
|
||||
allowClear
|
||||
addon-after="ms"
|
||||
:disabled="
|
||||
index !== 0 &&
|
||||
record.configuration[dataIndex].check
|
||||
"
|
||||
@blur="changeValue(index, dataIndex)"
|
||||
></j-input>
|
||||
<j-checkbox
|
||||
style="margin-left: 5px"
|
||||
v-show="index !== 0"
|
||||
v-model:checked="
|
||||
record.configuration[dataIndex].check
|
||||
"
|
||||
@click="changeCheckbox(index, dataIndex)"
|
||||
>同上</j-checkbox
|
||||
>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="dataIndex === 'features'">
|
||||
<a-form-item
|
||||
class="form-item"
|
||||
:name="['dataSource', index, 'features', 'value']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-select
|
||||
style="width: 50%"
|
||||
v-model:value="record[dataIndex].value"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
:filter-option="filterOption"
|
||||
:options="[
|
||||
{
|
||||
label: '是',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: false,
|
||||
},
|
||||
]"
|
||||
:disabled="index !== 0 && record[dataIndex].check"
|
||||
@change="changeValue(index, dataIndex)"
|
||||
>
|
||||
</j-select>
|
||||
|
||||
<j-checkbox
|
||||
style="margin-left: 5px"
|
||||
v-show="index !== 0"
|
||||
v-model:checked="record[dataIndex].check"
|
||||
@click="changeCheckbox(index, dataIndex)"
|
||||
>同上</j-checkbox
|
||||
>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-if="dataIndex === 'action'">
|
||||
<a-tooltip title="删除">
|
||||
<a-popconfirm
|
||||
title="确认删除"
|
||||
@confirm="clickDelete(record.id)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</j-table>
|
||||
</j-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormTableColumns } from '../../data';
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['change']);
|
||||
|
||||
const formTableRef = ref();
|
||||
const defaultType = ['accessModes', 'interval', 'features'];
|
||||
const modelRef = reactive({
|
||||
dataSource: [],
|
||||
});
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const clickDelete = (value: string) => {
|
||||
emits('change', value);
|
||||
};
|
||||
|
||||
const getTargetData = (index: number, type: string) => {
|
||||
const { dataSource } = modelRef;
|
||||
const Interval = type === 'interval';
|
||||
return !Interval
|
||||
? dataSource[index][type]
|
||||
: dataSource[index].configuration[type];
|
||||
};
|
||||
|
||||
const changeValue = (index: number, type: string) => {
|
||||
const { dataSource } = modelRef;
|
||||
const originData = getTargetData(index, type);
|
||||
for (let i = index + 1; i < dataSource.length; i++) {
|
||||
const targetType = getTargetData(i, type);
|
||||
if (!targetType.check) return;
|
||||
targetType.value = originData.value;
|
||||
}
|
||||
};
|
||||
|
||||
const changeCheckbox = (index: number, type: string) => {
|
||||
// console.log(1, getTargetData(index, type).check,getTargetData(index, type));
|
||||
//如果不使用setTimeout,会导致值更新不及时
|
||||
setTimeout(() => {
|
||||
// console.log(2, getTargetData(index, type).check,getTargetData(index, type));
|
||||
let startIndex = 0;
|
||||
const { dataSource } = modelRef;
|
||||
const currentCheck = getTargetData(index, type).check;
|
||||
if (!currentCheck) return;
|
||||
for (let i = index; i >= 0; i--) {
|
||||
const preDatCheck = getTargetData(i, type).check;
|
||||
if (!preDatCheck) {
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const originData = getTargetData(startIndex, type);
|
||||
for (let i = startIndex; i < dataSource.length - 1; i++) {
|
||||
const targetType = getTargetData(i + 1, type);
|
||||
if (!targetType.check) return;
|
||||
targetType.value = originData.value;
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
return new Promise((res, rej) => {
|
||||
formTableRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
res(modelRef.dataSource);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
rej(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
defineExpose({
|
||||
validate,
|
||||
});
|
||||
watch(
|
||||
() => props.data,
|
||||
(value, preValue) => {
|
||||
modelRef.dataSource = value;
|
||||
|
||||
// 有新增时同上数据
|
||||
const vlength = value.length,
|
||||
plength = preValue.length;
|
||||
if (plength !== 0 && plength < vlength) {
|
||||
defaultType.forEach((type) => {
|
||||
vlength === 2
|
||||
? changeValue(0, type)
|
||||
: changeCheckbox(vlength - 1, type);
|
||||
});
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form-item {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,240 @@
|
|||
<template>
|
||||
<div class="tree-content">
|
||||
<div class="tree-header">
|
||||
<div>数据源</div>
|
||||
<j-checkbox v-model:checked="isSelected">隐藏已有节点</j-checkbox>
|
||||
</div>
|
||||
<j-spin :spinning="spinning">
|
||||
<a-tree
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
:tree-data="treeData"
|
||||
default-expand-all
|
||||
checkable
|
||||
@check="onCheck"
|
||||
:height="600"
|
||||
>
|
||||
<!-- <a-tree
|
||||
:load-data="onLoadData"
|
||||
:tree-data="treeData"
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
checkable
|
||||
@check="onCheck"
|
||||
> -->
|
||||
<template #title="{ name, key }">
|
||||
<span
|
||||
:class="[
|
||||
selectKeys.includes(key)
|
||||
? 'tree-selected'
|
||||
: 'tree-title',
|
||||
]"
|
||||
>
|
||||
{{ name }}
|
||||
</span>
|
||||
</template>
|
||||
</a-tree>
|
||||
</j-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TreeProps } from 'ant-design-vue';
|
||||
import {
|
||||
scanOpcUAList,
|
||||
queryPointNoPaging,
|
||||
} from '@/api/data-collect/collector';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
unSelectKeys: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['change']);
|
||||
|
||||
// const channelId = '1610517801347788800'; //测试
|
||||
const channelId = props.data?.channelId;
|
||||
|
||||
const checkedKeys = ref<string[]>([]);
|
||||
const selectKeys = ref<string[]>([]);
|
||||
const spinning = ref(false);
|
||||
|
||||
const isSelected = ref(false);
|
||||
const treeData = ref<TreeProps['treeData']>();
|
||||
const treeAllData = ref<TreeProps['treeData']>();
|
||||
|
||||
const onLoadData = (node: any) =>
|
||||
new Promise<void>(async (resolve) => {
|
||||
if ((node?.children && node.children?.length) || !node?.folder) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const resp = await scanOpcUAList({
|
||||
id: channelId,
|
||||
nodeId: node.key,
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
const list = resp.result.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
key: item.id,
|
||||
title: item.name,
|
||||
disabled: item?.folder,
|
||||
isLeaf: !item?.folder,
|
||||
};
|
||||
});
|
||||
|
||||
treeAllData.value = updateTreeData(
|
||||
cloneDeep(treeAllData.value),
|
||||
node.key,
|
||||
[...list],
|
||||
);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
const handleData = (arr: any[]): any[] => {
|
||||
const data = arr.filter((item) => {
|
||||
return (
|
||||
(isSelected && !selectKeys.value.includes(item.id)) || !isSelected
|
||||
);
|
||||
});
|
||||
return data.map((item) => {
|
||||
if (item.children && item.children?.length) {
|
||||
return {
|
||||
...item,
|
||||
children: handleData(item.children),
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onCheck = (checkedKeys, info) => {
|
||||
const one: any = { ...info.node };
|
||||
const list: any = [];
|
||||
const last: any = list.length ? list[list.length - 1] : undefined;
|
||||
if (list.map((i: any) => i?.id).includes(one.id)) {
|
||||
return;
|
||||
}
|
||||
const item = {
|
||||
features: {
|
||||
value: last
|
||||
? last?.features?.value
|
||||
: (one?.features || []).includes('changedOnly'),
|
||||
check: true,
|
||||
},
|
||||
id: one?.id || '',
|
||||
name: one?.name || '',
|
||||
accessModes: {
|
||||
value: last ? last?.accessModes?.value : one?.accessModes || [],
|
||||
check: true,
|
||||
},
|
||||
configuration: {
|
||||
...one?.configuration,
|
||||
interval: {
|
||||
value: last
|
||||
? last?.configuration?.interval?.value
|
||||
: one?.configuration?.interval || 3000,
|
||||
check: true,
|
||||
},
|
||||
nodeId: one?.id,
|
||||
},
|
||||
};
|
||||
emits('change', item, info.checked);
|
||||
};
|
||||
|
||||
const updateTreeData = (list: any[], key: string, children: any[]): any[] => {
|
||||
const arr = list.map((node) => {
|
||||
if (node.key === key) {
|
||||
return {
|
||||
...node,
|
||||
children,
|
||||
};
|
||||
}
|
||||
if (node?.children && node?.children?.length) {
|
||||
return {
|
||||
...node,
|
||||
children: updateTreeData(node.children, key, children),
|
||||
};
|
||||
}
|
||||
return node;
|
||||
});
|
||||
return arr;
|
||||
};
|
||||
|
||||
const getPoint = async () => {
|
||||
spinning.value = true;
|
||||
const res = await queryPointNoPaging();
|
||||
if (res.status === 200) {
|
||||
selectKeys.value = res.result.map((item: any) => item.pointKey);
|
||||
}
|
||||
spinning.value = false;
|
||||
};
|
||||
getPoint();
|
||||
|
||||
const getScanOpcUAList = async () => {
|
||||
const res = await scanOpcUAList({ id: channelId });
|
||||
treeAllData.value = res.result.map((item: any) => ({
|
||||
...item,
|
||||
key: item.id,
|
||||
title: item.name,
|
||||
disabled: item?.folder || false,
|
||||
}));
|
||||
};
|
||||
getScanOpcUAList();
|
||||
|
||||
watch(
|
||||
() => isSelected.value,
|
||||
(value) => {
|
||||
if (value) {
|
||||
treeData.value = handleData(treeAllData.value);
|
||||
} else {
|
||||
treeData.value = treeAllData.value;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
watch(
|
||||
() => treeAllData.value,
|
||||
(value) => {
|
||||
if (isSelected.value) {
|
||||
treeData.value = handleData(value);
|
||||
} else {
|
||||
treeData.value = value;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.unSelectKeys,
|
||||
(value) => {
|
||||
checkedKeys.value = checkedKeys.value.filter((i) => i !== value);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tree-content {
|
||||
padding: 16px;
|
||||
padding-left: 0;
|
||||
.tree-header {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.tree-selected {
|
||||
color: #1d39c4;
|
||||
}
|
||||
.tree-title {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,107 @@
|
|||
<template lang="">
|
||||
<j-modal title="扫描" :visible="true" width="90%" @cancel="handleCancel">
|
||||
<div class="content">
|
||||
<Tree
|
||||
:data="treeData"
|
||||
class="tree"
|
||||
@change="changeTree"
|
||||
:unSelectKeys="unSelectKeys"
|
||||
></Tree>
|
||||
<Table
|
||||
:data="tableData"
|
||||
class="table"
|
||||
@change="changeTable"
|
||||
ref="formTableRef"
|
||||
></Table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<j-button key="back" @click="handleCancel">取消</j-button>
|
||||
<PermissionButton
|
||||
key="submit"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleOk"
|
||||
style="margin-left: 8px"
|
||||
:hasPermission="`DataCollect/Collector:update`"
|
||||
>
|
||||
确认
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { savePointBatch } from '@/api/data-collect/collector';
|
||||
import { Rule } from 'ant-design-vue/lib/form';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import Table from './Table.vue';
|
||||
import Tree from './Tree.vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const treeData = ref(props.data);
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
const loading = ref(false);
|
||||
const formTableRef = ref<FormInstance>();
|
||||
const tableData = ref();
|
||||
const tableDataMap = new Map();
|
||||
const unSelectKeys = ref();
|
||||
|
||||
const handleOk = async () => {
|
||||
loading.value = true;
|
||||
const data = await formTableRef.value?.validate();
|
||||
const list = data.map((item: any) => {
|
||||
return {
|
||||
name: item.name,
|
||||
provider: 'OPC_UA',
|
||||
collectorId: props.data?.id,
|
||||
collectorName: props.data?.name,
|
||||
pointKey: item.id,
|
||||
configuration: {
|
||||
interval: item.configuration?.interval?.value,
|
||||
},
|
||||
features: !item.features?.value ? [] : ['changedOnly'],
|
||||
accessModes: item.accessModes?.value || [],
|
||||
};
|
||||
});
|
||||
console.log(1112, props.data, data, list);
|
||||
const resp = await savePointBatch([...list]);
|
||||
if (resp.status === 200) {
|
||||
emit('change', true);
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
const handleCancel = () => {
|
||||
emit('change', false);
|
||||
};
|
||||
|
||||
const changeTree = (row: any, checked: boolean) => {
|
||||
checked ? tableDataMap.set(row.id, row) : tableDataMap.delete(row.id);
|
||||
tableData.value = [...tableDataMap.values()];
|
||||
};
|
||||
const changeTable = (value: string) => {
|
||||
unSelectKeys.value = value;
|
||||
tableDataMap.delete(value);
|
||||
tableData.value = [...tableDataMap.values()];
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.content {
|
||||
display: flex;
|
||||
min-height: 600px;
|
||||
.tree {
|
||||
width: 300px;
|
||||
}
|
||||
.table {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template lang="">
|
||||
<j-modal
|
||||
title="批量编辑"
|
||||
:visible="true"
|
||||
width="700px"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="sizeText">
|
||||
将批量修改{{
|
||||
data.length
|
||||
}}条数据的访问类型、采集频率、只推送变化的数据
|
||||
</div>
|
||||
<j-form
|
||||
class="form"
|
||||
layout="vertical"
|
||||
:model="formData"
|
||||
name="basic"
|
||||
autocomplete="off"
|
||||
ref="formRef"
|
||||
>
|
||||
<j-form-item label="访问类型" name="accessModes">
|
||||
<j-checkbox-group
|
||||
v-model:value="formData.accessModes"
|
||||
:options="[
|
||||
{ label: '读', value: 'read' },
|
||||
{ label: '写', value: 'write' },
|
||||
{ label: '订阅', value: 'subscribe' },
|
||||
]"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item :name="['interval']">
|
||||
<template #label>
|
||||
<span>
|
||||
采集频率
|
||||
<j-tooltip title="采集频率为0时不执行轮询任务">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<j-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入采集频率"
|
||||
v-model:value="formData.interval"
|
||||
:min="1"
|
||||
addon-after="ms"
|
||||
/>
|
||||
</j-form-item>
|
||||
|
||||
<a-form-item label="" :name="['features']">
|
||||
<a-checkbox-group v-model:value="formData.features">
|
||||
<a-checkbox value="changedOnly" name="type"
|
||||
>只推送变化的数据</a-checkbox
|
||||
>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</j-form>
|
||||
<template #footer>
|
||||
<j-button key="back" @click="handleCancel">取消</j-button>
|
||||
<PermissionButton
|
||||
key="submit"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleOk"
|
||||
style="margin-left: 8px"
|
||||
:hasPermission="`DataCollect/Collector:update`"
|
||||
>
|
||||
确认
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { savePointBatch } from '@/api/data-collect/collector';
|
||||
import { Rule } from 'ant-design-vue/lib/form';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const formData = ref({
|
||||
accessModes: [],
|
||||
interval: '',
|
||||
features: [],
|
||||
});
|
||||
|
||||
const checkLength = (_rule: Rule, value: string): Promise<any> =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
if (value) {
|
||||
return String(value).length > 64
|
||||
? reject('最多可输入64个字符')
|
||||
: resolve('');
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmit = async () => {
|
||||
const data = await formRef.value?.validate();
|
||||
const { accessModes, features, interval } = data;
|
||||
const ischange =
|
||||
accessModes.length !== 0 || features.length !== 0 || !!interval;
|
||||
if (ischange) {
|
||||
const params = cloneDeep(props.data);
|
||||
params.forEach((i: any) => {
|
||||
accessModes.length !== 0 && (i.accessModes = data.accessModes);
|
||||
features.length !== 0 && (i.features = data.features);
|
||||
if (!!interval) {
|
||||
i.interval = data.interval;
|
||||
i.configuration = {
|
||||
...i.configuration,
|
||||
interval: data.interval,
|
||||
};
|
||||
}
|
||||
});
|
||||
loading.value = true;
|
||||
const response = await savePointBatch(params);
|
||||
if (response.status === 200) {
|
||||
emit('change', true);
|
||||
}
|
||||
loading.value = false;
|
||||
} else {
|
||||
emit('change', true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
onSubmit();
|
||||
};
|
||||
const handleCancel = () => {
|
||||
emit('change', false);
|
||||
};
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sizeText {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
|
@ -1,10 +1,6 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<div
|
||||
class="card-warp"
|
||||
:class="{ active: active ? 'active' : '' }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div class="card-warp" :class="{ active: active ? 'active' : '' }">
|
||||
<div class="card-content">
|
||||
<div class="card-header">
|
||||
<div class="card-header-left">
|
||||
|
@ -15,7 +11,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex">
|
||||
<div
|
||||
class="card-item"
|
||||
style="display: flex"
|
||||
@click="handleClick"
|
||||
>
|
||||
<!-- 图片 -->
|
||||
<div class="card-item-avatar">
|
||||
<slot name="img"> </slot>
|
||||
|
@ -169,7 +169,7 @@ const handleClick = () => {
|
|||
border: 1px solid #e6e6e6;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
// cursor: pointer;
|
||||
box-shadow: 0 0 24px rgba(#000, 0.1);
|
||||
|
||||
.card-mask {
|
||||
|
@ -214,6 +214,9 @@ const handleClick = () => {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.card-item-avatar {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
<template lang="">
|
||||
<j-modal title="写入" :visible="true" width="500px" @cancel="handleCancel">
|
||||
<j-form
|
||||
class="form"
|
||||
layout="vertical"
|
||||
:model="formData"
|
||||
name="basic"
|
||||
autocomplete="off"
|
||||
ref="formRef"
|
||||
>
|
||||
<j-form-item
|
||||
:label="data.name"
|
||||
name="value"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: `请输入${data.name}`,
|
||||
},
|
||||
]"
|
||||
v-if="
|
||||
data.provider === 'MODBUS_TCP' &&
|
||||
data?.configuration.function === 'Coils'
|
||||
"
|
||||
>
|
||||
<j-textarea
|
||||
placeholder="请输入"
|
||||
v-model:value="formData.value"
|
||||
:maxlength="200"
|
||||
:rows="3"
|
||||
showCount
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
:label="data.name"
|
||||
name="value"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: `请输入${data.name}`,
|
||||
},
|
||||
]"
|
||||
v-else
|
||||
>
|
||||
<j-input-number
|
||||
v-if="valueTypeArray.includes(valueType)"
|
||||
style="width: 100%"
|
||||
placeholder="请输入"
|
||||
v-model:value="formData.value"
|
||||
/>
|
||||
<j-select
|
||||
v-else-if="['boolean'].includes(valueType)"
|
||||
style="width: 100%"
|
||||
v-model:value="formData.value"
|
||||
:options="[
|
||||
{
|
||||
label: '是',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: false,
|
||||
},
|
||||
]"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
<j-date-picker
|
||||
v-else-if="['datetime'].includes(valueType)"
|
||||
style="width: 100%"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
show-time
|
||||
placeholder="请选择"
|
||||
@change="onChange"
|
||||
/>
|
||||
|
||||
<j-input
|
||||
v-else
|
||||
placeholder="请输入"
|
||||
v-model:value="formData.value"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
<template #footer>
|
||||
<j-button key="back" @click="handleCancel">取消</j-button>
|
||||
<PermissionButton
|
||||
key="submit"
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleOk"
|
||||
style="margin-left: 8px"
|
||||
:hasPermission="`DataCollect/Collector:update`"
|
||||
>
|
||||
确认
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import { writePoint } from '@/api/data-collect/collector';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const valueType: string = (
|
||||
props.data?.provider === 'OPC_UA'
|
||||
? props?.data?.configuration?.type || 'Number'
|
||||
: props.data?.configuration?.codec?.provider || 'int8'
|
||||
).toLocaleLowerCase();
|
||||
|
||||
const valueTypeArray = [
|
||||
'int8',
|
||||
'int16',
|
||||
'int32',
|
||||
'ieee754_float',
|
||||
'ieee754_double',
|
||||
'hex,',
|
||||
'number',
|
||||
];
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const collectorId = props.data.collectorId;
|
||||
const pointId = props.data.id;
|
||||
|
||||
const formData = ref({
|
||||
value: '',
|
||||
});
|
||||
|
||||
const onChange = (value: Dayjs, dateString: string) => {
|
||||
formData.value.value = dateString;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
const data = await formRef.value?.validate();
|
||||
const params = {
|
||||
...data,
|
||||
pointId,
|
||||
};
|
||||
loading.value = true;
|
||||
const response = await writePoint(collectorId, [params]);
|
||||
if (response.status === 200) {
|
||||
emit('change', true);
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
onSubmit();
|
||||
};
|
||||
const handleCancel = () => {
|
||||
emit('change', false);
|
||||
};
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<j-spin :spinning="spinning">
|
||||
<j-advanced-search
|
||||
:columns="columns"
|
||||
target="search"
|
||||
|
@ -13,9 +13,7 @@
|
|||
:gridColumn="2"
|
||||
:gridColumns="[1, 2]"
|
||||
:request="queryPoint"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'id', order: 'desc' }],
|
||||
}"
|
||||
:defaultParams="defaultParams"
|
||||
:params="params"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
|
@ -26,12 +24,23 @@
|
|||
<template #headerTitle>
|
||||
<j-space>
|
||||
<PermissionButton
|
||||
v-if="data?.provider !== 'OPC_UA'"
|
||||
type="primary"
|
||||
@click="handlAdd"
|
||||
hasPermission="DataCollect/Collector:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
{{ data?.provider === 'OPC_UA' ? '扫描' : '新增点位' }}
|
||||
新增点位
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
v-if="data?.provider === 'OPC_UA'"
|
||||
type="primary"
|
||||
@click="handlScan"
|
||||
hasPermission="DataCollect/Collector:add"
|
||||
>
|
||||
<template #icon><AIcon type="PlusOutlined" /></template>
|
||||
扫描
|
||||
</PermissionButton>
|
||||
<j-dropdown v-if="data?.provider === 'OPC_UA'">
|
||||
<j-button
|
||||
|
@ -42,6 +51,7 @@
|
|||
<j-menu-item>
|
||||
<PermissionButton
|
||||
hasPermission="DataCollect/Collector:update"
|
||||
@click="handlBatchUpdate()"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="FormOutlined"
|
||||
|
@ -52,6 +62,10 @@
|
|||
<j-menu-item>
|
||||
<PermissionButton
|
||||
hasPermission="DataCollect/Collector:delete"
|
||||
:popConfirm="{
|
||||
title: `确定删除?`,
|
||||
onConfirm: () => handlDelete(),
|
||||
}"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon type="EditOutlined"
|
||||
|
@ -75,17 +89,6 @@
|
|||
:statusText="getState(slotProps)?.text"
|
||||
:statusNames="Object.fromEntries(colorMap.entries())"
|
||||
>
|
||||
<!-- <PointCardBox
|
||||
:showStatus="true"
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps)"
|
||||
:active="_selectedRowKeys.includes(slotProps.id)"
|
||||
v-bind="slotProps"
|
||||
class="card-box"
|
||||
:status="getState(slotProps).value"
|
||||
:statusText="slotProps.runningState?.text"
|
||||
:statusNames="Object.fromEntries(colorMap.entries())"
|
||||
> -->
|
||||
<template #title>
|
||||
<slot name="title">
|
||||
<div class="card-box-title">
|
||||
|
@ -95,8 +98,19 @@
|
|||
</template>
|
||||
<template #action>
|
||||
<div class="card-box-action">
|
||||
<a><AIcon type="DeleteOutlined" /></a>
|
||||
<a><AIcon type="FormOutlined" /></a>
|
||||
<a>
|
||||
<j-popconfirm
|
||||
title="确定删除?"
|
||||
@confirm="handlDelete(slotProps.id)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</j-popconfirm>
|
||||
</a>
|
||||
<a
|
||||
><AIcon
|
||||
@click="handlEdit(slotProps)"
|
||||
type="FormOutlined"
|
||||
/></a>
|
||||
</div>
|
||||
</template>
|
||||
<template #img>
|
||||
|
@ -112,8 +126,26 @@
|
|||
<div class="card-box-content">
|
||||
<div class="card-box-content-left">
|
||||
<span>--</span>
|
||||
<a><AIcon type="EditOutlined" /></a>
|
||||
<a><AIcon type="RedoOutlined" /></a>
|
||||
<a
|
||||
v-if="
|
||||
getAccessModes(slotProps).includes(
|
||||
'write',
|
||||
)
|
||||
"
|
||||
><AIcon
|
||||
@click.stop="clickEdit(slotProps)"
|
||||
type="EditOutlined"
|
||||
/></a>
|
||||
<a
|
||||
v-if="
|
||||
getAccessModes(slotProps).includes(
|
||||
'read',
|
||||
)
|
||||
"
|
||||
><AIcon
|
||||
@click.stop="clickRedo(slotProps)"
|
||||
type="RedoOutlined"
|
||||
/></a>
|
||||
</div>
|
||||
<div class="card-box-content-right">
|
||||
<div
|
||||
|
@ -135,21 +167,46 @@
|
|||
</template>
|
||||
</j-pro-table>
|
||||
<SaveModBus
|
||||
v-if="visibleSaveModBus"
|
||||
v-if="visible.saveModBus"
|
||||
:data="current"
|
||||
@change="saveChange"
|
||||
/>
|
||||
</div>
|
||||
<SaveOPCUA
|
||||
v-if="visible.saveOPCUA"
|
||||
:data="current"
|
||||
@change="saveChange"
|
||||
/>
|
||||
<WritePoint
|
||||
v-if="visible.writePoint"
|
||||
:data="current"
|
||||
@change="saveChange"
|
||||
/>
|
||||
<BatchUpdate
|
||||
v-if="visible.batchUpdate"
|
||||
:data="current"
|
||||
@change="saveChange"
|
||||
/>
|
||||
<Scan v-if="visible.scan" :data="current" @change="saveChange" />
|
||||
</j-spin>
|
||||
</template>
|
||||
<script lang="ts" setup name="PointPage">
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { queryPoint } from '@/api/data-collect/collector';
|
||||
import {
|
||||
queryPoint,
|
||||
batchDeletePoint,
|
||||
removePoint,
|
||||
readPoint,
|
||||
} from '@/api/data-collect/collector';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useMenuStore } from 'store/menu';
|
||||
import PointCardBox from './components/PointCardBox/index.vue';
|
||||
import { colorMap, getState } from '../data.ts';
|
||||
import WritePoint from './components/WritePoint/index.vue';
|
||||
import BatchUpdate from './components/BatchUpdate/index.vue';
|
||||
import SaveModBus from './Save/SaveModBus.vue';
|
||||
import SaveOPCUA from './Save/SaveOPCUA.vue';
|
||||
import Scan from './Scan/index.vue';
|
||||
import { colorMap, getState } from '../data.ts';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
@ -163,11 +220,35 @@ const tableRef = ref<Record<string, any>>({});
|
|||
const params = ref<Record<string, any>>({});
|
||||
const opcImage = getImage('/DataCollect/device-opcua.png');
|
||||
const modbusImage = getImage('/DataCollect/device-modbus.png');
|
||||
const visibleSaveModBus = ref(false);
|
||||
const visible = reactive({
|
||||
saveModBus: false,
|
||||
saveOPCUA: false,
|
||||
writePoint: false,
|
||||
batchUpdate: false,
|
||||
scan: false,
|
||||
});
|
||||
const current = ref({});
|
||||
const accessModesOption = ref();
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
|
||||
const spinning = ref(false);
|
||||
const collectorId = ref(props.data.id);
|
||||
|
||||
const defaultParams = ref({
|
||||
sorts: [{ name: 'id', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'collectorId',
|
||||
value: collectorId.value,
|
||||
// value: '1610517928766550016', //测试
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const accessModesMODBUS_TCP = [
|
||||
{
|
||||
label: '读',
|
||||
|
@ -179,11 +260,6 @@ const accessModesMODBUS_TCP = [
|
|||
},
|
||||
];
|
||||
|
||||
const accessModesOPC_UA = accessModesMODBUS_TCP.concat({
|
||||
label: '订阅',
|
||||
value: 'subscribe',
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '点位名称',
|
||||
|
@ -256,81 +332,57 @@ const columns = [
|
|||
},
|
||||
];
|
||||
|
||||
// const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
||||
// if (!data) return [];
|
||||
// const state = data.state.value;
|
||||
// const stateText = state === 'enabled' ? '禁用' : '启用';
|
||||
// const actions = [
|
||||
// {
|
||||
// key: 'update',
|
||||
// text: '编辑',
|
||||
// tooltip: {
|
||||
// title: '编辑',
|
||||
// },
|
||||
// icon: 'EditOutlined',
|
||||
// onClick: () => {
|
||||
// handlEdit(data.id);
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// key: 'action',
|
||||
// text: stateText,
|
||||
// tooltip: {
|
||||
// title: stateText,
|
||||
// },
|
||||
// icon: state === 'enabled' ? 'StopOutlined' : 'CheckCircleOutlined',
|
||||
// popConfirm: {
|
||||
// title: `确认${stateText}?`,
|
||||
// onConfirm: async () => {
|
||||
// let res =
|
||||
// state === 'enabled'
|
||||
// ? await disable(data.id)
|
||||
// : await enalbe(data.id);
|
||||
// if (res.success) {
|
||||
// message.success('操作成功');
|
||||
// tableRef.value?.reload();
|
||||
// } else {
|
||||
// message.error('操作失败!');
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// key: 'delete',
|
||||
// text: '删除',
|
||||
// disabled: state === 'enabled',
|
||||
// tooltip: {
|
||||
// title: state === 'enabled' ? '正常的流媒体不能删除' : '删除',
|
||||
// },
|
||||
// popConfirm: {
|
||||
// title: '确认删除?',
|
||||
// onConfirm: async () => {
|
||||
// const res = await remove(data.id);
|
||||
// if (res.success) {
|
||||
// message.success('操作成功');
|
||||
// tableRef.value.reload();
|
||||
// } else {
|
||||
// message.error('操作失败!');
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// icon: 'DeleteOutlined',
|
||||
// },
|
||||
// ];
|
||||
// return actions;
|
||||
// };
|
||||
|
||||
const handlAdd = () => {
|
||||
visibleSaveModBus.value = true;
|
||||
visible.saveModBus = true;
|
||||
current.value = {
|
||||
treeId: props.data.id,
|
||||
collectorId: collectorId.value,
|
||||
provider: props.data?.provider || 'MODBUS_TCP',
|
||||
};
|
||||
};
|
||||
const handlEdit = (id: string) => {
|
||||
// menuStory.jumpPage(`media/Stream/Detail`, { id }, { view: false });
|
||||
const handlEdit = (data: Object) => {
|
||||
if (data?.provider === 'OPC_UA') {
|
||||
visible.saveOPCUA = true;
|
||||
} else {
|
||||
visible.saveModBus = true;
|
||||
}
|
||||
current.value = cloneDeep(data);
|
||||
};
|
||||
const handlEye = (id: string) => {
|
||||
// menuStory.jumpPage(`media/Stream/Detail`, { id }, { view: true });
|
||||
const handlDelete = async (data: string | undefined = undefined) => {
|
||||
spinning.value = true;
|
||||
const res = !data
|
||||
? await batchDeletePoint(_selectedRowKeys.value)
|
||||
: await removePoint(data as string);
|
||||
if (res.status === 200) {
|
||||
cancelSelect();
|
||||
tableRef.value?.reload();
|
||||
message.success('操作成功');
|
||||
}
|
||||
spinning.value = false;
|
||||
};
|
||||
const handlBatchUpdate = () => {
|
||||
const dataSet = new Set(_selectedRowKeys.value);
|
||||
const dataMap = new Map();
|
||||
tableRef?.value?._dataSource.forEach((i) => {
|
||||
dataSet.has(i.id) && dataMap.set(i.id, i);
|
||||
});
|
||||
current.value = [...dataMap.values()];
|
||||
visible.batchUpdate = true;
|
||||
};
|
||||
const handlScan = () => {
|
||||
visible.scan = true;
|
||||
current.value = cloneDeep(props.data);
|
||||
};
|
||||
const clickEdit = async (data: object) => {
|
||||
visible.writePoint = true;
|
||||
current.value = cloneDeep(data);
|
||||
};
|
||||
const clickRedo = async (data: object) => {
|
||||
const res = await readPoint(data?.collectorId, [data?.id]);
|
||||
if (res.status === 200) {
|
||||
cancelSelect();
|
||||
tableRef.value?.reload();
|
||||
message.success('操作成功');
|
||||
}
|
||||
};
|
||||
|
||||
const getQuantity = (item: Partial<Record<string, any>>) => {
|
||||
|
@ -345,11 +397,9 @@ const getScaleFactor = (item: Partial<Record<string, any>>) => {
|
|||
const { scaleFactor } = item.configuration?.codec?.configuration || '';
|
||||
return !!scaleFactor ? scaleFactor + '(缩放因子)' : '';
|
||||
};
|
||||
|
||||
const getRight1 = (item: Partial<Record<string, any>>) => {
|
||||
return !!getQuantity(item) || getAddress(item) || getScaleFactor(item);
|
||||
};
|
||||
|
||||
const getText = (item: Partial<Record<string, any>>) => {
|
||||
return (item?.accessModes || []).map((i) => i?.text).join(',');
|
||||
};
|
||||
|
@ -357,22 +407,18 @@ const getInterval = (item: Partial<Record<string, any>>) => {
|
|||
const { interval } = item.configuration || '';
|
||||
return !!interval ? '采集频率' + interval + 'ms' : '';
|
||||
};
|
||||
|
||||
const getaccessModesOption = () => {
|
||||
return props.data?.provider !== 'MODBUS_TCP'
|
||||
? accessModesMODBUS_TCP.concat({
|
||||
label: '订阅',
|
||||
value: 'subscribe',
|
||||
})
|
||||
: accessModesMODBUS_TCP;
|
||||
const getAccessModes = (item: Partial<Record<string, any>>) => {
|
||||
return item?.accessModes?.map((i) => i?.value);
|
||||
};
|
||||
|
||||
const saveChange = (value: object) => {
|
||||
visibleSaveModBus.value = false;
|
||||
for (let key in visible) {
|
||||
visible[key] = false;
|
||||
}
|
||||
current.value = {};
|
||||
if (value) {
|
||||
handleSearch(params.value);
|
||||
// message.success('操作成功');
|
||||
tableRef.value?.reload();
|
||||
message.success('操作成功');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -404,22 +450,12 @@ watch(
|
|||
label: '订阅',
|
||||
value: 'subscribe',
|
||||
});
|
||||
|
||||
params.value = {
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'collectorId',
|
||||
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();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -435,9 +471,11 @@ const handleSearch = (e: any) => {
|
|||
min-width: 480px;
|
||||
a {
|
||||
color: #474747;
|
||||
z-index: 1;
|
||||
}
|
||||
a:hover {
|
||||
color: #315efb;
|
||||
z-index: 1;
|
||||
}
|
||||
.card-box-title {
|
||||
font-size: 18px;
|
||||
|
@ -459,7 +497,10 @@ const handleSearch = (e: any) => {
|
|||
height: 68px;
|
||||
padding-right: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
a {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.card-box-content-right {
|
||||
flex: 0.8;
|
||||
|
|
|
@ -201,7 +201,6 @@ const onSubmit = () => {
|
|||
};
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const response = !id
|
||||
? await save(params)
|
||||
: await update(id, { ...props.data, ...params });
|
||||
|
@ -238,6 +237,10 @@ const getChannelNoPaging = async () => {
|
|||
};
|
||||
getChannelNoPaging();
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => formData.value.channelId,
|
||||
(value) => {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
新增采集器
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<a-spin :spinning="spinning">
|
||||
<j-spin :spinning="spinning">
|
||||
<a-tree
|
||||
:tree-data="defualtDataSource"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
|
@ -92,7 +92,7 @@
|
|||
</template>
|
||||
</a-tree>
|
||||
<j-empty v-else description="暂无数据" />
|
||||
</a-spin>
|
||||
</j-spin>
|
||||
<Save v-if="visible" :data="current" @change="saveChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -16,3 +16,166 @@ export const getState = (record: any) => {
|
|||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const regOnlyNumber = new RegExp(/^\d+$/);
|
||||
|
||||
export const checkProviderData = {
|
||||
int8: 1,
|
||||
int16: 2,
|
||||
int32: 4,
|
||||
int64: 8,
|
||||
ieee754_float: 4,
|
||||
ieee754_double: 8,
|
||||
hex: 1,
|
||||
};
|
||||
|
||||
export const ModBusRules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入点位名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
],
|
||||
function: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择功能码',
|
||||
},
|
||||
],
|
||||
pointKey: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入地址',
|
||||
},
|
||||
{
|
||||
pattern: regOnlyNumber,
|
||||
message: '请输入0-255之间的正整数',
|
||||
},
|
||||
],
|
||||
quantity: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入寄存器数量',
|
||||
},
|
||||
{
|
||||
pattern: regOnlyNumber,
|
||||
message: '请输入1-255之间的正整数',
|
||||
},
|
||||
],
|
||||
provider: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据类型',
|
||||
},
|
||||
],
|
||||
scaleFactor: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入缩放因子',
|
||||
},
|
||||
],
|
||||
accessModes: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择访问类型',
|
||||
},
|
||||
],
|
||||
writeByteCount: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择是否写入数据区长度',
|
||||
},
|
||||
],
|
||||
byteCount: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入自定义数据区长度(byte)',
|
||||
},
|
||||
],
|
||||
interval: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入采集频率',
|
||||
},
|
||||
],
|
||||
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
};
|
||||
|
||||
export const OPCUARules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入点位名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择数据类型',
|
||||
},
|
||||
],
|
||||
accessModes: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择访问类型',
|
||||
},
|
||||
],
|
||||
interval: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入采集频率',
|
||||
},
|
||||
],
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
};
|
||||
|
||||
export const FormTableColumns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
},
|
||||
{
|
||||
title: 'nodeId',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '访问类型',
|
||||
dataIndex: 'accessModes',
|
||||
key: 'accessModes',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '采集频率',
|
||||
key: 'interval',
|
||||
dataIndex: 'interval',
|
||||
width: 280,
|
||||
},
|
||||
{
|
||||
title: '只推送变化的数据',
|
||||
key: 'features',
|
||||
dataIndex: 'features',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
dataIndex: 'action',
|
||||
|
||||
fixed: 'right',
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<Tree @change="changeTree" />
|
||||
</div>
|
||||
<div class="right">
|
||||
<Point :data="data"></Point>
|
||||
<Point v-if="!!data" :data="data" />
|
||||
</div>
|
||||
</div>
|
||||
</page-container>
|
||||
|
@ -32,7 +32,6 @@ const changeTree = (row: any) => {
|
|||
.left {
|
||||
width: 300px;
|
||||
border-right: 1px #eeeeee solid;
|
||||
// padding: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
.right {
|
||||
|
|
|
@ -1693,7 +1693,7 @@ const Status = defineComponent({
|
|||
/>
|
||||
)}
|
||||
{
|
||||
bindParentVisible && (
|
||||
bindParentVisible.value && (
|
||||
<BindParentDevice
|
||||
data={device.value}
|
||||
onCancel={() => {
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
instanceStore.current.deviceType?.text
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="固件版本">{{
|
||||
instanceStore.current.firmwareInfo?.version
|
||||
instanceStore.current?.firmwareInfo?.version
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="连接协议">{{
|
||||
instanceStore.current?.protocolName
|
||||
instanceStore.current?.transport
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="消息协议">{{
|
||||
instanceStore.current.transport
|
||||
instanceStore.current.protocolName
|
||||
}}</j-descriptions-item>
|
||||
<j-descriptions-item label="创建时间">{{
|
||||
instanceStore.current.createTime
|
||||
|
|
|
@ -109,7 +109,9 @@ const loading = ref<boolean>(false);
|
|||
const instanceStore = useInstanceStore();
|
||||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({
|
||||
const modelRef = reactive<{
|
||||
metrics: any[]
|
||||
}>({
|
||||
metrics: [],
|
||||
});
|
||||
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
:tab="i.tab"
|
||||
/>
|
||||
</j-tabs>
|
||||
<JEmpty v-else style="margin: 250px 0" />
|
||||
<JEmpty v-else style="margin: 180px 0" />
|
||||
</div>
|
||||
<div class="property-box-right">
|
||||
<Event v-if="type === 'event'" :data="data" />
|
||||
<Property v-else :data="properties" />
|
||||
<Property v-else-if="type === 'property'" :data="properties" />
|
||||
<JEmpty v-else style="margin: 220px 0" />
|
||||
</div>
|
||||
</div>
|
||||
</j-card>
|
||||
|
@ -97,6 +98,13 @@ const onSearch = () => {
|
|||
} else {
|
||||
tabList.value = _.cloneDeep(arr)
|
||||
}
|
||||
const dt = tabList.value?.[0]
|
||||
if (dt) {
|
||||
data.value = dt
|
||||
type.value = dt.type;
|
||||
} else {
|
||||
type.value = ''
|
||||
}
|
||||
};
|
||||
const tabChange = (key: string) => {
|
||||
const dt = tabList.value.find((i) => i.key === key);
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
<div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<AIcon type="ArrowLeftOutlined" @click="onBack" />
|
||||
<div style="margin-left: 20px">{{ instanceStore.current.name }}</div>
|
||||
<div style="margin-left: 20px">
|
||||
{{ instanceStore.current.name }}
|
||||
</div>
|
||||
<j-divider type="vertical" />
|
||||
<j-space>
|
||||
<j-badge
|
||||
|
@ -79,7 +81,7 @@
|
|||
<j-descriptions-item label="所属产品">
|
||||
<PermissionButton
|
||||
type="link"
|
||||
style="margin-top: -5px; padding: 0"
|
||||
style="margin-top: -5px; padding: 0"
|
||||
@click="jumpProduct"
|
||||
hasPermission="device/Product:view"
|
||||
>
|
||||
|
@ -116,8 +118,8 @@ import Function from './Function/index.vue';
|
|||
import Modbus from './Modbus/index.vue';
|
||||
import OPCUA from './OPCUA/index.vue';
|
||||
import EdgeMap from './EdgeMap/index.vue';
|
||||
import Parsing from './Parsing/index.vue'
|
||||
import Log from './Log/index.vue'
|
||||
import Parsing from './Parsing/index.vue';
|
||||
import Log from './Log/index.vue';
|
||||
import { _deploy, _disconnect } from '@/api/device/instance';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
@ -149,17 +151,13 @@ const list = ref([
|
|||
key: 'Metadata',
|
||||
tab: '物模型',
|
||||
},
|
||||
{
|
||||
key: 'Log',
|
||||
tab: '日志管理',
|
||||
},
|
||||
{
|
||||
key: 'Function',
|
||||
tab: '设备功能',
|
||||
},
|
||||
{
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备',
|
||||
key: 'Log',
|
||||
tab: '日志管理',
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -174,7 +172,7 @@ const tabs = {
|
|||
OPCUA,
|
||||
EdgeMap,
|
||||
Parsing,
|
||||
Log
|
||||
Log,
|
||||
};
|
||||
|
||||
const getStatus = (id: string) => {
|
||||
|
@ -255,6 +253,17 @@ watchEffect(() => {
|
|||
tab: '设备诊断',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.features?.find(
|
||||
(item: any) => item.id === 'transparentCodec',
|
||||
) &&
|
||||
!keys.includes('Parsing')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Parsing',
|
||||
tab: '数据解析',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.protocol === 'modbus-tcp' &&
|
||||
!keys.includes('Modbus')
|
||||
|
@ -273,6 +282,13 @@ watchEffect(() => {
|
|||
tab: 'OPC UA',
|
||||
});
|
||||
}
|
||||
if (instanceStore.current.deviceType?.value === 'gateway') {
|
||||
// 产品类型为网关的情况下才显示此模块
|
||||
list.value.push({
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.accessProvider === 'edge-child-device' &&
|
||||
instanceStore.current.parentId &&
|
||||
|
@ -283,15 +299,6 @@ watchEffect(() => {
|
|||
tab: '边缘端映射',
|
||||
});
|
||||
}
|
||||
if (
|
||||
instanceStore.current.features?.find((item: any) => item.id === 'transparentCodec') &&
|
||||
!keys.includes('Parsing')
|
||||
) {
|
||||
list.value.push({
|
||||
key: 'Parsing',
|
||||
tab: '数据解析',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
import { queryNoPagingPost } from '@/api/device/product';
|
||||
import { downloadFile } from '@/utils/utils';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import { BASE_API_PATH } from '@/utils/variable';
|
||||
import { deviceExport } from '@/api/device/instance';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
|
|
@ -1,92 +1,106 @@
|
|||
<template>
|
||||
<j-modal :maskClosable="false" width="800px" :visible="true" title="当前进度" @ok="handleCancel" @cancel="handleCancel">
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
width="800px"
|
||||
:visible="true"
|
||||
title="当前进度"
|
||||
@ok="handleCancel"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div>
|
||||
<j-badge v-if="flag" status="processing" text="进行中" />
|
||||
<j-badge v-else status="success" text="已完成" />
|
||||
<j-badge v-else status="success" text="已完成" />
|
||||
</div>
|
||||
<p>总数量:{{count}}</p>
|
||||
<a style="color: red">{{errMessage}}</a>
|
||||
<p>总数量:{{ count }}</p>
|
||||
<a style="color: red">{{ errMessage }}</a>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill'
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
api: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const eventSource = ref<Record<string, any>>({})
|
||||
const count = ref<number>(0)
|
||||
const flag = ref<boolean>(false)
|
||||
const errMessage = ref<string>('')
|
||||
const isSource = ref<boolean>(false)
|
||||
const id = ref<string>('')
|
||||
const source = ref<Record<string, any>>({})
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
// const eventSource = ref<Record<string, any>>({})
|
||||
const count = ref<number>(0);
|
||||
const flag = ref<boolean>(false);
|
||||
const errMessage = ref<string>('');
|
||||
const isSource = ref<boolean>(false);
|
||||
const id = ref<string>('');
|
||||
const source = ref<Record<string, any>>({});
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('close')
|
||||
}
|
||||
emit('close');
|
||||
emit('save');
|
||||
};
|
||||
|
||||
// const handleOk = () => {
|
||||
// emit('close');
|
||||
// emit('save');
|
||||
// };
|
||||
|
||||
const getData = (api: string) => {
|
||||
let dt = 0
|
||||
const _source = new EventSourcePolyfill(api)
|
||||
source.value = _source
|
||||
let dt = 0;
|
||||
const _source = new EventSourcePolyfill(api);
|
||||
source.value = _source;
|
||||
_source.onmessage = (e: any) => {
|
||||
const res = JSON.parse(e.data);
|
||||
switch (props.type) {
|
||||
case 'active':
|
||||
if (res.success) {
|
||||
dt += res.total;
|
||||
count.value = dt
|
||||
} else {
|
||||
if (res.source) {
|
||||
const msg = `${res.source.name}: ${res.message}`;
|
||||
errMessage.value = msg
|
||||
id.value = res.source.id
|
||||
isSource.value = true
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sync':
|
||||
dt += res;
|
||||
count.value = dt
|
||||
break;
|
||||
case 'import':
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt
|
||||
} else {
|
||||
errMessage.value = res.message
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const res = JSON.parse(e.data);
|
||||
switch (props.type) {
|
||||
case 'active':
|
||||
if (res.success) {
|
||||
dt += res.total;
|
||||
count.value = dt;
|
||||
} else {
|
||||
if (res.source) {
|
||||
const msg = `${res.source.name}: ${res.message}`;
|
||||
errMessage.value = msg;
|
||||
id.value = res.source.id;
|
||||
isSource.value = true;
|
||||
} else {
|
||||
errMessage.value = res.message;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'sync':
|
||||
dt += res;
|
||||
count.value = dt;
|
||||
break;
|
||||
case 'import':
|
||||
if (res.success) {
|
||||
const temp = res.result.total;
|
||||
dt += temp;
|
||||
count.value = dt;
|
||||
} else {
|
||||
errMessage.value = res.message;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
_source.onerror = () => {
|
||||
flag.value = false
|
||||
_source.close();
|
||||
flag.value = false;
|
||||
_source.close();
|
||||
};
|
||||
_source.onopen = () => {};
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.api,
|
||||
watch(
|
||||
() => props.api,
|
||||
(newValue) => {
|
||||
if(newValue) {
|
||||
getData(newValue)
|
||||
if (newValue) {
|
||||
getData(newValue);
|
||||
}
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
)
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
</script>
|
|
@ -9,12 +9,7 @@
|
|||
:confirmLoading="loading"
|
||||
>
|
||||
<div style="margin-top: 10px">
|
||||
<j-form
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:rules="rules"
|
||||
:model="modelRef"
|
||||
>
|
||||
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
|
||||
<j-row type="flex">
|
||||
<j-col flex="180px">
|
||||
<j-form-item name="photoUrl">
|
||||
|
@ -22,14 +17,33 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col flex="auto">
|
||||
<j-form-item name="id">
|
||||
<j-form-item
|
||||
name="id"
|
||||
:rules="[
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
ID
|
||||
<j-tooltip title="若不填写,系统将自动生成唯一ID">
|
||||
<j-tooltip
|
||||
title="若不填写,系统将自动生成唯一ID"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -39,7 +53,20 @@
|
|||
:disabled="!!data?.id"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item label="名称" name="name">
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="modelRef.name"
|
||||
placeholder="请输入名称"
|
||||
|
@ -47,13 +74,23 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item name="productId">
|
||||
<j-form-item
|
||||
name="productId"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>所属产品
|
||||
<span
|
||||
>所属产品
|
||||
<j-tooltip title="只能选择“正常”状态的产品">
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -68,10 +105,20 @@
|
|||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
>{{item.name}}</j-select-option>
|
||||
>{{ item.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="说明" name="describe">
|
||||
<j-form-item
|
||||
label="说明"
|
||||
name="describe"
|
||||
:rules="[
|
||||
{
|
||||
max: 200,
|
||||
message: '最多输入200个字符'
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-textarea
|
||||
v-model:value="modelRef.describe"
|
||||
placeholder="请输入说明"
|
||||
|
@ -123,45 +170,6 @@ const vailId = async (_: Record<string, any>, value: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
],
|
||||
photoUrl: [
|
||||
{
|
||||
required: true,
|
||||
message: '请上传图标',
|
||||
},
|
||||
],
|
||||
productId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所属产品',
|
||||
},
|
||||
],
|
||||
id: [
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
{
|
||||
pattern: /^[j-zA-Z0-9_\-]+$/,
|
||||
message: '请输入英文或者数字或者-或者_',
|
||||
},
|
||||
{
|
||||
validator: vailId,
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(newValue) => {
|
||||
|
@ -199,13 +207,13 @@ const handleSave = () => {
|
|||
.validate()
|
||||
.then(async (_data: any) => {
|
||||
loading.value = true;
|
||||
const obj = {...toRaw(modelRef), ..._data}
|
||||
if(!obj.id){
|
||||
delete obj.id
|
||||
const obj = { ..._data };
|
||||
if (!obj.id) {
|
||||
delete obj.id;
|
||||
}
|
||||
const resp = await update(obj).finally(() => {
|
||||
loading.value = false;
|
||||
})
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
emit('save');
|
||||
|
|
|
@ -252,6 +252,7 @@
|
|||
@close="operationVisible = false"
|
||||
:api="api"
|
||||
:type="type"
|
||||
@save="onRefresh"
|
||||
/>
|
||||
<Save
|
||||
v-if="visible"
|
||||
|
@ -314,6 +315,7 @@ const columns = [
|
|||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
defaultTermType: 'eq'
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -322,6 +324,7 @@ const columns = [
|
|||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
first: true
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -389,6 +392,7 @@ const columns = [
|
|||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
rename: 'productId$product-info',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
getProviders().then((resp: any) => {
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
</j-radio-group>
|
||||
</j-form-item>
|
||||
<j-form-item label="输入参数" name="inputs" :rules="[
|
||||
{ required: true, message: '请输入输入参数' },
|
||||
{ required: true, validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, '输入参数') },
|
||||
]">
|
||||
<JsonParam v-model:value="value.inputs" :name="['inputs']"></JsonParam>
|
||||
</j-form-item>
|
||||
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数"></value-type-form>
|
||||
<value-type-form :name="['output']" v-model:value="value.output" key="function" title="输出参数" :required="false"></value-type-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'events'">
|
||||
<j-form-item label="级别" :name="['expands', 'level']" :rules="[
|
||||
|
@ -43,12 +43,12 @@
|
|||
]">
|
||||
<j-select v-model:value="value.expands.level" :options="EventLevel" size="small"></j-select>
|
||||
</j-form-item>
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数"></value-type-form>
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="function" title="输出参数" only-object></value-type-form>
|
||||
</template>
|
||||
<template v-if="modelType === 'tags'">
|
||||
<value-type-form :name="['valueType']" v-model:value="value.valueType" key="property" title="数据类型"></value-type-form>
|
||||
<j-form-item label="读写类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
<j-form-item label="标签类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择标签类型' },
|
||||
]">
|
||||
<j-select v-model:value="value.expands.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select>
|
||||
</j-form-item>
|
||||
|
@ -68,6 +68,8 @@ import { getMetadataConfig } from '@/api/device/product'
|
|||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
import { EventLevel, ExpandsTypeList } from '@/views/device/data';
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
import { validateJson } from './validator';
|
||||
import { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
{ required: true, message: '请选择来源' },
|
||||
]">
|
||||
<j-select v-model:value="_value.source" :options="PropertySource" size="small"
|
||||
:disabled="metadataStore.model.action === 'edit'"></j-select>
|
||||
:disabled="metadataStore.model.action === 'edit'" @change="changeSource"></j-select>
|
||||
</j-form-item>
|
||||
<virtual-rule-param v-if="_value.source === 'rule'" v-model:value="_value.virtualRule"
|
||||
:name="name.concat(['virtualRule'])" :id="id" :showWindow="_value.source === 'rule'"></virtual-rule-param>
|
||||
<j-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
]">
|
||||
<j-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small"></j-select>
|
||||
<j-select v-model:value="_value.type" :options="ExpandsTypeList" mode="multiple" size="small" :disabled="['manual', 'rule'].includes(_value.source)"></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="其他配置" v-if="config.length > 0">
|
||||
<j-form-item v-for="(item, index) in config" :key="index">
|
||||
|
@ -100,5 +100,15 @@ const validateMetrics = (value: Record<any, any>[]) => {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const changeSource = (val: string) => {
|
||||
if (val === 'manual') {
|
||||
_value.value.type = ['write']
|
||||
} else if (val === 'rule') {
|
||||
_value.value.type = ['report']
|
||||
} else {
|
||||
_value.value.type = []
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
|
@ -1,23 +1,23 @@
|
|||
<template>
|
||||
<j-form-item :label="title" :name="name.concat(['type'])" :rules="[
|
||||
metadataStore.model.type !== 'functions' ? { required: true, message: `请选择${title}` } : {},
|
||||
required ? { required: true, message: `请选择${title}` } : {},
|
||||
]">
|
||||
<j-select v-model:value="_value.type"
|
||||
:options="metadataStore.model.type === 'events' ? eventDataTypeList : _dataTypeList" size="small"
|
||||
:options="onlyObject ? eventDataTypeList : _dataTypeList" size="small"
|
||||
@change="changeType"></j-select>
|
||||
</j-form-item>
|
||||
<j-form-item label="单位" :name="name.concat(['unit'])" v-if="['int', 'float', 'long', 'double'].includes(_value.type)">
|
||||
<InputSelect v-model:value="_value.unit" :options="unit.unitOptions" size="small"></InputSelect>
|
||||
</j-form-item>
|
||||
<j-form-item label="精度" :name="name.concat(['scale'])" v-if="['float', 'double'].includes(_value.type)">
|
||||
<j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0" :default-value="2"
|
||||
<j-input-number v-model:value="_value.scale" size="small" :min="0" :max="2147483647" :precision="0"
|
||||
style="width: 100%"></j-input-number>
|
||||
</j-form-item>
|
||||
<j-form-item label="布尔值" name="booleanConfig" v-if="['boolean'].includes(_value.type)">
|
||||
<BooleanParam :name="name" v-model:value="_value"></BooleanParam>
|
||||
</j-form-item>
|
||||
<j-form-item label="枚举项" :name="name.concat(['elements'])" v-if="['enum'].includes(_value.type)" :rules="[
|
||||
{ required: true, validator: validateEnum, message: '请配置枚举项' }
|
||||
{ required: true, validator: validateEnum }
|
||||
]">
|
||||
<EnumParam v-model:value="_value.elements" :name="name.concat(['elements'])"></EnumParam>
|
||||
</j-form-item>
|
||||
|
@ -39,7 +39,7 @@
|
|||
<ArrayParam v-model:value="_value.elementType" :name="name.concat(['elementType'])"></ArrayParam>
|
||||
</j-form-item>
|
||||
<j-form-item label="JSON对象" :name="name.concat(['properties'])" v-if="['object'].includes(_value.type)" :rules="[
|
||||
{ validator: validateJson }
|
||||
{ validator: (_rule: Rule, val: Record<any, any>[]) => validateJson(_rule, val, 'JSON对象') }
|
||||
]">
|
||||
<JsonParam v-model:value="_value.properties" :name="name.concat(['properties'])"></JsonParam>
|
||||
</j-form-item>
|
||||
|
@ -62,8 +62,8 @@ import EnumParam from '@/components/Metadata/EnumParam/index.vue'
|
|||
import ArrayParam from '@/components/Metadata/ArrayParam/index.vue'
|
||||
import JsonParam from '@/components/Metadata/JsonParam/index.vue'
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
import { validateEnum, validateArray, validateJson } from './validator'
|
||||
import { Rule } from 'ant-design-vue/es/form';
|
||||
import { Form } from 'ant-design-vue/es';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
|
@ -81,8 +81,16 @@ const props = defineProps({
|
|||
required: true
|
||||
},
|
||||
title: {
|
||||
String,
|
||||
type: String,
|
||||
default: '数据类型'
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
onlyObject: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -108,7 +116,7 @@ watch(_value,
|
|||
{ deep: true, immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
if (metadataStore.model.type === 'events') {
|
||||
if (props.onlyObject) {
|
||||
_value.value = {
|
||||
type: 'object',
|
||||
expands: {}
|
||||
|
@ -141,63 +149,12 @@ const eventDataTypeList = [
|
|||
]
|
||||
|
||||
const changeType = (val: SelectValue) => {
|
||||
if (['float', 'double'].includes(_value.value.type) && _value.value.scale === undefined) {
|
||||
_value.value.scale = 2
|
||||
}
|
||||
emit('changeType', val as string)
|
||||
}
|
||||
|
||||
const validateEnum = async (_rule: Rule, val: Record<any, any>[]) => {
|
||||
if (val.length === 0) return Promise.reject(new Error('请配置枚举项'));
|
||||
const flag = val.every((item) => {
|
||||
return item.value && item.text;
|
||||
});
|
||||
if (!flag) {
|
||||
return Promise.reject(new Error('请配置枚举项'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const validateArray = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
await validateValueType(_rule, val)
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const validateJson = async (_rule: Rule, val: Record<any, any>[]) => {
|
||||
if (!val || val.length === 0) {
|
||||
return Promise.reject(new Error('请输入配置参数'));
|
||||
}
|
||||
for (let item of val) {
|
||||
if (!item) return Promise.reject(new Error('请输入配置参数'));
|
||||
await validateValueType(_rule, item)
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const validateValueType = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
if (!val.id) {
|
||||
return Promise.reject(new Error('请输入标识'))
|
||||
}
|
||||
if (!val.name) {
|
||||
return Promise.reject(new Error('请输入名称'))
|
||||
}
|
||||
if (metadataStore.model.type !== 'functions' && !val.valueType?.type) {
|
||||
return Promise.reject(new Error(`请选择${props.title}`))
|
||||
}
|
||||
if (['enum'].includes(val.valueType.type)) {
|
||||
await validateEnum(_rule, val.valueType.elements)
|
||||
}
|
||||
if (['array'].includes(val.valueType.type)) {
|
||||
await validateArray(_rule, val.valueType.elementType)
|
||||
}
|
||||
if (['object'].includes(val.valueType.type)) {
|
||||
await validateJson(_rule, val.valueType.properties)
|
||||
}
|
||||
if (['file'].includes(val.valueType.type) && !val.valueType.fileType) {
|
||||
return Promise.reject(new Error('请选择文件类型'))
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// const rules = ref({
|
||||
// type: [
|
||||
// metadataStore.model.type !== 'functions' ? { required: true, message: `请选择${props.title}` } : {},
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { Rule } from "ant-design-vue/es/form";
|
||||
|
||||
export const validateEnum = async (_rule: Rule, val: Record<any, any>[]) => {
|
||||
if (val.length === 0) return Promise.reject(new Error('请配置枚举项'));
|
||||
const flag = val.every((item) => {
|
||||
return item.value && item.text;
|
||||
});
|
||||
if (!flag) {
|
||||
return Promise.reject(new Error('请配置枚举项'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateArray = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val) return Promise.reject(new Error(`请输入元素配置`));
|
||||
await validateValueType(_rule, val)
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateJson = async (_rule: Rule, val: Record<any, any>[], title = '配置参数') => {
|
||||
if (!val || val.length === 0) {
|
||||
return Promise.reject(new Error(`请输入${title}`));
|
||||
}
|
||||
for (let item of val) {
|
||||
if (!item) return Promise.reject(new Error(`请输入${title}`));
|
||||
await validateIdName(_rule, item)
|
||||
await validateValueType(_rule, item.valueType)
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export const validateIdName = async (_rule: Rule, val: Record<any, any>) => {
|
||||
if (!val.id) {
|
||||
return Promise.reject(new Error('请输入标识'))
|
||||
}
|
||||
if (!val.name) {
|
||||
return Promise.reject(new Error('请输入名称'))
|
||||
}
|
||||
}
|
||||
|
||||
export const validateValueType = async (_rule: Rule, val: Record<any, any>, title = '数据类型') => {
|
||||
console.log(val)
|
||||
if (!val) return Promise.reject(new Error('请输入元素配置'));
|
||||
if (!val?.type) {
|
||||
return Promise.reject(new Error(`请选择${title}`))
|
||||
}
|
||||
if (['enum'].includes(val.type)) {
|
||||
await validateEnum(_rule, val.elements)
|
||||
}
|
||||
if (['array'].includes(val.type)) {
|
||||
await validateArray(_rule, val.elementType)
|
||||
}
|
||||
if (['object'].includes(val.type)) {
|
||||
await validateJson(_rule, val.properties)
|
||||
}
|
||||
if (['file'].includes(val.type) && !val.fileType) {
|
||||
return Promise.reject(new Error('请选择文件类型'))
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
|
@ -38,22 +38,20 @@
|
|||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<j-space>
|
||||
<PermissionButton :uhas-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
<PermissionButton :has-permission="`${permission}:update`" type="link" key="edit" style="padding: 0"
|
||||
:udisabled="operateLimits('updata', type)" @click="handleEditClick(record)" :tooltip="{
|
||||
title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
|
||||
}">
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton :uhas-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
<PermissionButton :has-permission="`${permission}:delete`" type="link" key="delete" style="padding: 0"
|
||||
:pop-confirm="{
|
||||
title: '确认删除?', onConfirm: async () => {
|
||||
await removeItem(record);
|
||||
},
|
||||
}"
|
||||
:tooltip="{
|
||||
title: '删除',
|
||||
}"
|
||||
>
|
||||
}" :tooltip="{
|
||||
title: '删除',
|
||||
}">
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
|
@ -69,13 +67,9 @@ import { useProductStore } from '@/store/product'
|
|||
import { useMetadataStore } from '@/store/metadata'
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import { TablePaginationConfig, message } from 'ant-design-vue/es'
|
||||
import { SystemConst } from '@/utils/consts'
|
||||
import { Store } from 'jetlinks-store'
|
||||
import { asyncUpdateMetadata, removeMetadata } from '../metadata'
|
||||
import { detail } from '@/api/device/instance'
|
||||
import Edit from './Edit/index.vue'
|
||||
// import { detail } from '@/api/device/instance'
|
||||
// import { detail as productDetail } from '@/api/device/product'
|
||||
interface Props {
|
||||
type: MetadataType;
|
||||
target: 'product' | 'device';
|
||||
|
@ -134,10 +128,6 @@ const handleSearch = (searchValue: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
|
||||
const refreshMetadata = () => {
|
||||
loading.value = true
|
||||
// const res = target === 'product'
|
||||
|
@ -188,11 +178,18 @@ const handleEditClick = (record: MetadataItem) => {
|
|||
}
|
||||
|
||||
const resetMetadata = async () => {
|
||||
// const { id } = route.params
|
||||
// const resp = await detail(id as string);
|
||||
// if (resp.status === 200) {
|
||||
// instanceStore.setCurrent(resp?.result || []);
|
||||
// }
|
||||
const { id } = route.params
|
||||
const resp = await detail(id as string);
|
||||
if (resp.status === 200) {
|
||||
instanceStore.setCurrent(resp?.result || []);
|
||||
if (target === 'device') {
|
||||
instanceStore.refresh(id as string)
|
||||
} else {
|
||||
productStore.refresh(id as string)
|
||||
}
|
||||
metadataStore.set('importMetadata', true)
|
||||
};
|
||||
|
||||
const removeItem = async (record: MetadataItem) => {
|
||||
|
@ -203,7 +200,7 @@ const removeItem = async (record: MetadataItem) => {
|
|||
const result = await asyncUpdateMetadata(target, _currentData);
|
||||
if (result.status === 200) {
|
||||
message.success('操作成功!');
|
||||
Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
|
||||
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
|
||||
metadataStore.model.edit = false;
|
||||
metadataStore.model.item = {};
|
||||
resetMetadata();
|
||||
|
|
|
@ -52,11 +52,30 @@
|
|||
<j-form-item
|
||||
:name="['templateDetailTable', index, 'value']"
|
||||
:rules="{
|
||||
required: true,
|
||||
required: record.required,
|
||||
message: '该字段为必填字段',
|
||||
}"
|
||||
>
|
||||
<ToUser
|
||||
v-if="record.type === 'user'"
|
||||
v-model:toUser="record.value"
|
||||
:type="data.type"
|
||||
:config-id="data.id"
|
||||
/>
|
||||
<ToOrg
|
||||
v-else-if="record.type === 'org'"
|
||||
:type="data.type"
|
||||
:config-id="data.id"
|
||||
v-model:toParty="record.value"
|
||||
/>
|
||||
<ToTag
|
||||
v-else-if="record.type === 'tag'"
|
||||
:type="data.type"
|
||||
:config-id="data.id"
|
||||
v-model:toTag="record.value"
|
||||
/>
|
||||
<ValueItem
|
||||
v-else
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
/>
|
||||
|
@ -78,6 +97,10 @@ import type {
|
|||
} from '@/views/notice/Template/types';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import ToUser from '@/views/notice/Template/Detail/components/ToUser.vue';
|
||||
import ToOrg from '@/views/notice/Template/Detail/components/ToOrg.vue';
|
||||
import ToTag from '@/views/notice/Template/Detail/components/ToTag.vue';
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:visible', data: boolean): void;
|
||||
};
|
||||
|
@ -128,6 +151,7 @@ const getTemplateDetail = async () => {
|
|||
formData.value.templateDetailTable = result.variableDefinitions.map(
|
||||
(m: any) => ({
|
||||
...m,
|
||||
type: m.expands ? m.expands.businessType : m.type,
|
||||
value: undefined,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -393,11 +393,11 @@ const formRules = ref({
|
|||
// webhook
|
||||
'configuration.url': [
|
||||
{ required: true, message: '请输入Webhook' },
|
||||
{
|
||||
pattern:
|
||||
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[j-z]{2,6}\/?/,
|
||||
message: 'Webhook需要是一个合法的URL',
|
||||
},
|
||||
// {
|
||||
// pattern:
|
||||
// /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[j-z]{2,6}\/?/,
|
||||
// message: 'Webhook需要是一个合法的URL',
|
||||
// },
|
||||
],
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
});
|
||||
|
|
|
@ -156,6 +156,8 @@ watch(
|
|||
(val) => {
|
||||
if (val) {
|
||||
getDepartment();
|
||||
} else {
|
||||
dataSource.value = [];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -357,19 +359,19 @@ const getTableData = () => {
|
|||
const bindUser = bindUsers.find(
|
||||
(f: any) => f.thirdPartyUserId === deptUser.id,
|
||||
);
|
||||
if (bindUser) {
|
||||
unBindUser = unBindUsers.find(
|
||||
(f: any) => f.id === bindUser.userId,
|
||||
);
|
||||
}
|
||||
// if (bindUser) {
|
||||
// unBindUser = unBindUsers.find(
|
||||
// (f: any) => f.id === bindUser.userId,
|
||||
// );
|
||||
// }
|
||||
dataSource.value.push({
|
||||
thirdPartyUserId: deptUser.id,
|
||||
thirdPartyUserName: deptUser.name,
|
||||
bindId: bindUser?.userId,
|
||||
userId: unBindUser?.id,
|
||||
bindId: bindUser?.id,
|
||||
userId: bindUser?.userId,
|
||||
userName: unBindUser
|
||||
? `${unBindUser.name}(${unBindUser.username})`
|
||||
: '',
|
||||
: bindUser?.providerName,
|
||||
status: {
|
||||
text: bindUser?.providerName ? '已绑定' : '未绑定',
|
||||
value: bindUser?.providerName ? 'success' : 'error',
|
||||
|
|
|
@ -11,10 +11,26 @@
|
|||
<span v-if="column.dataIndex === 'id'">
|
||||
{{ record[column.dataIndex] }}
|
||||
</span>
|
||||
<j-input
|
||||
v-if="column.dataIndex === 'name'"
|
||||
v-model:value="record.name"
|
||||
/>
|
||||
<template v-if="column.dataIndex === 'name'">
|
||||
<j-input
|
||||
v-model:value="record.name"
|
||||
:class="
|
||||
!record.name || record.name.length > 64
|
||||
? 'has-error'
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
<!-- antd useForm 无table表单校验 手动添加校验 -->
|
||||
<div
|
||||
class="error-text"
|
||||
v-show="!record.name || record.name.length > 64"
|
||||
>
|
||||
<span v-show="!record.name"> 该字段是必填字段 </span>
|
||||
<span v-show="record.name.length > 64">
|
||||
最多可输入64个字符
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<j-select
|
||||
v-if="column.dataIndex === 'type'"
|
||||
v-model:value="record.type"
|
||||
|
@ -91,7 +107,7 @@ const columns = [
|
|||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
// width: 160,
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
|
@ -133,4 +149,20 @@ const handleTypeChange = (record: IVariable) => {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.table-wrapper {
|
||||
.has-error {
|
||||
border-color: rgba(255, 77, 79);
|
||||
&:focus {
|
||||
border-color: rgba(255, 120, 117);
|
||||
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2);
|
||||
border-right-width: 1px !important;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
.error-text {
|
||||
color: rgba(255, 77, 79);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -526,6 +526,7 @@
|
|||
</j-form-item>
|
||||
<j-form-item
|
||||
v-if="formData.template.templateType === 'tts'"
|
||||
v-bind="validateInfos['template.ttsmessage']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
|
@ -541,8 +542,7 @@
|
|||
</span>
|
||||
</template>
|
||||
<j-textarea
|
||||
v-model:value="formData.template.message"
|
||||
show-count
|
||||
v-model:value="formData.template.ttsmessage"
|
||||
:rows="5"
|
||||
placeholder="内容中的变量将用于阿里云语音验证码"
|
||||
/>
|
||||
|
@ -840,7 +840,8 @@ const resetPublicFiles = () => {
|
|||
formData.value.template.templateType = 'tts';
|
||||
formData.value.template.templateCode = '';
|
||||
formData.value.template.ttsCode = '';
|
||||
formData.value.template.message = '';
|
||||
// formData.value.template.message = '';
|
||||
formData.value.template.ttsmessage = '';
|
||||
formData.value.template.playTimes = 1;
|
||||
formData.value.template.calledShowNumbers = '';
|
||||
formData.value.template.calledNumber = '';
|
||||
|
@ -910,26 +911,29 @@ const formRules = ref({
|
|||
// 阿里云语音
|
||||
'template.templateType': [{ required: true, message: '请选择类型' }],
|
||||
'template.templateCode': [{ required: true, message: '请输入模板ID' }],
|
||||
'template.calledShowNumbers': [
|
||||
{
|
||||
trigger: 'change',
|
||||
validator(_rule: Rule, value: string) {
|
||||
if (!value) return Promise.resolve();
|
||||
if (!phoneRegEx(value)) return Promise.reject('请输入有效号码');
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
// 短信
|
||||
'template.code': [{ required: true, message: '请选择模板' }],
|
||||
'template.signName': [{ required: true, message: '请输入签名' }],
|
||||
// webhook
|
||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||
'template.message': [
|
||||
{ required: true, message: '请输入' },
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
{ max: 500, message: '最多可输入500个字符' },
|
||||
],
|
||||
'template.calledShowNumbers': [
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator(_rule: Rule, value: string) {
|
||||
if (!phoneRegEx(value)) {
|
||||
return Promise.reject('请输入有效号码');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
'template.ttsmessage': [{ max: 500, message: '最多可输入500个字符' }],
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||
|
@ -1033,6 +1037,7 @@ const variableReg = () => {
|
|||
* 钉钉机器人 消息类型选择改变
|
||||
*/
|
||||
const handleMessageTypeChange = () => {
|
||||
if (formData.value.type !== 'dingTalk') return;
|
||||
delete formData.value.template.markdown;
|
||||
delete formData.value.template.link;
|
||||
delete formData.value.template.text;
|
||||
|
@ -1159,6 +1164,13 @@ const getSignsList = async () => {
|
|||
*/
|
||||
const btnLoading = ref<boolean>(false);
|
||||
const handleSubmit = () => {
|
||||
// 变量列表存在, 且存在空值
|
||||
if (
|
||||
formData.value.variableDefinitions.length &&
|
||||
formData.value.variableDefinitions.some((s: any) => !s.name)
|
||||
)
|
||||
return;
|
||||
// 邮件没有配置字段
|
||||
if (formData.value.type === 'email') delete formData.value.configId;
|
||||
if (formData.value.template.messageType === 'markdown')
|
||||
delete formData.value.template.link;
|
||||
|
@ -1169,9 +1181,16 @@ const handleSubmit = () => {
|
|||
setTimeout(() => {
|
||||
validate()
|
||||
.then(async () => {
|
||||
if (formData.value.provider === 'ttsCode')
|
||||
if (formData.value.provider === 'aliyun') {
|
||||
formData.value.template.ttsCode =
|
||||
formData.value.template.templateCode;
|
||||
// 语音message字段与其他类型的message字段重名, 但语音不需要必填验证
|
||||
// 取别名ttsmessage, 验证通过之后, 赋值回message字段, 并删除别名字段
|
||||
formData.value.template.message =
|
||||
formData.value.template.ttsmessage;
|
||||
delete formData.value.template.ttsmessage;
|
||||
}
|
||||
|
||||
btnLoading.value = true;
|
||||
let res;
|
||||
if (!formData.value.id) {
|
||||
|
|
|
@ -60,6 +60,7 @@ export type TemplateFormData = {
|
|||
templateCode?: string;
|
||||
ttsCode?: string;
|
||||
// message?: string;
|
||||
ttsmessage?: string;
|
||||
playTimes?: number;
|
||||
calledShowNumbers?: string;
|
||||
calledNumber?: string;
|
||||
|
|
|
@ -189,7 +189,8 @@ export const TEMPLATE_FIELD_MAP = {
|
|||
templateType: 'tts',
|
||||
templateCode: '',
|
||||
ttsCode: '',
|
||||
message: '',
|
||||
// message: '',
|
||||
ttsmessage: '',
|
||||
playTimes: 1,
|
||||
calledShowNumbers: '',
|
||||
calledNumber: '',
|
||||
|
|
|
@ -13,17 +13,16 @@
|
|||
import { useSceneStore } from '@/store/scene';
|
||||
import Action from '../action/index.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ActionsType } from '@/components/Table';
|
||||
import { ActionsType } from '@/views/rule-engine/Scene/typings';
|
||||
|
||||
const sceneStore = useSceneStore();
|
||||
const { data } = storeToRefs(sceneStore);
|
||||
|
||||
const onActionAdd = (_data: ActionsType) => {
|
||||
console.log(_data)
|
||||
// if (data?.branches && _data) {
|
||||
// const newThen = [...data?.branches?.[0].then, data];
|
||||
// data.branches[0].then = newThen;
|
||||
// }
|
||||
const onActionAdd = (_data: any) => {
|
||||
if (data.value?.branches && _data) {
|
||||
data?.value.branches?.[0].then.push(_data)
|
||||
console.log(data?.value.branches?.[0].then)
|
||||
}
|
||||
};
|
||||
|
||||
const onActionUpdate = (_data: ActionsType, type: boolean) => {
|
||||
|
|
|
@ -18,13 +18,7 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<!-- <j-card-select
|
||||
v-model:value="formModel.type"
|
||||
:options="options"
|
||||
type="horizontal"
|
||||
float="right"
|
||||
/> -->
|
||||
<a-radio-group v-model:value="formModel.type" :options="options" />
|
||||
<CardSelect v-model:value="formModel.type" :options="options"/>
|
||||
</a-form-item>
|
||||
<ActionTypeComponent
|
||||
v-bind="props"
|
||||
|
@ -46,6 +40,7 @@ import { PropType } from 'vue';
|
|||
import { ActionsType } from '../../../typings';
|
||||
import ActionTypeComponent from './ActionTypeComponent.vue';
|
||||
import { randomString } from '@/utils/utils';
|
||||
import CardSelect from '../../components/CardSelect.vue'
|
||||
|
||||
const props = defineProps({
|
||||
branchesName: {
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<!-- <j-card-select
|
||||
v-model:value="notifyType"
|
||||
:options="options"
|
||||
:icon-size="106"
|
||||
/> -->
|
||||
<a-radio-group v-model:value="notifyType" :options="options" @change="onRadioChange" />
|
||||
<div class="notify-type-warp" :class="{ disabled: disabled }">
|
||||
<div
|
||||
:key="item.id"
|
||||
v-for="item in options"
|
||||
class="notify-type-item"
|
||||
:class="{ active: notifyType === item.value }"
|
||||
@click="onSelect(item.value)"
|
||||
>
|
||||
<div class="notify-type-item-image">
|
||||
<img :width="106" :src="item.iconUrl" />
|
||||
</div>
|
||||
<div class="notify-type-item-title">{{item.label}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
|
@ -26,9 +34,13 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value'])
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const notifyType = ref('');
|
||||
|
@ -37,17 +49,19 @@ const options = ref<any[]>([]);
|
|||
watch(
|
||||
() => props.value,
|
||||
(newVal) => {
|
||||
notifyType.value = newVal
|
||||
notifyType.value = newVal;
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const onRadioChange = (e: any) => {
|
||||
emit('update:value', e.target.value)
|
||||
}
|
||||
const onSelect = (val: string) => {
|
||||
if (!props.disabled) {
|
||||
emit('update:value', val);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
notice.queryMessageType().then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
options.value = (resp.result as any[]).map((item) => {
|
||||
|
@ -58,11 +72,65 @@ onMounted(() => {
|
|||
};
|
||||
});
|
||||
}
|
||||
loading.value = false
|
||||
loading.value = false;
|
||||
});
|
||||
notifyType.value = props.value
|
||||
notifyType.value = props.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.notify-type-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.notify-type-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 172px;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.notify-type-item-title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.notify-type-item-image {
|
||||
width: 106px;
|
||||
margin: 16px 33px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: @primary-color-active;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.notify-type-item {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -26,7 +26,7 @@
|
|||
:actions="
|
||||
serialArray.length ? serialArray[0].actions : []
|
||||
"
|
||||
@add="onAdd"
|
||||
@add="(_item) => onAdd(_item, false)"
|
||||
@delete="onDelete"
|
||||
/>
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@
|
|||
? parallelArray[0].actions
|
||||
: []
|
||||
"
|
||||
@add="onAdd"
|
||||
@add="(_item) => onAdd(_item, true)"
|
||||
@delete="onDelete"
|
||||
/>
|
||||
</div>
|
||||
|
@ -98,6 +98,8 @@ watch(
|
|||
parallelArray.value = newVal.filter((item) => item.parallel);
|
||||
serialArray.value = newVal.filter((item) => !item.parallel);
|
||||
|
||||
console.log(parallelArray.value, serialArray.value, '123')
|
||||
|
||||
const isSerialActions = serialArray.value.some((item) => {
|
||||
return !!item.actions.length;
|
||||
});
|
||||
|
@ -128,7 +130,7 @@ const onDelete = (_key: string) => {
|
|||
emit('update', serialArray[0], false);
|
||||
}
|
||||
};
|
||||
const onAdd = (actionItem: any) => {
|
||||
const onAdd = (actionItem: any, _parallel: boolean) => {
|
||||
const newParallelArray = [...parallelArray.value];
|
||||
if (newParallelArray.length) {
|
||||
const indexOf = newParallelArray[0].actions?.findIndex(
|
||||
|
@ -140,12 +142,11 @@ const onAdd = (actionItem: any) => {
|
|||
newParallelArray[0].actions.push(actionItem);
|
||||
}
|
||||
parallelArray.value = [...newParallelArray];
|
||||
console.log(parallelArray.value);
|
||||
emit('update', newParallelArray[0], true);
|
||||
emit('update', newParallelArray[0], _parallel);
|
||||
} else {
|
||||
actionItem.key = randomString();
|
||||
emit('add', {
|
||||
parallel: true,
|
||||
parallel: _parallel,
|
||||
key: randomString(),
|
||||
actions: [actionItem],
|
||||
});
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div class="scene-trigger-way-warp" :class="{disabled: disabled}">
|
||||
<template v-for="item in options" :key="item.value">
|
||||
<div
|
||||
class="trigger-way-item"
|
||||
:class="{ active: item?.value === value }"
|
||||
:style="{width: `${cardSize}px`}"
|
||||
@click="onSelect(item.value)"
|
||||
>
|
||||
<div class="way-item-title">
|
||||
<p>{{ item.label }}</p>
|
||||
<span>{{ item.subLabel }}</span>
|
||||
</div>
|
||||
<div class="way-item-image">
|
||||
<img :src="item.iconUrl" :width="imageSize" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'vertical' | 'horizontal'>,
|
||||
default: 'horizontal',
|
||||
},
|
||||
imageSize: {
|
||||
type: Number,
|
||||
default: 48
|
||||
},
|
||||
cardSize: {
|
||||
type: Number,
|
||||
default: 237
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value'])
|
||||
|
||||
const onSelect = (_type: string) => {
|
||||
if (!props.disabled) {
|
||||
emit('update:value', _type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.scene-trigger-way-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 24px;
|
||||
width: 100%;
|
||||
|
||||
.trigger-way-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.way-item-title {
|
||||
p {
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(#000, 0.35);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.way-item-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin: 0 !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
.way-item-image {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: @primary-color-active;
|
||||
.way-item-image {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.trigger-way-item {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: initial;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -12,7 +12,6 @@
|
|||
<j-form-item label="系统名称" name="title">
|
||||
<j-input
|
||||
v-model:value="formValue.title"
|
||||
:maxlength="64"
|
||||
placeholder="请输入系统名称"
|
||||
/>
|
||||
</j-form-item>
|
||||
|
@ -43,7 +42,7 @@
|
|||
placeholder="请输入高德API Key"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-form-item name="'base-path'">
|
||||
<j-form-item name="base-path">
|
||||
<template #label>
|
||||
<span>base-path</span>
|
||||
<j-tooltip title="系统后台访问的url">
|
||||
|
@ -221,7 +220,7 @@
|
|||
:action="action"
|
||||
:headers="headers"
|
||||
:beforeUpload="
|
||||
uploader.beforeBackUpload
|
||||
uploader.beforeLogoUpload
|
||||
"
|
||||
:showUploadList="false"
|
||||
@change="uploader.changeBackUpload"
|
||||
|
@ -286,7 +285,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts" name="Basis">
|
||||
import { formType, uploaderType } from './index';
|
||||
import { formType, uploaderType } from './typing';
|
||||
import { getImage } from '@/utils/comm.ts';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
|
@ -312,7 +311,16 @@ const form = reactive<formType>({
|
|||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择本地地址',
|
||||
message: '名称必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
|
@ -411,21 +419,34 @@ const form = reactive<formType>({
|
|||
const { formValue, rulesFrom } = toRefs(form);
|
||||
|
||||
const uploader: uploaderType = {
|
||||
imageTypes: ['image/jpeg', 'image/png'],
|
||||
imageTypes: ['jpg', 'jpeg', 'png', 'jfif', 'pjp', 'pjpeg'],
|
||||
iconTypes: ['image/x-icon'],
|
||||
// logo格式校验
|
||||
beforeLogoUpload: (file) => {
|
||||
const isType = uploader.imageTypes.includes(file.type);
|
||||
if (!isType) {
|
||||
beforeLogoUpload: ({ size, type }: File) => {
|
||||
const typeBool =
|
||||
uploader.imageTypes.filter((typeStr) => type.includes(typeStr))
|
||||
.length > 0;
|
||||
const sizeBool = size / 1024 / 1024 < 4;
|
||||
|
||||
if (!typeBool) {
|
||||
message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`);
|
||||
return false;
|
||||
} else if (!sizeBool) {
|
||||
message.error(`图片大小必须小于4M`);
|
||||
}
|
||||
const isSize = file.size / 1024 / 1024 < 4;
|
||||
if (!isSize) {
|
||||
message.error(`图片大小必须小于${4}M`);
|
||||
}
|
||||
return isType && isSize;
|
||||
return typeBool && sizeBool;
|
||||
},
|
||||
// 浏览器页签格式校验
|
||||
beforeIconUpload: (file) => {
|
||||
const typeBool = uploader.iconTypes.includes(file.type);
|
||||
const sizeBool = file.size / 1024 / 1024 < 1;
|
||||
if (!typeBool) {
|
||||
message.error(`请上传ico格式的图片`);
|
||||
} else if (!sizeBool) {
|
||||
message.error(`图片大小必须小于${1}M`);
|
||||
}
|
||||
return typeBool && sizeBool;
|
||||
},
|
||||
|
||||
// logo上传改变事件
|
||||
handleChangeLogo: (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
|
@ -435,63 +456,32 @@ const uploader: uploaderType = {
|
|||
form.logoLoading = false;
|
||||
form.formValue.logo = info.file.response?.result;
|
||||
} else if (info.file.status === 'error') {
|
||||
console.log(info.file);
|
||||
form.logoLoading = false;
|
||||
message.error('系统logo上传失败,请稍后再试');
|
||||
}
|
||||
},
|
||||
// 背景图片上传之前
|
||||
beforeBackUpload: (file) => {
|
||||
const isType = uploader.imageTypes.includes(file.type);
|
||||
if (!isType) {
|
||||
message.error(`请上传.jpg.png.jfif.pjp.pjpeg.jpeg格式的图片`);
|
||||
return false;
|
||||
}
|
||||
const isSize = file.size / 1024 / 1024 < 4;
|
||||
if (!isSize) {
|
||||
message.error(`图片大小必须小于${4}M`);
|
||||
}
|
||||
return isType && isSize;
|
||||
},
|
||||
// 背景图片发生改变
|
||||
// 背景上传改变事件
|
||||
changeBackUpload: (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
form.backLoading = true;
|
||||
} else if (info.file.status === 'done') {
|
||||
console.log(info);
|
||||
|
||||
info.file.url = info.file.response?.result;
|
||||
form.backLoading = false;
|
||||
form.formValue.backgroud = info.file.response?.result;
|
||||
} else if (info.file.status === 'error') {
|
||||
console.log(info.file);
|
||||
form.logoLoading = false;
|
||||
message.error('背景图上传失败,请稍后再试');
|
||||
}
|
||||
},
|
||||
// 上传之前
|
||||
beforeIconUpload: (file) => {
|
||||
const isType = uploader.iconTypes.includes(file.type);
|
||||
if (!isType) {
|
||||
message.error(`请上传ico格式的图片`);
|
||||
return false;
|
||||
}
|
||||
const isSize = file.size / 1024 / 1024 < 1;
|
||||
if (!isSize) {
|
||||
message.error(`图片大小必须小于${1}M`);
|
||||
}
|
||||
return isType && isSize;
|
||||
},
|
||||
// 图标发生改变
|
||||
// 浏览器页签上传改变事件
|
||||
changeIconUpload: (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
form.iconLoading = true;
|
||||
} else if (info.file.status === 'done') {
|
||||
info.file.url = info.file.response?.result;
|
||||
form.iconLoading = true;
|
||||
form.iconLoading = false;
|
||||
form.formValue.ico = info.file.response?.result;
|
||||
} else if (info.file.status === 'error') {
|
||||
console.log(info.file);
|
||||
form.logoLoading = false;
|
||||
message.error('浏览器页签上传失败,请稍后再试');
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import BaseService from '@/utils/BaseService';
|
||||
import { request } from 'umi';
|
||||
import SystemConst from '@/utils/const';
|
||||
|
||||
class Service extends BaseService<ReationItem> {
|
||||
getTypes = () =>
|
||||
request(`/${SystemConst.API_BASE}/relation/types`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
validator = (params: any) =>
|
||||
request(`/${SystemConst.API_BASE}/relation/_validate?`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export default Service;
|
|
@ -30,7 +30,7 @@ export interface uploaderType {
|
|||
iconTypes: Array<string>,
|
||||
beforeLogoUpload: (file: UploadProps['beforeUpload']) => void
|
||||
handleChangeLogo: (info: UploadChangeParam) => void
|
||||
beforeBackUpload: (file: UploadProps['beforeUpload']) => void
|
||||
beforeBackUpload?: (file: UploadProps['beforeUpload']) => void
|
||||
changeBackUpload: (info: UploadChangeParam) => void
|
||||
beforeIconUpload: (file: UploadProps['beforeUpload']) => void
|
||||
changeIconUpload: (info: UploadChangeParam) => void
|
|
@ -36,6 +36,7 @@
|
|||
:params="query.params.value"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: table._selectedRowKeys.value,
|
||||
onChange: pageChange
|
||||
}"
|
||||
@cancelSelect="table.cancelSelect"
|
||||
>
|
||||
|
@ -420,6 +421,9 @@ const table: any = {
|
|||
},
|
||||
};
|
||||
table.init();
|
||||
const pageChange = ()=>{
|
||||
console.log(1111,table._selectedRowKeys.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
visible
|
||||
:title="title"
|
||||
width="520px"
|
||||
@cancel="emits('update:visible',false)"
|
||||
@cancel="emits('update:visible', false)"
|
||||
@ok="confirm"
|
||||
class="edit-dialog-container"
|
||||
cancelText="取消"
|
||||
|
@ -16,7 +16,7 @@
|
|||
v-model:value="form.data.parentId"
|
||||
style="width: 100%"
|
||||
placeholder="请选择上级组织"
|
||||
:tree-data="props.treeData"
|
||||
:tree-data="treeData"
|
||||
:field-names="{ value: 'id' }"
|
||||
>
|
||||
<template #title="{ name }"> {{ name }} </template>
|
||||
|
@ -80,6 +80,42 @@ const confirm = () => {
|
|||
})
|
||||
.finally(() => (loading.value = false));
|
||||
};
|
||||
|
||||
const treeData = computed(() => {
|
||||
if (!props.data.id) return props.treeData;
|
||||
const result = cloneDeep(props.treeData) as treeType[];
|
||||
const me = findItemById(result, props.data.id) as treeType;
|
||||
me.disabled = true;
|
||||
me.children && me.children.length > 0 && filterTree(me.children);
|
||||
return result;
|
||||
});
|
||||
/**
|
||||
* 在给定的树中通过id匹配
|
||||
* @param node
|
||||
* @param id
|
||||
*/
|
||||
const findItemById = (node: treeType[], id: string): treeType | null => {
|
||||
let result = null;
|
||||
for (const item of node) {
|
||||
if (item.id === id) return item;
|
||||
else if (item.children && item.children.length > 0) {
|
||||
result = findItemById(item.children, id);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* 将此树下的所有节点禁用
|
||||
* @param treeNode
|
||||
*/
|
||||
const filterTree = (treeNode: treeType[]) => {
|
||||
if (treeNode.length < 1) return;
|
||||
treeNode.forEach((item) => {
|
||||
item.disabled = true;
|
||||
item.children && item.children.length > 0 && filterTree(item.children);
|
||||
});
|
||||
};
|
||||
// 表单相关
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
|
@ -124,6 +160,14 @@ const form = reactive({
|
|||
});
|
||||
form.init();
|
||||
|
||||
type treeType = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
name: string;
|
||||
sortIndex: string | number;
|
||||
children?: treeType[];
|
||||
disabled?: boolean;
|
||||
};
|
||||
type formType = {
|
||||
id?: string;
|
||||
parentId?: string;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<PermissionButton
|
||||
type="primary"
|
||||
class="add-btn"
|
||||
:uhasPermission="`${permission}:add`"
|
||||
:hasPermission="`${permission}:add`"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增
|
||||
|
@ -31,7 +31,7 @@
|
|||
<span>{{ name }}</span>
|
||||
<span class="func-btns" @click="(e) => e.stopPropagation()">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
:hasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
|
@ -41,7 +41,7 @@
|
|||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:add`"
|
||||
:hasPermission="`${permission}:add`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '新增子组织',
|
||||
|
@ -58,7 +58,7 @@
|
|||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:hasPermission="`${permission}:delete`"
|
||||
:tooltip="{ title: '删除' }"
|
||||
:popConfirm="{
|
||||
title: `确定要删除吗`,
|
||||
|
@ -103,7 +103,7 @@ const treeMap = new Map(); // 数据的map版本
|
|||
const treeData = ref<any[]>([]); // 展示的数据
|
||||
const selectedKeys = ref<string[]>([]); // 当前选中的项
|
||||
|
||||
function getTree() {
|
||||
function getTree(cb?: Function) {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
paging: false,
|
||||
|
@ -121,6 +121,7 @@ function getTree() {
|
|||
sourceTree.value = resp.result; // 报存源数据
|
||||
handleTreeMap(resp.result); // 将树形结构转换为map结构
|
||||
treeData.value = resp.result; // 第一次不用进行过滤
|
||||
cb && cb();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
|
@ -193,8 +194,9 @@ const openDialog = (row: any = {}) => {
|
|||
if (row.parentId) {
|
||||
childrens = row.children;
|
||||
} else childrens = treeData.value;
|
||||
const indexs = childrens.length > 0 ? childrens?.map((item) => item.sortIndex) :[0]
|
||||
sortIndex =
|
||||
Math.max(...(childrens?.map((item) => item.sortIndex) || [0])) + 1;
|
||||
Math.max(...indexs) + 1;
|
||||
}
|
||||
|
||||
dialog.selectItem = { ...row, sortIndex };
|
||||
|
@ -202,15 +204,10 @@ const openDialog = (row: any = {}) => {
|
|||
};
|
||||
init();
|
||||
function init() {
|
||||
getTree();
|
||||
getTree(save ? openDialog : undefined);
|
||||
watch(selectedKeys, (n) => {
|
||||
emits('change', n[0]);
|
||||
});
|
||||
if (save) {
|
||||
nextTick(() => {
|
||||
openDialog();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
:params="queryParams"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: table._selectedRowKeys.value,
|
||||
onChange:(keys:string[])=>table._selectedRowKeys.value = [...keys]
|
||||
}"
|
||||
:columns="columns"
|
||||
@cancelSelect="table.cancelSelect"
|
||||
|
@ -145,7 +146,7 @@
|
|||
</a-dropdown>
|
||||
<PermissionButton
|
||||
v-else
|
||||
:uhasPermission="item.permission"
|
||||
:hasPermission="item.permission"
|
||||
:tooltip="item.tooltip"
|
||||
:pop-confirm="item.popConfirm"
|
||||
@click="item.onClick"
|
||||
|
@ -157,23 +158,6 @@
|
|||
}}</span>
|
||||
</PermissionButton>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- <PermissionButton
|
||||
:uhasPermission="`${permission}:assert`"
|
||||
@click="() => table.clickEdit(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:bind`"
|
||||
:popConfirm="{
|
||||
title: `是否解除绑定`,
|
||||
onConfirm: () => table.clickUnBind(slotProps),
|
||||
}"
|
||||
>
|
||||
<AIcon type="DisconnectOutlined" />
|
||||
</PermissionButton> -->
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
|
@ -199,7 +183,7 @@
|
|||
<a-space :size="16">
|
||||
<PermissionButton
|
||||
v-for="i in table.getActions(slotProps, 'table')"
|
||||
:uhasPermission="i.permission"
|
||||
:hasPermission="i.permission"
|
||||
type="link"
|
||||
:tooltip="i?.tooltip"
|
||||
:pop-confirm="i.popConfirm"
|
||||
|
@ -334,7 +318,7 @@ const tableRef = ref();
|
|||
const table = {
|
||||
_selectedRowKeys: ref<string[]>([]),
|
||||
selectedRows: [] as any[],
|
||||
permissionList: ref<dictType>([]),
|
||||
permissionList: ref<any[]>([]),
|
||||
|
||||
init: () => {
|
||||
table.getPermissionDict();
|
||||
|
@ -354,14 +338,14 @@ const table = {
|
|||
else
|
||||
return [
|
||||
{
|
||||
permission: true,
|
||||
permission: `${permission}:assert`,
|
||||
key: 'edit',
|
||||
tooltip: { title: '编辑' },
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => table.clickEdit(data),
|
||||
},
|
||||
{
|
||||
permission: true,
|
||||
permission: `${permission}:assert`,
|
||||
key: 'unbind',
|
||||
tooltip: { title: '解除绑定' },
|
||||
popConfirm: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<Search :columns="columns" @search="(p:any)=>params = p" />
|
||||
<j-advanced-search :columns="columns" @search="(p:any)=>params = p" />
|
||||
|
||||
<j-pro-table
|
||||
ref="tableRef"
|
||||
|
@ -17,7 +17,7 @@
|
|||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
:uhasPermission="`${permission}:bind-user`"
|
||||
:hasPermission="`${permission}:bind-user`"
|
||||
@click="dialogVisible = true"
|
||||
style="margin-right: 15px"
|
||||
>
|
||||
|
@ -27,7 +27,7 @@
|
|||
style="display: inline-block; width: 12px; height: 1px"
|
||||
></div>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:bind`"
|
||||
:hasPermission="`${permission}:bind`"
|
||||
:popConfirm="{
|
||||
title: `是否解除绑定`,
|
||||
onConfirm: () => table.unBind(),
|
||||
|
@ -50,7 +50,7 @@
|
|||
<j-space :size="16">
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:bind`"
|
||||
:hasPermission="`${permission}:bind`"
|
||||
:popConfirm="{
|
||||
title: `是否解除绑定`,
|
||||
onConfirm: () => table.unBind(slotProps),
|
||||
|
|
|
@ -16,13 +16,7 @@
|
|||
<j-form-item
|
||||
name="name"
|
||||
label="姓名"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入姓名' },
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
]"
|
||||
:rules="[{ required: true, message: '请输入姓名' }]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="form.data.name"
|
||||
|
@ -35,7 +29,7 @@
|
|||
name="username"
|
||||
label="用户名"
|
||||
:rules="[
|
||||
{ required: true },
|
||||
{ required: true, message: '' },
|
||||
{
|
||||
validator: form.rules.checkUserName,
|
||||
trigger: 'blur',
|
||||
|
@ -56,7 +50,7 @@
|
|||
name="password"
|
||||
label="密码"
|
||||
:rules="[
|
||||
{ required: true },
|
||||
{ required: true, message: '' },
|
||||
{
|
||||
validator: form.rules.checkPassword,
|
||||
trigger: 'blur',
|
||||
|
@ -76,10 +70,10 @@
|
|||
name="confirmPassword"
|
||||
label="确认密码"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入8~64位的密码' },
|
||||
{ required: true, message: '' },
|
||||
{
|
||||
validator: form.rules.checkAgainPassword,
|
||||
trigger: 'change',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
>
|
||||
|
@ -91,7 +85,6 @@
|
|||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<!-- 还差页面权限 -->
|
||||
<j-row :gutter="24" v-if="form.IsShow('add', 'edit')">
|
||||
<j-col :span="12">
|
||||
<j-form-item name="roleIdList" label="角色" class="flex">
|
||||
|
@ -104,9 +97,8 @@
|
|||
></j-select>
|
||||
|
||||
<PermissionButton
|
||||
:uhasPermission="`${rolePermission}:update`"
|
||||
:hasPermission="`${rolePermission}:add`"
|
||||
@click="form.clickAddItem('roleIdList', 'Role')"
|
||||
class="add-item"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />
|
||||
</PermissionButton>
|
||||
|
@ -128,9 +120,10 @@
|
|||
</template>
|
||||
</j-tree-select>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${deptPermission}:update`"
|
||||
@click="form.clickAddItem('roleIdList', 'Role')"
|
||||
class="add-item"
|
||||
:hasPermission="`${deptPermission}:add`"
|
||||
@click="
|
||||
form.clickAddItem('orgIdList', 'Department')
|
||||
"
|
||||
>
|
||||
<AIcon type="PlusOutlined" />
|
||||
</PermissionButton>
|
||||
|
@ -235,7 +228,6 @@ const form = reactive({
|
|||
rules: {
|
||||
checkUserName: (_rule: Rule, value: string): Promise<any> =>
|
||||
new Promise((resolve, reject) => {
|
||||
console.log(_rule);
|
||||
if (props.type === 'edit') return resolve('');
|
||||
|
||||
if (!value) return reject('请输入用户名');
|
||||
|
@ -248,7 +240,7 @@ const form = reactive({
|
|||
}),
|
||||
checkPassword: (_rule: Rule, value: string): Promise<any> =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (!value) return reject('请输入8~64位的密码');
|
||||
if (!value) return reject('请输入密码');
|
||||
else if (value.length > 64) return reject('最多可输入64个字符');
|
||||
else if (value.length < 8) return reject('密码不能少于8位');
|
||||
validateField_api('password', value).then((resp: any) => {
|
||||
|
@ -258,7 +250,7 @@ const form = reactive({
|
|||
});
|
||||
}),
|
||||
checkAgainPassword: (_rule: Rule, value: string): Promise<any> => {
|
||||
if (!value) return Promise.reject('');
|
||||
if (!value) return Promise.reject('请输入8~64位的密码');
|
||||
return value === form.data.password
|
||||
? Promise.resolve()
|
||||
: Promise.reject('两次密码输入不一致');
|
||||
|
@ -386,6 +378,15 @@ type optionType = {
|
|||
.ant-select {
|
||||
flex: 1;
|
||||
}
|
||||
.ant-tooltip-disabled-compatible-wrapper {
|
||||
.ant-btn {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
border-color: #d9d9d9;
|
||||
background: #f5f5f5;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.ant-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="user-container">
|
||||
<j-advanced-search :columns="columns" @search="(params:any)=>queryParams = {...params}" />
|
||||
<j-advanced-search
|
||||
:columns="columns"
|
||||
@search="(params:any)=>queryParams = {...params}"
|
||||
/>
|
||||
|
||||
<j-pro-table
|
||||
ref="tableRef"
|
||||
|
@ -15,7 +18,7 @@
|
|||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:add`"
|
||||
:hasPermission="`${permission}:add`"
|
||||
type="primary"
|
||||
@click="table.openDialog('add')"
|
||||
>
|
||||
|
@ -38,7 +41,7 @@
|
|||
<template #action="slotProps">
|
||||
<j-space :size="16">
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
:hasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
|
@ -48,7 +51,7 @@
|
|||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:action`"
|
||||
:hasPermission="`${permission}:action`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: `${slotProps.status ? '禁用' : '启用'}`,
|
||||
|
@ -64,7 +67,7 @@
|
|||
<play-circle-outlined v-else />
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
:uhasPermission="`${permission}:update`"
|
||||
:hasPermission="`${permission}:update`"
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '重置密码',
|
||||
|
@ -75,7 +78,7 @@
|
|||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:uhasPermission="`${permission}:delete`"
|
||||
:hasPermission="`${permission}:delete`"
|
||||
:tooltip="{
|
||||
title: slotProps.status
|
||||
? '请先禁用,再删除'
|
||||
|
@ -133,7 +136,6 @@ const columns = [
|
|||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -143,7 +145,6 @@ const columns = [
|
|||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
|
@ -186,7 +187,6 @@ const columns = [
|
|||
dataIndex: 'telephone',
|
||||
key: 'telephone',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -196,7 +196,6 @@ const columns = [
|
|||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
@ -205,10 +204,11 @@ const columns = [
|
|||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
const queryParams = ({});
|
||||
const queryParams = ref({});
|
||||
|
||||
const tableRef = ref<Record<string, any>>({}); // 表格实例
|
||||
const table = {
|
||||
|
|
Loading…
Reference in New Issue