iot-ui-vue/src/views/Northbound/AliCloud/Detail/index.vue

707 lines
35 KiB
Vue

<template>
<page-container>
<FullPage>
<j-card>
<div class="box">
<div class="left">
<div class="left-content">
<TitleComponent data="基本信息" />
<j-alert
v-if="!!_error && modelRef?.id"
style="margin: 10px 0"
type="warning"
>
<template #message>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
"
>
<span
style="
width: calc(100% - 100px);
text-align: center;
"
>{{ _error }}</span
>
<PermissionButton
:popConfirm="{
title: '确认启用',
onConfirm: onActiveProduct,
}"
size="small"
:hasPermission="'device/Product:action'"
>
立即启用
</PermissionButton>
</div>
</template>
</j-alert>
<j-form
:layout="'vertical'"
ref="formRef"
:model="modelRef"
>
<j-row :gutter="24">
<j-col :span="24">
<j-form-item
label="名称"
name="name"
:rules="[
{
required: true,
message: '请输入名称',
},
{
max: 64,
message: '最多输入64个字符',
},
]"
>
<j-input
placeholder="请输入名称"
v-model:value="modelRef.name"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="['accessConfig', 'regionId']"
:rules="[
{
required: true,
message: '请选择服务地址',
},
]"
>
<template #label>
<span>
服务地址
<j-tooltip
title="阿里云内部给每台机器设置的唯一编号"
>
<AIcon
type="QuestionCircleOutlined"
style="
margin-left: 2px;
"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择服务地址"
v-model:value="
modelRef.accessConfig
.regionId
"
show-search
@blur="productChange"
>
<j-select-option
v-for="item in regionsList"
:key="item.id"
:value="item.id"
:label="item.name"
>{{
item.name
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="[
'accessConfig',
'instanceId',
]"
>
<template #label>
<span>
实例ID
<j-tooltip
title="阿里云物联网平台中的实例ID,没有则不填"
>
<AIcon
type="QuestionCircleOutlined"
style="
margin-left: 2px;
"
/>
</j-tooltip>
</span>
</template>
<j-input
placeholder="请输入实例ID"
v-model:value="
modelRef.accessConfig
.instanceId
"
@blur="productChange"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="[
'accessConfig',
'accessKeyId',
]"
:rules="[
{
required: true,
message: '请输入accessKey',
},
{
max: 64,
message: '最多输入64个字符',
},
]"
>
<template #label>
<span>
accessKey
<j-tooltip
title="用于程序通知方式调用云服务API的用户标识"
>
<AIcon
type="QuestionCircleOutlined"
style="
margin-left: 2px;
"
/>
</j-tooltip>
</span>
</template>
<j-input
placeholder="请输入accessKey"
v-model:value="
modelRef.accessConfig
.accessKeyId
"
@blur="productChange"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
:name="[
'accessConfig',
'accessSecret',
]"
:rules="[
{
required: true,
message:
'请输入accessSecret',
},
{
max: 64,
message: '最多输入64个字符',
},
]"
>
<template #label>
<span>
accessSecret
<j-tooltip
title="用于程序通知方式调用云服务费API的秘钥标识"
>
<AIcon
type="QuestionCircleOutlined"
style="
margin-left: 2px;
"
/>
</j-tooltip>
</span>
</template>
<j-input
placeholder="请输入accessSecret"
v-model:value="
modelRef.accessConfig
.accessSecret
"
@blur="productChange"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<j-form-item
name="bridgeProductKey"
:rules="{
required: true,
message: '请选择网桥产品',
}"
>
<template #label>
<span>
网桥产品
<j-tooltip
title="物联网平台对应的阿里云产品"
>
<AIcon
type="QuestionCircleOutlined"
style="
margin-left: 2px;
"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择网桥产品"
v-model:value="
modelRef.bridgeProductKey
"
show-search
>
<j-select-option
v-for="item in aliyunProductList"
:key="item.productKey"
:value="item.productKey"
:label="item.productName"
>{{
item.productName
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="24">
<p>产品映射</p>
<j-collapse
v-if="modelRef.mappings.length"
:activeKey="activeKey"
@change="onCollChange"
>
<j-collapse-panel
v-for="(
item, index
) in modelRef.mappings"
:key="index"
:forceRender="false"
:header="
item.productKey
? aliyunProductList.find(
(i) =>
i.productKey ===
item.productKey,
)?.productName ||
`产品映射${index + 1}`
: `产品映射${index + 1}`
"
>
<template #extra
><AIcon
type="DeleteOutlined"
@click="delItem(index)"
/></template>
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
label="阿里云产品"
:name="[
'mappings',
index,
'productKey',
]"
:rules="{
required: true,
message:
'请选择阿里云产品',
}"
>
<j-select
placeholder="请选择阿里云产品"
v-model:value="
item.productKey
"
show-search
>
<j-select-option
v-for="i in getAliyunProductList(
item?.productKey ||
'',
)"
:key="
i.productKey
"
:value="
i.productKey
"
:label="
i.productName
"
>{{
i.productName
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="平台产品"
:name="[
'mappings',
index,
'productId',
]"
:rules="[
{
required: true,
message:
'请选择平台产品',
},
{
validator:
_validator,
trigger:
'change',
},
]"
>
<!-- <j-select
placeholder="请选择平台产品"
v-model:value="
item.productId
"
show-search
>
<j-select-option
v-for="i in getPlatProduct(
item.productId ||
'',
)"
:key="i.id"
:value="
i?.id
"
:label="
i.name
"
>{{
i.name
}}</j-select-option
>
</j-select> -->
<MSelect
v-model:value="
item.productId
"
:options="
getPlatProduct(
item.productId ||
'',
)
"
@error="
onPlatError
"
/>
</j-form-item>
</j-col>
</j-row>
</j-collapse-panel>
</j-collapse>
<j-card v-else>
<j-empty />
</j-card>
</j-col>
<j-col :span="24">
<j-button
type="dashed"
style="
width: 100%;
margin-top: 10px;
"
@click="addItem"
>
<AIcon
type="PlusOutlined"
style="margin-left: 2px"
/>添加
</j-button>
</j-col>
<j-col :span="24" style="margin-top: 20px">
<j-form-item
label="说明"
name="description"
:rules="{
max: 200,
message: '最多输入200个字符',
}"
>
<j-textarea
v-model:value="
modelRef.description
"
placeholder="请输入说明"
showCount
:maxlength="200"
/>
</j-form-item>
</j-col>
</j-row>
</j-form>
<div v-if="type === 'edit'">
<PermissionButton
type="primary"
:loading="loading"
@click="saveBtn"
:hasPermission="[
'Northbound/AliCloud:add',
'Northbound/AliCloud:update',
]"
>
保存
</PermissionButton>
</div>
</div>
</div>
<div class="right">
<Doc />
</div>
</div>
</j-card>
</FullPage>
</page-container>
</template>
<script lang="ts" setup>
import Doc from './doc.vue';
import {
savePatch,
detail,
getRegionsList,
getAliyunProductsList,
queryProductList,
} from '@/api/northbound/alicloud';
import _ from 'lodash-es';
import { onlyMessage } from '@/utils/comm';
import MSelect from '../../components/MSelect/index.vue';
import { _deploy } from '@/api/device/product';
const router = useRouter();
const route = useRoute();
const formRef = ref();
const _errorSet = ref<Set<string>>(new Set());
const modelRef = reactive({
id: undefined,
name: undefined,
accessConfig: {
regionId: undefined,
instanceId: undefined,
accessKeyId: undefined,
accessSecret: undefined,
},
bridgeProductKey: undefined,
bridgeProductName: undefined,
mappings: [
{
productKey: undefined,
productId: undefined,
},
],
description: undefined,
});
const addItem = () => {
activeKey.value.push(String(modelRef.mappings.length));
modelRef.mappings.push({
productKey: undefined,
productId: undefined,
});
};
const delItem = (index: number) => {
modelRef.mappings.splice(index, 1);
};
const productList = ref<Record<string, any>[]>([]);
const regionsList = ref<Record<string, any>[]>([]);
const aliyunProductList = ref<Record<string, any>[]>([]);
const loading = ref<boolean>(false);
const type = ref<'edit' | 'view'>('edit');
const activeKey = ref<string[]>(['0']);
const queryRegionsList = async () => {
const resp = await getRegionsList();
if (resp.status === 200) {
regionsList.value = resp.result as Record<string, any>[];
}
};
const getProduct = async () => {
const resp = await queryProductList({
paging: false,
sorts: [{ name: 'createTime', order: 'desc' }],
});
if (resp.status === 200) {
productList.value = resp?.result as Record<string, any>[];
}
};
const getAliyunProduct = async (data: any) => {
if (data.regionId && data.accessKeyId && data.accessSecret) {
const resp: any = await getAliyunProductsList(data);
if (resp.status === 200) {
aliyunProductList.value = resp?.result?.data as Record<
string,
any
>[];
}
}
};
const productChange = () => {
const data = modelRef.accessConfig;
getAliyunProduct(data);
};
const getPlatProduct = (val: string) => {
const arr = modelRef.mappings.map((item) => item?.productId) || [];
const checked = _.cloneDeep(arr);
const _index = checked.findIndex((i) => i === val);
checked.splice(_index, 1);
const list = productList.value.filter(
(i: any) => !checked.includes(i?.id as any),
);
return list || [];
};
const getAliyunProductList = (val: string) => {
const items = modelRef.mappings.map((item) => item?.productKey) || [];
const checked = _.cloneDeep(items);
const _index = checked.findIndex((i) => i === val);
checked.splice(_index, 1);
const list = aliyunProductList.value?.filter(
(i: any) => !checked.includes(i?.productKey as any),
);
return list || [];
};
const onCollChange = (_key: string[]) => {
activeKey.value = _key;
};
const _error = computed(() => {
return _errorSet.value.size ? `当前选择的部分产品为禁用状态` : ''
})
const onActiveProduct = async () => {
[..._errorSet.value.values()].forEach(async (i: any) => {
const resp = await _deploy(i).catch((error) => {
onlyMessage('操作失败', 'error');
});
if(resp?.status === 200) {
_errorSet.value.delete(i)
onlyMessage('操作成功!');
}
});
await getProduct();
};
const onPlatError = (val: any) => {
const _item = productList.value.find((item) => item.id === val);
if (val && _item && !_item?.state) {
_errorSet.value.add(val)
}
};
const _validator = (_rule: any, value: string): Promise<any> =>
new Promise((resolve, reject) => {
const _item = productList.value.find((item) => item.id === value);
if(!modelRef.id || modelRef.id === ':id') {
return resolve('');
} else if (!_item && value) {
return reject('关联产品已被删除,请重新选择');
}
return resolve('');
});
const saveBtn = () => {
formRef.value
.validate()
.then(async (data: any) => {
const product = (aliyunProductList.value || []).find(
(item: any) => item?.productKey === data?.bridgeProductKey,
);
data.bridgeProductName = product?.productName || '';
loading.value = true;
const resp = await savePatch({
...toRaw(modelRef),
...data,
}).finally(() => {
loading.value = false;
});
if (resp.status === 200) {
onlyMessage('操作成功!');
formRef.value.resetFields();
router.push('/iot/northbound/AliCloud');
}
})
.catch((err: any) => {
const _arr = err.errorFields.map((i: any) => i.name);
_arr.map((item: string | any[]) => {
if (item.length === 3 && !activeKey.value.includes(item[1])) {
activeKey.value.push(item[1]);
}
});
});
};
watch(
() => route.params?.id,
async (newId) => {
if (newId) {
queryRegionsList();
await getProduct();
if (newId === ':id' || !newId) return;
const resp = await detail(newId as string);
const _data: any = resp.result;
if (_data) {
getAliyunProduct(_data?.accessConfig);
}
Object.assign(modelRef, _data);
activeKey.value = (_data?.mappings || []).map(
(_: any, index: number) => index,
);
}
},
{ immediate: true, deep: true },
);
watch(
() => route.query.type,
(newVal) => {
if (newVal) {
type.value = newVal as 'edit' | 'view';
}
},
{ immediate: true, deep: true },
);
</script>
<style scoped lang="less">
.box {
position: relative;
.left {
.left-content {
width: 66%;
}
}
.right {
width: 33%;
position: absolute;
right: 0;
top: 0;
overflow-y: auto;
height: 100%;
}
}
</style>