Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
xieyonghong 2023-02-02 10:38:20 +08:00
commit eaea45668c
8 changed files with 506 additions and 20 deletions

View File

@ -115,3 +115,10 @@ export const deleteProduct = (id: string) => server.patch(`/device-product/${id}
* @returns * @returns
*/ */
export const saveProductMetadata = (data: Record<string, unknown>) => server.patch('/device-product', data) export const saveProductMetadata = (data: Record<string, unknown>) => server.patch('/device-product', data)
/**
*
* @param data
* @returns
*/
export const getDeviceNumber = (params:any) => server.get('/device-instance/_count', params)

View File

@ -1,14 +1,28 @@
import { ProductItem } from "@/views/device/Product/typings"; import { ProductItem } from "@/views/device/Product/typings";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { detail} from '@/api/device/product'
export const useProductStore = defineStore({ export const useProductStore = defineStore({
id: 'product', id: 'product',
state: () => ({ state: () => ({
current: {} as ProductItem | undefined current: {} as ProductItem | undefined,
detail: {} as ProductItem | undefined,
tabActiveKey: 'Info'
}), }),
actions: { actions: {
setCurrent(current: ProductItem) { setCurrent(current: ProductItem) {
this.current = current this.current = current
this.detail = current
},
async refresh(id: string) {
const resp = await detail(id)
if(resp.status === 200){
this.current = resp.result
this.detail = resp.result
} }
},
setTabActiveKey(key: string) {
this.tabActiveKey = key
},
} }
}) })

View File

@ -0,0 +1,72 @@
<!-- 配置信息 -->
<template>
<a-card>
<a-descriptions bordered>
<template #title>
<div style="display: flex">
<h3>配置信息</h3>
<div style="margin: 0 0px 0 15px; color: #1d39c4">
<edit-outlined @click="editConfig" />
</div>
</div>
</template>
<a-descriptions-item label="ID">{{
productStore.current.id
}}</a-descriptions-item>
<a-descriptions-item label="产品分类">{{
productStore.current.classifiedName
}}</a-descriptions-item>
<a-descriptions-item label="设备类型">{{
productStore.current.deviceType?.text
}}</a-descriptions-item>
<a-descriptions-item label="接入方式">
<a-button type="link" @click="changeTables">{{
productStore.current.transportProtocol
? productStore.current.transportProtocol
: '配置接入方式'
}}</a-button>
</a-descriptions-item>
<a-descriptions-item label="创建时间">{{
productStore.current.createTime
}}</a-descriptions-item>
<a-descriptions-item label="更新时间">{{
productStore.current.modifyTime
}}</a-descriptions-item>
<a-descriptions-item label="说明" :span="3">
{{ productStore.current.describe }}
</a-descriptions-item>
</a-descriptions>
</a-card>
<!-- 编辑 -->
<Save ref="saveRef" :isAdd="isAdd" :title="title" />
</template>
<script lang="ts" setup>
import { useProductStore } from '@/store/product';
import Save from '../../Save/index.vue';
import {
EditOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
const productStore = useProductStore();
const saveRef = ref();
const isAdd = ref(2);
const title = ref('编辑');
// console.log(productStore.current.deviceType.text, ' productStore');
/**
* 编辑配置信息
*/
const editConfig = () => {
saveRef.value.show(productStore.current);
};
/**
* 切换tabs
*/
const changeTables = () => {
productStore.tabActiveKey = 'Device';
};
</script>

View File

@ -0,0 +1,153 @@
<!-- 设备接入 -->
<template>
<a-card>
<div v-if="productStore.current.accessId === undefined || null">
<a-empty :image="simpleImage">
<template #description>
<span>
请先<a-button type="link" @click="showModal"
>选择</a-button
>设备接入网关用以提供设备接入能力
</span>
</template>
</a-empty>
</div>
<div v-else></div>
</a-card>
<!-- 选择设备 -->
<a-modal
title="设备接入配置"
:visible="visible"
width="1200px"
okText="确定"
cancelText="取消"
@cancel="cancel"
>
<Search :columns="query.columns" target="deviceModal" />
</a-modal>
</template>
<script lang="ts" setup>
import { useProductStore } from '@/store/product';
import { Empty } from 'ant-design-vue';
import {
getProviders,
category,
queryOrgThree,
queryGatewayList,
queryProductList,
_deploy,
_undeploy,
deleteProduct,
addProduct,
editProduct,
queryProductId,
} from '@/api/device/product';
import { isNoCommunity } from '@/utils/utils';
const productStore = useProductStore();
const simpleImage = ref(Empty.PRESENTED_IMAGE_SIMPLE);
const visible = ref(false);
const listData = ref([]);
/**
* 显示弹窗
*/
const showModal = () => {
visible.value = true;
};
/**
* 关闭弹窗
*/
const cancel = () => {
visible.value = false;
};
/**
* 筛选
*/
const query = reactive({
columns: [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
first: true,
type: 'string',
},
},
{
title: '网关类型',
key: 'accessProvider',
dataIndex: 'accessProvider',
search: {
type: 'select',
options: async () => {
return new Promise((res) => {
getProviders().then((resp: any) => {
listData.value = [];
// const list = () => {
if (isNoCommunity) {
listData.value = (resp?.result || []).map(
(item: any) => ({
label: item.name,
value: item.id,
}),
);
} else {
listData.value = (resp?.result || [])
.filter((i: any) =>
[
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(i.id),
)
.map((item: any) => ({
label: item.name,
value: item.id,
}));
// }
}
res(listData.value);
});
});
},
},
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
search: {
type: 'select',
options: [
{
label: '正常',
value: 1,
},
{
label: '禁用',
value: 0,
},
],
},
},
{
title: '说明',
key: 'describe',
dataIndex: 'describe',
search: {
type: 'string',
},
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 250,
scopedSlots: true,
},
],
});
</script>

View File

@ -0,0 +1,179 @@
<template>
<page-container
:tabList="list"
@back="onBack"
:tabActiveKey="productStore.active"
@tabChange="onTabChange"
>
<template #title>
<div>
<div style="display: flex; align-items: center">
<div>{{ productStore.current.name }}</div>
<div style="margin: -5px 0 0 20px">
<a-popconfirm
title="确认禁用"
@confirm="handleUndeploy"
v-if="productStore.current.state === 1"
okText="确定"
cancelText="取消"
>
<a-switch
:checked="productStore.current.state === 1"
checked-children="正常"
un-checked-children="禁用"
/>
</a-popconfirm>
<a-popconfirm
title="确认启用"
@confirm="handleDeploy"
v-if="productStore.current.state === 0"
okText="确定"
cancelText="取消"
>
<a-switch
:unCheckedValue="
productStore.current.state === 0
"
checked-children="正常"
un-checked-children="禁用"
/>
</a-popconfirm>
</div>
</div>
</div>
<div style="padding-top: 10px">
<a-descriptions size="small" :column="4">
<a-descriptions-item label="设备数量"
>1</a-descriptions-item
>
</a-descriptions>
</div>
</template>
<template #extra>
<a-popconfirm
title="确认应用配置"
@confirm="handleCofig"
okText="确定"
cancelText="取消"
>
<a-button
:disabled="productStore.current.state === 0"
type="primary"
>应用配置</a-button
>
</a-popconfirm>
</template>
<component :is="tabs[productStore.tabActiveKey]" />
</page-container>
</template>
<script lang="ts" setup>
import { useProductStore } from '@/store/product';
import Info from './BasicInfo/indev.vue';
import Device from './DeviceAccess/index.vue';
import Metadata from '../../../device/components/Metadata/index.vue';
// import Metadata from '../../../components/Metadata/index.vue';
import { _deploy, _undeploy, getDeviceNumber } from '@/api/device/product';
import { message } from 'ant-design-vue';
import { getImage } from '@/utils/comm';
import encodeQuery from '@/utils/encodeQuery';
const route = useRoute();
const checked = ref<boolean>(true);
const productStore = useProductStore();
const searchParams = ref({
terms1: [
{
column: 'productId',
termType: 'eq',
value: productStore.current?.id,
},
],
terms2: undefined,
type: 'and',
});
const list = [
{
key: 'Info',
tab: '配置信息',
},
{
key: 'Metadata',
tab: '物模型',
},
{
key: 'Device',
tab: '设备接入',
},
];
const tabs = {
Info,
Metadata,
Device,
};
watch(
() => route.params.id,
(newId) => {
if (newId) {
productStore.tabActiveKey = 'Info';
productStore.refresh(newId as string);
}
},
{ immediate: true, deep: true },
);
const onBack = () => {};
const onTabChange = (e: string) => {
productStore.tabActiveKey = e;
};
/**
* 启用产品
*/
const handleDeploy = async () => {
if (productStore.current.id) {
const resp = await _deploy(productStore.current.id);
if (resp.status === 200) {
message.success('操作成功!');
productStore.refresh(productStore.current.id);
}
}
};
/**
* 禁用产品
*/
const handleUndeploy = async () => {
if (productStore.current.id) {
const resp = await _undeploy(productStore.current.id);
if (resp.status === 200) {
message.success('操作成功!');
productStore.refresh(productStore.current.id);
}
}
};
/**
* 查询设备数量
*/
const getNunmber = async () => {
// const params = new URLSearchParams();
// params.append('q', JSON.stringify(searchParams.value));
// params.append('target', 'device-instance');
// console.log(params, ' params');
// const res = await getDeviceNumber(
// encodeQuery({ terms: { productId: params?.id } }),
// );
};
getNunmber();
</script>
<style scoped lang="less">
.ant-switch-loading,
.ant-switch-disabled {
cursor: not-allowed;
}
</style>

View File

@ -22,8 +22,8 @@
<div class="product-title">产品创建成功</div> <div class="product-title">产品创建成功</div>
</div> </div>
<div style="display: flex"> <div style="display: flex">
<div class="product-id">产品ID: 12333</div> <div class="product-id">产品ID: {{ idValue.value }}</div>
<div class="product-btn" @click="jump">查看详情</div> <div class="product-btn" @click="showDetail">查看详情</div>
</div> </div>
<div>接下来推荐操作:</div> <div>接下来推荐操作:</div>
<div class="product-main">1配置产品接入方式</div> <div class="product-main">1配置产品接入方式</div>
@ -47,8 +47,12 @@
</template> </template>
<script lang="ts" setup name="DialogTips"> <script lang="ts" setup name="DialogTips">
import { getImage } from '@/utils/comm.ts'; import { getImage } from '@/utils/comm.ts';
import { useProductStore } from '@/store/product';
import { CheckCircleOutlined } from '@ant-design/icons-vue'; import { CheckCircleOutlined } from '@ant-design/icons-vue';
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const productStore = useProductStore();
const router = useRouter();
const idValue = ref({});
/** /**
* 弹窗关闭 * 弹窗关闭
*/ */
@ -58,13 +62,22 @@ const cancel = () => {
/** /**
* 显示弹窗 * 显示弹窗
*/ */
const show = () => { const show = (id: string) => {
visible.value = true; visible.value = true;
idValue.value = id;
};
/**
* 查看详情
*/
const showDetail = () => {
jump(idValue.value);
}; };
/** /**
* 跳转页面 * 跳转页面
*/ */
const jump = () => {}; const jump = (id: string) => {
router.push('/iot/device/product/detail/' + id);
};
defineExpose({ defineExpose({
show: show, show: show,
}); });

View File

@ -207,7 +207,7 @@ import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm';
import { FILE_UPLOAD } from '@/api/comm'; import { FILE_UPLOAD } from '@/api/comm';
import { isInput } from '@/utils/regular'; import { isInput } from '@/utils/regular';
import type { Rule } from 'ant-design-vue/es/form'; import type { Rule } from 'ant-design-vue/es/form';
import { queryProductId } from '@/api/device/product'; import { queryProductId, addProduct, editProduct } from '@/api/device/product';
import { import {
SearchOutlined, SearchOutlined,
CheckOutlined, CheckOutlined,
@ -278,6 +278,7 @@ const validateInput = async (_rule: Rule, value: string) => {
if (!isInput(value)) { if (!isInput(value)) {
return Promise.reject('请输入英文或者数字或者-或者_'); return Promise.reject('请输入英文或者数字或者-或者_');
} else { } else {
if (props.isAdd === 1) {
const res = await queryProductId(value); const res = await queryProductId(value);
if (res.status === 200 && res.result) { if (res.status === 200 && res.result) {
return Promise.reject('ID重复'); return Promise.reject('ID重复');
@ -285,6 +286,7 @@ const validateInput = async (_rule: Rule, value: string) => {
return Promise.resolve(); return Promise.resolve();
} }
} }
}
} else { } else {
return Promise.resolve(); return Promise.resolve();
} }
@ -355,9 +357,8 @@ const show = (data: any) => {
form.describe = ''; form.describe = '';
form.id = ''; form.id = '';
disabled.value = false; disabled.value = false;
dialogRef.value.show();
} }
// visible.value = true; visible.value = true;
}; };
/** /**
@ -376,13 +377,34 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
const submitData = () => { const submitData = () => {
formRef.value formRef.value
.validate() .validate()
.then(async () => {}) .then(async () => {
//
if (props.isAdd === 1) {
const res = await addProduct(form);
if (res.status === 200) {
message.success('保存成功!');
visible.value = false;
dialogRef.value.show(form.id);
} else {
message.error('操作失败');
}
} else if (props.isAdd === 2) {
//
const res = await editProduct(form);
if (res.status === 200) {
message.success('保存成功!');
visible.value = false;
} else {
message.error('操作失败');
}
}
})
.catch((err: any) => {}); .catch((err: any) => {});
}; };
/** /**
* 初始化 * 初始化
*/ */
// queryProductTree(); queryProductTree();
defineExpose({ defineExpose({
show: show, show: show,
}); });

View File

@ -30,7 +30,12 @@
</slot> </slot>
</template> </template>
<template #content> <template #content>
<h3>{{ slotProps.name }}</h3> <h3
@click.stop="handleView(slotProps.id)"
style="font-weight: 600"
>
{{ slotProps.name }}
</h3>
<a-row> <a-row>
<a-col :span="12"> <a-col :span="12">
<div class="card-item-content-text"> <div class="card-item-content-text">
@ -49,6 +54,8 @@
v-if="item.popConfirm" v-if="item.popConfirm"
v-bind="item.popConfirm" v-bind="item.popConfirm"
:disabled="item.disabled" :disabled="item.disabled"
okText="确定"
cancelText="取消"
> >
<a-button :disabled="item.disabled"> <a-button :disabled="item.disabled">
<AIcon <AIcon
@ -96,7 +103,12 @@
:key="i.key" :key="i.key"
v-bind="i.tooltip" v-bind="i.tooltip"
> >
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm"> <a-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
okText="确定"
cancelText="取消"
>
<a-button <a-button
:disabled="i.disabled" :disabled="i.disabled"
style="padding: 0" style="padding: 0"
@ -155,6 +167,7 @@ import Save from './Save/index.vue';
/** /**
* 表格数据 * 表格数据
*/ */
const router = useRouter();
const isAdd = ref<number>(0); const isAdd = ref<number>(0);
const title = ref<string>(''); const title = ref<string>('');
const statusMap = new Map(); const statusMap = new Map();
@ -238,7 +251,9 @@ const getActions = (
title: '查看', title: '查看',
}, },
icon: 'EyeOutlined', icon: 'EyeOutlined',
onClick: () => {}, onClick: () => {
handleView(data.id);
},
}, },
{ {
key: 'edit', key: 'edit',
@ -295,7 +310,7 @@ const getActions = (
text: '删除', text: '删除',
disabled: data.state !== 0, disabled: data.state !== 0,
tooltip: { tooltip: {
title: data.state !== 0 ? '已启用的设备不能删除' : '删除', title: data.state !== 0 ? '已启用的产品不能删除' : '删除',
}, },
popConfirm: { popConfirm: {
title: '确认删除?', title: '确认删除?',
@ -312,9 +327,14 @@ const getActions = (
icon: 'DeleteOutlined', icon: 'DeleteOutlined',
}, },
]; ];
if (type === 'card')
return actions.filter((i: ActionsType) => i.key !== 'view');
return actions; return actions;
}; };
/**
* 新增
*/
const add = () => { const add = () => {
isAdd.value = 1; isAdd.value = 1;
title.value = '新增'; title.value = '新增';
@ -322,6 +342,12 @@ const add = () => {
saveRef.value.show(currentForm.value); saveRef.value.show(currentForm.value);
}); });
}; };
/**
* 查看
*/
const handleView = (id: string) => {
router.push('/iot/device/product/detail/' + id);
};
// //
const listData = ref([]); const listData = ref([]);