fix: 自测修改设备管理

This commit is contained in:
100011797 2023-03-10 16:52:05 +08:00
parent 0589ea17ed
commit db5514d27f
16 changed files with 474 additions and 242 deletions

View File

@ -100,7 +100,7 @@ export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boo
* @param type * @param type
* @returns * @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是否重复 * ID是否重复

View File

@ -5,7 +5,7 @@
name="file" name="file"
:action="FILE_UPLOAD" :action="FILE_UPLOAD"
:headers="{ :headers="{
'X-Access-Token': LocalStore.get(TOKEN_KEY) 'X-Access-Token': LocalStore.get(TOKEN_KEY),
}" }"
accept=".xlsx,.csv" accept=".xlsx,.csv"
:maxCount="1" :maxCount="1"
@ -26,19 +26,23 @@
</a-space> </a-space>
<div style="margin-top: 20px" v-if="importLoading"> <div style="margin-top: 20px" v-if="importLoading">
<a-badge v-if="flag" status="processing" text="进行中" /> <a-badge v-if="flag" status="processing" text="进行中" />
<a-badge v-else status="success" text="已完成" /> <a-badge v-else status="success" text="已完成" />
<span>总数量{{count}}</span> <span>总数量{{ count }}</span>
<p style="color: red">{{errMessage}}</p> <p style="color: red">{{ errMessage }}</p>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { FILE_UPLOAD } from '@/api/comm' import { FILE_UPLOAD } from '@/api/comm';
import { TOKEN_KEY } from '@/utils/variable'; import { TOKEN_KEY } from '@/utils/variable';
import { LocalStore } from '@/utils/comm'; import { LocalStore } from '@/utils/comm';
import { downloadFile, downloadFileByUrl } from '@/utils/utils'; import { downloadFile, downloadFileByUrl } from '@/utils/utils';
import { deviceImport, deviceTemplateDownload ,templateDownload} from '@/api/device/instance' import {
import { EventSourcePolyfill } from 'event-source-polyfill' deviceImport,
deviceTemplateDownload,
templateDownload,
} from '@/api/device/instance';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
type Emits = { type Emits = {
@ -50,11 +54,11 @@ const props = defineProps({
// //
modelValue: { modelValue: {
type: Array, type: Array,
default: () => [] default: () => [],
}, },
product: { product: {
type: String, type: String,
default: '' default: '',
}, },
file: { file: {
type: Object, type: Object,
@ -62,67 +66,62 @@ const props = defineProps({
return { return {
fileType: 'xlsx', fileType: 'xlsx',
autoDeploy: false, autoDeploy: false,
} };
} },
} },
}) });
const importLoading = ref<boolean>(false) const importLoading = ref<boolean>(false);
const flag = ref<boolean>(false) const flag = ref<boolean>(false);
const count = ref<number>(0) const count = ref<number>(0);
const errMessage = ref<string>('') const errMessage = ref<string>('');
const downFile =async (type: string) => { const downFile = async (type: string) => {
// downloadFile(deviceTemplateDownload(props.product, type)); // downloadFile(deviceTemplateDownload(props.product, type));
const res:any =await templateDownload(props.product, type) const res: any = await templateDownload(props.product, type);
if(res){ if (res) {
const blob = new Blob([res], { type: type }); const blob = new Blob([res], { type: type });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
console.log(url); downloadFileByUrl(url, `设备导入模版`, type);
downloadFileByUrl(
url,
`设备导入模版`,
type,
);
} }
};
}
const submitData = async (fileUrl: string) => { const submitData = async (fileUrl: string) => {
if (!!fileUrl) { if (!!fileUrl) {
count.value = 0 count.value = 0;
errMessage.value = '' errMessage.value = '';
flag.value = true flag.value = true;
const autoDeploy = !!props?.file?.autoDeploy || false; const autoDeploy = !!props?.file?.autoDeploy || false;
importLoading.value = true importLoading.value = true;
let dt = 0; let dt = 0;
const source = new EventSourcePolyfill(deviceImport(props.product, fileUrl, autoDeploy)); const source = new EventSourcePolyfill(
source.onmessage = (e: any) => { deviceImport(props.product, fileUrl, autoDeploy),
const res = JSON.parse(e.data); );
if (res.success) { source.onmessage = (e: any) => {
const temp = res.result.total; const res = JSON.parse(e.data);
dt += temp; if (res.success) {
count.value = dt const temp = res.result.total;
} else { dt += temp;
errMessage.value = res.message || '失败' count.value = dt;
} } else {
}; errMessage.value = res.message || '失败';
source.onerror = (e: { status: number; }) => { }
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员' };
flag.value = false source.onerror = (e: { status: number }) => {
source.close(); if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
}; flag.value = false;
source.onopen = () => {}; source.close();
};
source.onopen = () => {};
} else { } else {
message.error('请先上传文件') message.error('请先上传文件');
} }
} };
const uploadChange = async (info: Record<string, any>) => { const uploadChange = async (info: Record<string, any>) => {
if (info.file.status === 'done') { if (info.file.status === 'done') {
const resp: any = info.file.response || { result: '' }; const resp: any = info.file.response || { result: '' };
await submitData(resp?.result || ''); await submitData(resp?.result || '');
} }
} };
</script> </script>

View File

@ -1693,7 +1693,7 @@ const Status = defineComponent({
/> />
)} )}
{ {
bindParentVisible && ( bindParentVisible.value && (
<BindParentDevice <BindParentDevice
data={device.value} data={device.value}
onCancel={() => { onCancel={() => {

View File

@ -25,13 +25,13 @@
instanceStore.current.deviceType?.text instanceStore.current.deviceType?.text
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="固件版本">{{ <j-descriptions-item label="固件版本">{{
instanceStore.current.firmwareInfo?.version instanceStore.current?.firmwareInfo?.version
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="连接协议">{{ <j-descriptions-item label="连接协议">{{
instanceStore.current?.protocolName instanceStore.current?.transport
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="消息协议">{{ <j-descriptions-item label="消息协议">{{
instanceStore.current.transport instanceStore.current.protocolName
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="创建时间">{{ <j-descriptions-item label="创建时间">{{
instanceStore.current.createTime instanceStore.current.createTime

View File

@ -109,7 +109,9 @@ const loading = ref<boolean>(false);
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const formRef = ref(); const formRef = ref();
const modelRef = reactive({ const modelRef = reactive<{
metrics: any[]
}>({
metrics: [], metrics: [],
}); });

View File

@ -23,11 +23,12 @@
:tab="i.tab" :tab="i.tab"
/> />
</j-tabs> </j-tabs>
<JEmpty v-else style="margin: 250px 0" /> <JEmpty v-else style="margin: 180px 0" />
</div> </div>
<div class="property-box-right"> <div class="property-box-right">
<Event v-if="type === 'event'" :data="data" /> <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>
</div> </div>
</j-card> </j-card>
@ -97,6 +98,13 @@ const onSearch = () => {
} else { } else {
tabList.value = _.cloneDeep(arr) 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 tabChange = (key: string) => {
const dt = tabList.value.find((i) => i.key === key); const dt = tabList.value.find((i) => i.key === key);

View File

@ -9,7 +9,9 @@
<div> <div>
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<AIcon type="ArrowLeftOutlined" @click="onBack" /> <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-divider type="vertical" />
<j-space> <j-space>
<j-badge <j-badge
@ -79,7 +81,7 @@
<j-descriptions-item label="所属产品"> <j-descriptions-item label="所属产品">
<PermissionButton <PermissionButton
type="link" type="link"
style="margin-top: -5px; padding: 0" style="margin-top: -5px; padding: 0"
@click="jumpProduct" @click="jumpProduct"
hasPermission="device/Product:view" hasPermission="device/Product:view"
> >
@ -116,8 +118,8 @@ import Function from './Function/index.vue';
import Modbus from './Modbus/index.vue'; import Modbus from './Modbus/index.vue';
import OPCUA from './OPCUA/index.vue'; import OPCUA from './OPCUA/index.vue';
import EdgeMap from './EdgeMap/index.vue'; import EdgeMap from './EdgeMap/index.vue';
import Parsing from './Parsing/index.vue' import Parsing from './Parsing/index.vue';
import Log from './Log/index.vue' import Log from './Log/index.vue';
import { _deploy, _disconnect } from '@/api/device/instance'; import { _deploy, _disconnect } from '@/api/device/instance';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
@ -149,17 +151,13 @@ const list = ref([
key: 'Metadata', key: 'Metadata',
tab: '物模型', tab: '物模型',
}, },
{
key: 'Log',
tab: '日志管理',
},
{ {
key: 'Function', key: 'Function',
tab: '设备功能', tab: '设备功能',
}, },
{ {
key: 'ChildDevice', key: 'Log',
tab: '子设备', tab: '日志管理',
}, },
]); ]);
@ -174,7 +172,7 @@ const tabs = {
OPCUA, OPCUA,
EdgeMap, EdgeMap,
Parsing, Parsing,
Log Log,
}; };
const getStatus = (id: string) => { const getStatus = (id: string) => {
@ -255,6 +253,17 @@ watchEffect(() => {
tab: '设备诊断', tab: '设备诊断',
}); });
} }
if (
instanceStore.current.features?.find(
(item: any) => item.id === 'transparentCodec',
) &&
!keys.includes('Parsing')
) {
list.value.push({
key: 'Parsing',
tab: '数据解析',
});
}
if ( if (
instanceStore.current.protocol === 'modbus-tcp' && instanceStore.current.protocol === 'modbus-tcp' &&
!keys.includes('Modbus') !keys.includes('Modbus')
@ -273,6 +282,13 @@ watchEffect(() => {
tab: 'OPC UA', tab: 'OPC UA',
}); });
} }
if (instanceStore.current.deviceType?.value === 'gateway') {
//
list.value.push({
key: 'ChildDevice',
tab: '子设备',
});
}
if ( if (
instanceStore.current.accessProvider === 'edge-child-device' && instanceStore.current.accessProvider === 'edge-child-device' &&
instanceStore.current.parentId && instanceStore.current.parentId &&
@ -283,15 +299,6 @@ watchEffect(() => {
tab: '边缘端映射', tab: '边缘端映射',
}); });
} }
if (
instanceStore.current.features?.find((item: any) => item.id === 'transparentCodec') &&
!keys.includes('Parsing')
) {
list.value.push({
key: 'Parsing',
tab: '数据解析',
});
}
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@ -51,7 +51,6 @@
import { queryNoPagingPost } from '@/api/device/product'; import { queryNoPagingPost } from '@/api/device/product';
import { downloadFile } from '@/utils/utils'; import { downloadFile } from '@/utils/utils';
import encodeQuery from '@/utils/encodeQuery'; import encodeQuery from '@/utils/encodeQuery';
import { BASE_API_PATH } from '@/utils/variable';
import { deviceExport } from '@/api/device/instance'; import { deviceExport } from '@/api/device/instance';
const emit = defineEmits(['close']); const emit = defineEmits(['close']);

View File

@ -1,92 +1,106 @@
<template> <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> <div>
<j-badge v-if="flag" status="processing" text="进行中" /> <j-badge v-if="flag" status="processing" text="进行中" />
<j-badge v-else status="success" text="已完成" /> <j-badge v-else status="success" text="已完成" />
</div> </div>
<p>总数量{{count}}</p> <p>总数量{{ count }}</p>
<a style="color: red">{{errMessage}}</a> <a style="color: red">{{ errMessage }}</a>
</j-modal> </j-modal>
</template> </template>
<script lang="ts" setup> <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({ const props = defineProps({
api: { api: {
type: String, type: String,
default: '' default: '',
}, },
type: { type: {
type: String, type: String,
default: '' default: '',
} },
}) });
const eventSource = ref<Record<string, any>>({}) // const eventSource = ref<Record<string, any>>({})
const count = ref<number>(0) const count = ref<number>(0);
const flag = ref<boolean>(false) const flag = ref<boolean>(false);
const errMessage = ref<string>('') const errMessage = ref<string>('');
const isSource = ref<boolean>(false) const isSource = ref<boolean>(false);
const id = ref<string>('') const id = ref<string>('');
const source = ref<Record<string, any>>({}) const source = ref<Record<string, any>>({});
const handleCancel = () => { const handleCancel = () => {
emit('close') emit('close');
} emit('save');
};
// const handleOk = () => {
// emit('close');
// emit('save');
// };
const getData = (api: string) => { const getData = (api: string) => {
let dt = 0 let dt = 0;
const _source = new EventSourcePolyfill(api) const _source = new EventSourcePolyfill(api);
source.value = _source source.value = _source;
_source.onmessage = (e: any) => { _source.onmessage = (e: any) => {
const res = JSON.parse(e.data); const res = JSON.parse(e.data);
switch (props.type) { switch (props.type) {
case 'active': case 'active':
if (res.success) { if (res.success) {
dt += res.total; dt += res.total;
count.value = dt count.value = dt;
} else { } else {
if (res.source) { if (res.source) {
const msg = `${res.source.name}: ${res.message}`; const msg = `${res.source.name}: ${res.message}`;
errMessage.value = msg errMessage.value = msg;
id.value = res.source.id id.value = res.source.id;
isSource.value = true isSource.value = true;
} else { } else {
errMessage.value = res.message errMessage.value = res.message;
} }
} }
break; break;
case 'sync': case 'sync':
dt += res; dt += res;
count.value = dt count.value = dt;
break; break;
case 'import': case 'import':
if (res.success) { if (res.success) {
const temp = res.result.total; const temp = res.result.total;
dt += temp; dt += temp;
count.value = dt count.value = dt;
} else { } else {
errMessage.value = res.message errMessage.value = res.message;
} }
break; break;
default: default:
break; break;
} }
}; };
_source.onerror = () => { _source.onerror = () => {
flag.value = false flag.value = false;
_source.close(); _source.close();
}; };
_source.onopen = () => {}; _source.onopen = () => {};
} };
watch(() => props.api, watch(
() => props.api,
(newValue) => { (newValue) => {
if(newValue) { if (newValue) {
getData(newValue) getData(newValue);
} }
}, },
{deep: true, immediate: true} { deep: true, immediate: true },
) );
</script> </script>

View File

@ -9,12 +9,7 @@
:confirmLoading="loading" :confirmLoading="loading"
> >
<div style="margin-top: 10px"> <div style="margin-top: 10px">
<j-form <j-form :layout="'vertical'" ref="formRef" :model="modelRef">
:layout="'vertical'"
ref="formRef"
:rules="rules"
:model="modelRef"
>
<j-row type="flex"> <j-row type="flex">
<j-col flex="180px"> <j-col flex="180px">
<j-form-item name="photoUrl"> <j-form-item name="photoUrl">
@ -22,14 +17,33 @@
</j-form-item> </j-form-item>
</j-col> </j-col>
<j-col flex="auto"> <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> <template #label>
<span> <span>
ID ID
<j-tooltip title="若不填写系统将自动生成唯一ID"> <j-tooltip
title="若不填写系统将自动生成唯一ID"
>
<AIcon <AIcon
type="QuestionCircleOutlined" type="QuestionCircleOutlined"
style="margin-left: 2px;" /> style="margin-left: 2px"
/>
</j-tooltip> </j-tooltip>
</span> </span>
</template> </template>
@ -39,7 +53,20 @@
:disabled="!!data?.id" :disabled="!!data?.id"
/> />
</j-form-item> </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 <j-input
v-model:value="modelRef.name" v-model:value="modelRef.name"
placeholder="请输入名称" placeholder="请输入名称"
@ -47,13 +74,23 @@
</j-form-item> </j-form-item>
</j-col> </j-col>
</j-row> </j-row>
<j-form-item name="productId"> <j-form-item
name="productId"
:rules="[
{
required: true,
message: '请选择所属产品',
},
]"
>
<template #label> <template #label>
<span>所属产品 <span
>所属产品
<j-tooltip title="只能选择“正常”状态的产品"> <j-tooltip title="只能选择“正常”状态的产品">
<AIcon <AIcon
type="QuestionCircleOutlined" type="QuestionCircleOutlined"
style="margin-left: 2px" /> style="margin-left: 2px"
/>
</j-tooltip> </j-tooltip>
</span> </span>
</template> </template>
@ -68,10 +105,20 @@
v-for="item in productList" v-for="item in productList"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
>{{item.name}}</j-select-option> >{{ item.name }}</j-select-option
>
</j-select> </j-select>
</j-form-item> </j-form-item>
<j-form-item label="说明" name="describe"> <j-form-item
label="说明"
name="describe"
:rules="[
{
max: 200,
message: '最多输入200个字符'
},
]"
>
<j-textarea <j-textarea
v-model:value="modelRef.describe" v-model:value="modelRef.describe"
placeholder="请输入说明" 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( watch(
() => props.data, () => props.data,
(newValue) => { (newValue) => {
@ -199,13 +207,13 @@ const handleSave = () => {
.validate() .validate()
.then(async (_data: any) => { .then(async (_data: any) => {
loading.value = true; loading.value = true;
const obj = {...toRaw(modelRef), ..._data} const obj = { ..._data };
if(!obj.id){ if (!obj.id) {
delete obj.id delete obj.id;
} }
const resp = await update(obj).finally(() => { const resp = await update(obj).finally(() => {
loading.value = false; loading.value = false;
}) });
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
emit('save'); emit('save');

View File

@ -252,6 +252,7 @@
@close="operationVisible = false" @close="operationVisible = false"
:api="api" :api="api"
:type="type" :type="type"
@save="onRefresh"
/> />
<Save <Save
v-if="visible" v-if="visible"
@ -314,6 +315,7 @@ const columns = [
key: 'id', key: 'id',
search: { search: {
type: 'string', type: 'string',
defaultTermType: 'eq'
}, },
}, },
{ {
@ -322,6 +324,7 @@ const columns = [
key: 'name', key: 'name',
search: { search: {
type: 'string', type: 'string',
first: true
}, },
}, },
{ {
@ -389,6 +392,7 @@ const columns = [
hideInTable: true, hideInTable: true,
search: { search: {
type: 'select', type: 'select',
rename: 'productId$product-info',
options: () => options: () =>
new Promise((resolve) => { new Promise((resolve) => {
getProviders().then((resp: any) => { getProviders().then((resp: any) => {

View File

@ -13,17 +13,16 @@
import { useSceneStore } from '@/store/scene'; import { useSceneStore } from '@/store/scene';
import Action from '../action/index.vue'; import Action from '../action/index.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ActionsType } from '@/components/Table'; import { ActionsType } from '@/views/rule-engine/Scene/typings';
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore); const { data } = storeToRefs(sceneStore);
const onActionAdd = (_data: ActionsType) => { const onActionAdd = (_data: any) => {
console.log(_data) if (data.value?.branches && _data) {
// if (data?.branches && _data) { data?.value.branches?.[0].then.push(_data)
// const newThen = [...data?.branches?.[0].then, data]; console.log(data?.value.branches?.[0].then)
// data.branches[0].then = newThen; }
// }
}; };
const onActionUpdate = (_data: ActionsType, type: boolean) => { const onActionUpdate = (_data: ActionsType, type: boolean) => {

View File

@ -18,13 +18,7 @@
}, },
]" ]"
> >
<!-- <j-card-select <CardSelect v-model:value="formModel.type" :options="options"/>
v-model:value="formModel.type"
:options="options"
type="horizontal"
float="right"
/> -->
<a-radio-group v-model:value="formModel.type" :options="options" />
</a-form-item> </a-form-item>
<ActionTypeComponent <ActionTypeComponent
v-bind="props" v-bind="props"
@ -46,6 +40,7 @@ import { PropType } from 'vue';
import { ActionsType } from '../../../typings'; import { ActionsType } from '../../../typings';
import ActionTypeComponent from './ActionTypeComponent.vue'; import ActionTypeComponent from './ActionTypeComponent.vue';
import { randomString } from '@/utils/utils'; import { randomString } from '@/utils/utils';
import CardSelect from '../../components/CardSelect.vue'
const props = defineProps({ const props = defineProps({
branchesName: { branchesName: {

View File

@ -1,11 +1,19 @@
<template> <template>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<!-- <j-card-select <div class="notify-type-warp" :class="{ disabled: disabled }">
v-model:value="notifyType" <div
:options="options" :key="item.id"
:icon-size="106" v-for="item in options"
/> --> class="notify-type-item"
<a-radio-group v-model:value="notifyType" :options="options" @change="onRadioChange" /> :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> </a-spin>
</template> </template>
@ -26,9 +34,13 @@ const props = defineProps({
type: String, type: String,
default: '', default: '',
}, },
disabled: {
type: Boolean,
default: false,
},
}); });
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value']);
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const notifyType = ref(''); const notifyType = ref('');
@ -37,17 +49,19 @@ const options = ref<any[]>([]);
watch( watch(
() => props.value, () => props.value,
(newVal) => { (newVal) => {
notifyType.value = newVal notifyType.value = newVal;
}, },
{ deep: true, immediate: true }, { deep: true, immediate: true },
); );
const onRadioChange = (e: any) => { const onSelect = (val: string) => {
emit('update:value', e.target.value) if (!props.disabled) {
} emit('update:value', val);
}
};
onMounted(() => { onMounted(() => {
loading.value = true loading.value = true;
notice.queryMessageType().then((resp) => { notice.queryMessageType().then((resp) => {
if (resp.status === 200) { if (resp.status === 200) {
options.value = (resp.result as any[]).map((item) => { 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> </script>
<style lang="less" scoped> <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> </style>

View File

@ -26,7 +26,7 @@
:actions=" :actions="
serialArray.length ? serialArray[0].actions : [] serialArray.length ? serialArray[0].actions : []
" "
@add="onAdd" @add="(_item) => onAdd(_item, false)"
@delete="onDelete" @delete="onDelete"
/> />
</div> </div>
@ -50,7 +50,7 @@
? parallelArray[0].actions ? parallelArray[0].actions
: [] : []
" "
@add="onAdd" @add="(_item) => onAdd(_item, true)"
@delete="onDelete" @delete="onDelete"
/> />
</div> </div>
@ -98,6 +98,8 @@ watch(
parallelArray.value = newVal.filter((item) => item.parallel); parallelArray.value = newVal.filter((item) => item.parallel);
serialArray.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) => { const isSerialActions = serialArray.value.some((item) => {
return !!item.actions.length; return !!item.actions.length;
}); });
@ -128,7 +130,7 @@ const onDelete = (_key: string) => {
emit('update', serialArray[0], false); emit('update', serialArray[0], false);
} }
}; };
const onAdd = (actionItem: any) => { const onAdd = (actionItem: any, _parallel: boolean) => {
const newParallelArray = [...parallelArray.value]; const newParallelArray = [...parallelArray.value];
if (newParallelArray.length) { if (newParallelArray.length) {
const indexOf = newParallelArray[0].actions?.findIndex( const indexOf = newParallelArray[0].actions?.findIndex(
@ -140,12 +142,11 @@ const onAdd = (actionItem: any) => {
newParallelArray[0].actions.push(actionItem); newParallelArray[0].actions.push(actionItem);
} }
parallelArray.value = [...newParallelArray]; parallelArray.value = [...newParallelArray];
console.log(parallelArray.value); emit('update', newParallelArray[0], _parallel);
emit('update', newParallelArray[0], true);
} else { } else {
actionItem.key = randomString(); actionItem.key = randomString();
emit('add', { emit('add', {
parallel: true, parallel: _parallel,
key: randomString(), key: randomString(),
actions: [actionItem], actions: [actionItem],
}); });

View File

@ -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>