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

This commit is contained in:
jackhoo_98 2023-03-30 18:02:26 +08:00
commit f878cffe82
63 changed files with 6231 additions and 5791 deletions

View File

@ -5,6 +5,7 @@ type DepartmentStateType = {
productId: string; productId: string;
optType: string | undefined; optType: string | undefined;
crossPageKeys: string[]; crossPageKeys: string[];
changedApis: any;
} }
export const useDepartmentStore = defineStore({ export const useDepartmentStore = defineStore({
@ -16,6 +17,7 @@ export const useDepartmentStore = defineStore({
// 2. optType === ': 产品资产分配后, 自动弹出设备资产分配 // 2. optType === ': 产品资产分配后, 自动弹出设备资产分配
optType: '', optType: '',
crossPageKeys: [], // 表格跨页多选的keys crossPageKeys: [], // 表格跨页多选的keys
changedApis: {},
}), }),
actions: { actions: {
setProductId(value: string) { setProductId(value: string) {
@ -27,6 +29,9 @@ export const useDepartmentStore = defineStore({
setSelectedKeys(value: string[], type?: string) { setSelectedKeys(value: string[], type?: string) {
// 分页保留选中项 // 分页保留选中项
this.crossPageKeys = type === 'concat' ? [...new Set([...this.crossPageKeys, ...value])] : value; this.crossPageKeys = type === 'concat' ? [...new Set([...this.crossPageKeys, ...value])] : value;
},
setChangedApis(value: any) {
this.changedApis = { ...this.changedApis, ...value };
} }
} }
}) })

View File

@ -57,7 +57,7 @@ import { getImage } from '@/utils/comm';
<style lang="less" scoped> <style lang="less" scoped>
.doc { .doc {
height: 1000px; height: 100%;
padding: 24px; padding: 24px;
overflow-y: auto; overflow-y: auto;
color: rgba(#000, 0.8); color: rgba(#000, 0.8);

View File

@ -1,371 +1,418 @@
<template> <template>
<page-container> <page-container>
<j-card> <FullPage>
<j-row :gutter="24"> <j-card>
<j-col :span="16"> <div class="box">
<TitleComponent data="基本信息" /> <div class="left">
<j-form <div class="left-content">
:layout="'vertical'" <TitleComponent data="基本信息" />
ref="formRef" <j-form
:model="modelRef" :layout="'vertical'"
> ref="formRef"
<j-row :gutter="24"> :model="modelRef"
<j-col :span="24"> >
<j-form-item <j-row :gutter="24">
label="名称" <j-col :span="24">
name="name" <j-form-item
:rules="[ label="名称"
{ name="name"
required: true, :rules="[
message: '请输入名称', {
}, required: true,
{ message: '请输入名称',
max: 64, },
message: '最多输入64个字符', {
}, 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-input
</j-form-item> placeholder="请输入名称"
</j-col> v-model:value="modelRef.name"
<j-col :span="24"> />
<j-form-item </j-form-item>
:name="['accessConfig', 'instanceId']" </j-col>
> <j-col :span="24">
<template #label> <j-form-item
<span> :name="['accessConfig', 'regionId']"
实例ID :rules="[
<j-tooltip {
title="阿里云物联网平台中的实例ID,没有则不填" required: true,
> message: '请选择服务地址',
<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> <template #label>
</j-form-item> <span>
</j-col> 服务地址
<j-col :span="24"> <j-tooltip
<p>产品映射</p> title="阿里云内部给每台机器设置的唯一编号"
<j-collapse
v-if="modelRef.mappings.length"
:activeKey="activeKey"
@change="onCollChange"
>
<j-collapse-panel
v-for="(
item, index
) in modelRef.mappings"
:key="index"
: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 <AIcon
v-for="i in getAliyunProductList( type="QuestionCircleOutlined"
item?.productKey || '' style="
)" margin-left: 2px;
:key="i.productKey"
:value="
i.productKey
" "
:label=" />
i.productName </j-tooltip>
" </span>
>{{ </template>
i.productName <j-select
}}</j-select-option placeholder="请选择服务地址"
> v-model:value="
</j-select> modelRef.accessConfig
</j-form-item> .regionId
</j-col> "
<j-col :span="12"> show-search
<j-form-item @blur="productChange"
label="平台产品" >
:name="[ <j-select-option
'mappings', v-for="item in regionsList"
index, :key="item.id"
'productId', :value="item.id"
]" :label="item.name"
:rules="{ >{{
required: true, item.name
message: }}</j-select-option
'请选择平台产品',
}"
> >
<j-select </j-select>
placeholder="请选择平台产品" </j-form-item>
v-model:value=" </j-col>
item.productId <j-col :span="24">
" <j-form-item
show-search :name="[
'accessConfig',
'instanceId',
]"
>
<template #label>
<span>
实例ID
<j-tooltip
title="阿里云物联网平台中的实例ID,没有则不填"
> >
<j-select-option <AIcon
v-for="i in getPlatProduct( type="QuestionCircleOutlined"
item.productId || '' style="
)" margin-left: 2px;
:key="i.id" "
:value="i?.id" />
:label="i.name" </j-tooltip>
>{{ </span>
i.name </template>
}}</j-select-option <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"
: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> <j-select
</j-form-item> placeholder="请选择阿里云产品"
</j-col> v-model:value="
</j-row> item.productKey
</j-collapse-panel> "
</j-collapse> show-search
<j-card v-else> >
<j-empty /> <j-select-option
</j-card> v-for="i in getAliyunProductList(
</j-col> item?.productKey ||
<j-col :span="24"> '',
<j-button )"
type="dashed" :key="
style="width: 100%; margin-top: 10px" i.productKey
@click="addItem" "
: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:
'请选择平台产品',
}"
>
<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>
</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',
]"
> >
<AIcon 保存
type="PlusOutlined" </PermissionButton>
style="margin-left: 2px" </div>
/> </div>
</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>
</j-col> <div class="right">
<j-col :span="8"> <Doc />
<Doc /> </div>
</j-col> </div>
</j-row> </j-card>
</j-card> </FullPage>
</page-container> </page-container>
</template> </template>
@ -489,14 +536,16 @@ const saveBtn = () => {
.validate() .validate()
.then(async (data: any) => { .then(async (data: any) => {
const product = (aliyunProductList.value || []).find( const product = (aliyunProductList.value || []).find(
(item: any) => (item: any) => item?.productKey === data?.bridgeProductKey,
item?.productKey === data?.bridgeProductKey,
); );
data.bridgeProductName = product?.productName || ''; data.bridgeProductName = product?.productName || '';
loading.value = true; loading.value = true;
const resp = await savePatch({...toRaw(modelRef), ...data}).finally(() => { const resp = await savePatch({
...toRaw(modelRef),
...data,
}).finally(() => {
loading.value = false; loading.value = false;
}) });
if (resp.status === 200) { if (resp.status === 200) {
message.success('操作成功!'); message.success('操作成功!');
formRef.value.resetFields(); formRef.value.resetFields();
@ -539,4 +588,23 @@ watch(
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },
); );
</script> </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>

View File

@ -5,119 +5,133 @@
target="northbound-aliyun" target="northbound-aliyun"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="instanceRef" <JProTable
:columns="columns" ref="instanceRef"
:request="query" :columns="columns"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :request="query"
:params="params" :defaultParams="{
> sorts: [{ name: 'createTime', order: 'desc' }],
<template #headerTitle> }"
<j-space> :params="params"
<PermissionButton >
type="primary" <template #headerTitle>
@click="handleAdd" <j-space>
hasPermission="Northbound/AliCloud:add" <PermissionButton
type="primary"
@click="handleAdd"
hasPermission="Northbound/AliCloud:add"
>
<template #icon
><AIcon type="PlusOutlined"
/></template>
新增
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleView(slotProps.id)"
:actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
> >
<template #icon><AIcon type="PlusOutlined" /></template> <template #img>
新增 <img :src="getImage('/northbound/aliyun.png')" />
</PermissionButton> </template>
</j-space> <template #content>
</template> <Ellipsis style="width: calc(100% - 100px)">
<template #card="slotProps"> <span style="font-size: 16px; font-weight: 600">
<CardBox {{ slotProps.name }}
:value="slotProps" </span>
@click="handleView(slotProps.id)" </Ellipsis>
:actions="getActions(slotProps, 'card')" <j-row style="margin-top: 15px">
:status="slotProps.state?.value" <j-col :span="12">
:statusText="slotProps.state?.text" <div class="card-item-content-text">
:statusNames="{ 网桥产品
enabled: 'processing',
disabled: 'error',
}"
>
<template #img>
<img :src="getImage('/northbound/aliyun.png')" />
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row style="margin-top: 15px">
<j-col :span="12">
<div class="card-item-content-text">
网桥产品
</div>
<Ellipsis>
<div>
{{ slotProps?.bridgeProductName }}
</div> </div>
</Ellipsis> <Ellipsis>
</j-col> <div>
<j-col :span="12"> {{ slotProps?.bridgeProductName }}
<div class="card-item-content-text"> </div>
<label>说明</label> </Ellipsis>
</div> </j-col>
<Ellipsis> <j-col :span="12">
<div>{{ slotProps?.description }}</div> <div class="card-item-content-text">
</Ellipsis> <label>说明</label>
</j-col> </div>
</j-row> <Ellipsis>
</template> <div>{{ slotProps?.description }}</div>
<template #actions="item"> </Ellipsis>
<PermissionButton </j-col>
:disabled="item.disabled" </j-row>
:popConfirm="item.popConfirm" </template>
:tooltip="item.tooltip" <template #actions="item">
@click="item.onClick" <PermissionButton
:hasPermission="'Northbound/AliCloud:' + item.key" :disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="item.tooltip"
@click="item.onClick"
:hasPermission="
'Northbound/AliCloud:' + item.key
"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
/>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<AIcon <PermissionButton
type="DeleteOutlined" :disabled="i.disabled"
v-if="item.key === 'delete'" :popConfirm="i.popConfirm"
/> :tooltip="i.tooltip"
<template v-else> style="padding: 0 5px"
<AIcon :type="item.icon" /> @click="i.onClick"
<span>{{ item?.text }}</span> type="link"
</template> :danger="i.key === 'delete'"
</PermissionButton> :hasPermission="
</template> i.key === 'view'
</CardBox> ? true
</template> : 'Northbound/AliCloud:' + i.key
<template #state="slotProps"> "
<BadgeStatus >
:status="slotProps.state?.value" <template #icon
:text="slotProps.state?.text" ><AIcon :type="i.icon"
:statusNames="{ /></template>
enabled: 'processing', </PermissionButton>
disabled: 'error', </template>
}" </j-space>
/> </template>
</template> </JProTable>
<template #action="slotProps"> </FullPage>
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="i.tooltip"
style="padding: 0 5px"
@click="i.onClick"
type="link"
:danger="i.key === 'delete'"
:hasPermission="i.key === 'view' ? true : 'Northbound/AliCloud:' + i.key"
>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</page-container> </page-container>
</template> </template>

View File

@ -64,7 +64,7 @@ import { getImage } from '@/utils/comm';
<style lang="less" scoped> <style lang="less" scoped>
.doc { .doc {
height: 1000px; height: 100%;
padding: 24px; padding: 24px;
overflow-y: auto; overflow-y: auto;
color: rgba(#000, 0.8); color: rgba(#000, 0.8);

View File

@ -1,437 +1,479 @@
<template> <template>
<page-container> <page-container>
<j-card> <FullPage>
<j-row :gutter="24"> <j-card>
<j-col :span="16"> <div class="box">
<TitleComponent data="基本信息" /> <div class="left">
<j-form <div class="left-content">
:layout="'vertical'" <TitleComponent data="基本信息" />
ref="formRef" <j-form
:model="modelRef" :layout="'vertical'"
> ref="formRef"
<j-row :gutter="24"> :model="modelRef"
<j-col :span="24"> >
<j-form-item <j-row :gutter="24">
label="名称" <j-col :span="24">
name="name" <j-form-item
:rules="[ label="名称"
{ name="name"
required: true, :rules="[
message: '请输入名称', {
}, required: true,
{ message: '请输入名称',
max: 64, },
message: '最多输入64个字符', {
}, max: 64,
message: '最多输入64个字符',
},
]"
>
<j-input
placeholder="请输入名称"
v-model:value="modelRef.name"
/>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="产品"
name="id"
:rules="[
{
required: true,
message: '请选择产品',
},
]"
>
<j-select
:disabled="
type !== 'edit' &&
modelRef.id &&
modelRef.id !== ':id'
"
placeholder="请选择产品"
v-model:value="modelRef.id"
show-search
@change="productChange"
>
<j-select-option
v-for="item in productList"
:key="item.id"
:value="item.id"
:label="item.name"
>{{
item.name
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="applianceType"
:rules="{
required: true,
message: '请选择设备类型',
}"
>
<template #label>
<span>
设备类型
<j-tooltip
title="DuerOS平台拟定的规范"
>
<AIcon
type="QuestionCircleOutlined"
style="
margin-left: 2px;
"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择设备类型"
v-model:value="
modelRef.applianceType
"
show-search
@change="typeChange"
>
<j-select-option
v-for="item in typeList"
:key="item.id"
:value="item.id"
:label="item.name"
>{{
item.name
}}</j-select-option
>
</j-select>
</j-form-item>
<j-form-item
name="productName"
v-show="false"
label="产品名称"
>
<j-input
v-model:value="
modelRef.productName
"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<p>动作映射</p>
<j-collapse
v-if="
modelRef.actionMappings.length
"
:activeKey="actionActiveKey"
@change="onActionCollChange"
>
<j-collapse-panel
v-for="(
item, index
) in modelRef.actionMappings"
:key="index"
:header="
item.action
? getTypesActions(
item.action,
).find(
(i) =>
i.id ===
item.action,
)?.name
: `动作映射${index + 1}`
"
>
<template #extra
><AIcon
type="DeleteOutlined"
@click="delItem(index)"
/></template>
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
:name="[
'actionMappings',
index,
'action',
]"
:rules="{
required: true,
message:
'请选择动作',
}"
>
<template #label>
<span>
动作
<j-tooltip
title="DuerOS平台拟定的设备类型具有的相关动作"
>
<AIcon
type="QuestionCircleOutlined"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择动作"
v-model:value="
item.action
"
show-search
>
<j-select-option
v-for="i in getTypesActions(
item.action ||
'',
)"
:key="i.id"
:value="
i.id
"
:label="
i.name
"
>{{
i.name
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
:name="[
'actionMappings',
index,
'actionType',
]"
:rules="{
required: true,
message:
'请选择操作',
}"
>
<template #label>
<span>
操作
<j-tooltip
title="映射物联网平台中所选产品具备的动作"
>
<AIcon
type="QuestionCircleOutlined"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择操作"
v-model:value="
item.actionType
"
show-search
@change="
() =>
onActionTypeChange(
index,
)
"
>
<j-select-option
value="command"
>下发指令</j-select-option
>
<j-select-option
value="latestData"
>获取历史数据</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col
:span="24"
v-if="item.actionType"
>
<j-form-item
:name="[
'actionMappings',
index,
'command',
]"
>
<Command
ref="command"
:metadata="
findProductMetadata
"
v-model:modelValue="
item.command
"
:actionType="
item.actionType
"
/>
</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">
<p style="margin-top: 20px">属性映射</p>
<j-collapse
v-if="
modelRef.propertyMappings.length
"
:activeKey="propertyActiveKey"
@change="onPropertyCollChange"
>
<j-collapse-panel
v-for="(
item, index
) in modelRef.propertyMappings"
:key="index"
:header="
item.source
? getDuerOSProperties(
item.source,
).find(
(i) =>
i.id ===
item.source,
)?.name
: `属性映射${index + 1}`
"
>
<template #extra
><AIcon
type="DeleteOutlined"
@click="
delPropertyItem(
index,
)
"
/></template>
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
label="DuerOS属性"
:name="[
'propertyMappings',
index,
'source',
]"
:rules="{
required: true,
message:
'请选择DuerOS属性',
}"
>
<j-select
placeholder="请选择DuerOS属性"
v-model:value="
item.source
"
show-search
>
<j-select-option
v-for="i in getDuerOSProperties(
item.source ||
'',
)"
:key="i.id"
:value="
i.id
"
>{{
i.name
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="平台属性"
:name="[
'propertyMappings',
index,
'target',
]"
:rules="{
required: true,
message:
'请选择平台属性',
}"
>
<j-select
placeholder="请选择平台属性"
v-model:value="
item.target
"
mode="tags"
show-search
>
<j-select-option
v-for="i in getProductProperties(
item.target,
)"
:key="i.id"
:value="
i.id
"
>{{
i.name
}}</j-select-option
>
</j-select>
</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="addPropertyItem"
>
<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/DuerOS:add',
'Northbound/DuerOS:update',
]" ]"
> >
<j-input 保存
placeholder="请输入名称" </PermissionButton>
v-model:value="modelRef.name" </div>
/> </div>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="产品"
name="id"
:rules="[
{
required: true,
message: '请选择产品',
},
]"
>
<j-select
:disabled="
type !== 'edit' &&
modelRef.id &&
modelRef.id !== ':id'
"
placeholder="请选择产品"
v-model:value="modelRef.id"
show-search
@change="productChange"
>
<j-select-option
v-for="item in productList"
:key="item.id"
:value="item.id"
:label="item.name"
>{{ item.name }}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
name="applianceType"
:rules="{
required: true,
message: '请选择设备类型',
}"
>
<template #label>
<span>
设备类型
<j-tooltip
title="DuerOS平台拟定的规范"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择设备类型"
v-model:value="modelRef.applianceType"
show-search
@change="typeChange"
>
<j-select-option
v-for="item in typeList"
:key="item.id"
:value="item.id"
:label="item.name"
>{{ item.name }}</j-select-option
>
</j-select>
</j-form-item>
<j-form-item
name="productName"
v-show="false"
label="产品名称"
>
<j-input
v-model:value="modelRef.productName"
/>
</j-form-item>
</j-col>
<j-col :span="24">
<p>动作映射</p>
<j-collapse
v-if="modelRef.actionMappings.length"
:activeKey="actionActiveKey"
@change="onActionCollChange"
>
<j-collapse-panel
v-for="(
item, index
) in modelRef.actionMappings"
:key="index"
:header="
item.action
? getTypesActions(
item.action,
).find(
(i) =>
i.id === item.action,
)?.name
: `动作映射${index + 1}`
"
>
<template #extra
><AIcon
type="DeleteOutlined"
@click="delItem(index)"
/></template>
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
:name="[
'actionMappings',
index,
'action',
]"
:rules="{
required: true,
message: '请选择动作',
}"
>
<template #label>
<span>
动作
<j-tooltip
title="DuerOS平台拟定的设备类型具有的相关动作"
>
<AIcon
type="QuestionCircleOutlined"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择动作"
v-model:value="
item.action
"
show-search
>
<j-select-option
v-for="i in getTypesActions(
item.action ||
'',
)"
:key="i.id"
:value="i.id"
:label="i.name"
>{{
i.name
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
:name="[
'actionMappings',
index,
'actionType',
]"
:rules="{
required: true,
message: '请选择操作',
}"
>
<template #label>
<span>
操作
<j-tooltip
title="映射物联网平台中所选产品具备的动作"
>
<AIcon
type="QuestionCircleOutlined"
/>
</j-tooltip>
</span>
</template>
<j-select
placeholder="请选择操作"
v-model:value="
item.actionType
"
show-search
@change="
() =>
onActionTypeChange(
index,
)
"
>
<j-select-option
value="command"
>下发指令</j-select-option
>
<j-select-option
value="latestData"
>获取历史数据</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col
:span="24"
v-if="item.actionType"
>
<j-form-item
:name="[
'actionMappings',
index,
'command',
]"
>
<Command
ref="command"
:metadata="
findProductMetadata
"
v-model:modelValue="
item.command
"
:actionType="
item.actionType
"
/>
</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">
<p style="margin-top: 20px">属性映射</p>
<j-collapse
v-if="modelRef.propertyMappings.length"
:activeKey="propertyActiveKey"
@change="onPropertyCollChange"
>
<j-collapse-panel
v-for="(
item, index
) in modelRef.propertyMappings"
:key="index"
:header="
item.source
? getDuerOSProperties(
item.source,
).find(
(i) =>
i.id === item.source,
)?.name
: `属性映射${index + 1}`
"
>
<template #extra
><AIcon
type="DeleteOutlined"
@click="delPropertyItem(index)"
/></template>
<j-row :gutter="24">
<j-col :span="12">
<j-form-item
label="DuerOS属性"
:name="[
'propertyMappings',
index,
'source',
]"
:rules="{
required: true,
message:
'请选择DuerOS属性',
}"
>
<j-select
placeholder="请选择DuerOS属性"
v-model:value="
item.source
"
show-search
>
<j-select-option
v-for="i in getDuerOSProperties(
item.source ||
'',
)"
:key="i.id"
:value="i.id"
>{{
i.name
}}</j-select-option
>
</j-select>
</j-form-item>
</j-col>
<j-col :span="12">
<j-form-item
label="平台属性"
:name="[
'propertyMappings',
index,
'target',
]"
:rules="{
required: true,
message:
'请选择平台属性',
}"
>
<j-select
placeholder="请选择平台属性"
v-model:value="
item.target
"
mode="tags"
show-search
>
<j-select-option
v-for="i in getProductProperties(
item.target,
)"
:key="i.id"
:value="i.id"
>{{
i.name
}}</j-select-option
>
</j-select>
</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="addPropertyItem"
>
<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/DuerOS:add',
'Northbound/DuerOS:update',
]"
>
保存
</PermissionButton>
</div> </div>
</j-col> <div class="right">
<j-col :span="8"> <Doc />
<Doc /> </div>
</j-col> </div>
</j-row> </j-card>
</j-card> </FullPage>
</page-container> </page-container>
</template> </template>
@ -504,7 +546,7 @@ const onActionTypeChange = (_index: number) => {
properties: undefined, properties: undefined,
functionId: undefined, functionId: undefined,
inputs: [], inputs: [],
value: undefined value: undefined,
}, },
}; };
}; };
@ -720,4 +762,23 @@ watch(
}, },
{ immediate: true, deep: true }, { immediate: true, deep: true },
); );
</script> </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>

View File

@ -5,122 +5,138 @@
target="northbound-dueros" target="northbound-dueros"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="instanceRef" <JProTable
:columns="columns" ref="instanceRef"
:request="query" :columns="columns"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :request="query"
:params="params" :defaultParams="{
> sorts: [{ name: 'createTime', order: 'desc' }],
<template #headerTitle> }"
<j-space> :params="params"
<PermissionButton >
type="primary" <template #headerTitle>
@click="handleAdd" <j-space>
hasPermission="Northbound/DuerOS:add"
>
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
@click="handleView(slotProps.id)"
:actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
>
<template #img>
<img :src="getImage('/cloud/dueros.png')" />
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row style="margin-top: 15px">
<j-col :span="12">
<div class="card-item-content-text">产品</div>
<Ellipsis>
<div>{{ slotProps?.productName }}</div>
</Ellipsis>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<Ellipsis>
<div>{{ slotProps?.applianceType?.text }}</div>
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton <PermissionButton
:disabled="item.disabled" type="primary"
:popConfirm="item.popConfirm" @click="handleAdd"
:tooltip="{ hasPermission="Northbound/DuerOS:add"
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'Northbound/DuerOS:' + item.key"
> >
<AIcon <template #icon
type="DeleteOutlined" ><AIcon type="PlusOutlined"
v-if="item.key === 'delete'" /></template>
/> 新增
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton> </PermissionButton>
</template> </j-space>
</CardBox> </template>
</template> <template #card="slotProps">
<template #state="slotProps"> <CardBox
<BadgeStatus :value="slotProps"
:status="slotProps.state?.value" @click="handleView(slotProps.id)"
:text="slotProps.state?.text" :actions="getActions(slotProps, 'card')"
:statusNames="{ :status="slotProps.state?.value"
enabled: 'processing', :statusText="slotProps.state?.text"
disabled: 'error', :statusNames="{
}" enabled: 'processing',
/> disabled: 'error',
</template> }"
<template #applianceType="slotProps">
{{ slotProps.applianceType.text }}
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #img>
:disabled="i.disabled" <img :src="getImage('/cloud/dueros.png')" />
:popConfirm="i.popConfirm" </template>
:tooltip="{ <template #content>
...i.tooltip, <Ellipsis style="width: calc(100% - 100px)">
}" <span style="font-size: 16px; font-weight: 600">
style="padding: 0 5px" {{ slotProps.name }}
@click="i.onClick" </span>
type="link" </Ellipsis>
:danger="i.key === 'delete'" <j-row style="margin-top: 15px">
:hasPermission="i.key === 'view' ? true : 'Northbound/DuerOS:' + i.key" <j-col :span="12">
<div class="card-item-content-text">
产品
</div>
<Ellipsis>
<div>{{ slotProps?.productName }}</div>
</Ellipsis>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<Ellipsis>
<div>
{{ slotProps?.applianceType?.text }}
</div>
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'Northbound/DuerOS:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
/>
</template>
<template #applianceType="slotProps">
{{ slotProps.applianceType.text }}
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> <PermissionButton
</PermissionButton> :disabled="i.disabled"
</template> :popConfirm="i.popConfirm"
</j-space> :tooltip="{
</template> ...i.tooltip,
</JProTable> }"
style="padding: 0 5px"
@click="i.onClick"
type="link"
:danger="i.key === 'delete'"
:hasPermission="
i.key === 'view'
? true
: 'Northbound/DuerOS:' + i.key
"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
</page-container> </page-container>
</template> </template>

View File

@ -6,61 +6,65 @@
target="category" target="category"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="tableRef" <JProTable
:columns="table.columns" ref="tableRef"
:request="queryTree" :columns="table.columns"
model="TABLE" :request="queryTree"
type="TREE" model="TABLE"
v-model:expandedRowKeys="expandedRowKeys" type="TREE"
:scroll="{ y: 550 }" v-model:expandedRowKeys="expandedRowKeys"
:defaultParams="{ :scroll="{ y: 550 }"
paging: false, :defaultParams="{
sorts: [ paging: false,
{ name: 'sortIndex', order: 'asc' }, sorts: [
{ { name: 'sortIndex', order: 'asc' },
name: 'createTime', {
order: 'desc', name: 'createTime',
}, order: 'desc',
], },
}" ],
:params="params" }"
:loading="tableLoading" :params="params"
> :loading="tableLoading"
<template #headerTitle> >
<PermissionButton <template #headerTitle>
type="primary" <PermissionButton
@click="add" type="primary"
hasPermission="device/Category:add" @click="add"
> hasPermission="device/Category:add"
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #icon><AIcon type="PlusOutlined" /></template>
:disabled="i.disabled" 新增
:popConfirm="i.popConfirm" </PermissionButton>
:hasPermission="'device/Category:' + i.key" </template>
:tooltip="{ <template #action="slotProps">
...i.tooltip, <j-space>
}" <template
@click="i.onClick" v-for="i in getActions(slotProps, 'table')"
type="link" :key="i.key"
style="padding: 0px"
:danger="i.key === 'delete'"
> >
<template #icon><AIcon :type="i.icon" /></template> <PermissionButton
</PermissionButton> :disabled="i.disabled"
</template> :popConfirm="i.popConfirm"
</j-space> :hasPermission="'device/Category:' + i.key"
</template> :tooltip="{
</JProTable> ...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:danger="i.key === 'delete'"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<!-- 新增和编辑弹窗 --> <!-- 新增和编辑弹窗 -->
<ModifyModal <ModifyModal
ref="modifyRef" ref="modifyRef"

View File

@ -1,114 +1,113 @@
<template> <template>
<j-card> <SaveChild
<SaveChild v-if="childVisible"
v-if="childVisible" @close-child-save="closeChildSave"
@close-child-save="closeChildSave" :childData="_current"
:childData="_current" />
<div v-else>
<pro-search
:columns="columns"
target="child-device"
@search="handleSearch"
class="device-child-device-search"
/> />
<div v-else> <!-- <j-divider /> -->
<pro-search <JProTable
:columns="columns" ref="childDeviceRef"
target="child-device" :columns="columns"
@search="handleSearch" :request="query"
class="child-device-search" :bodyStyle="{
/> padding: 0
<JProTable }"
ref="childDeviceRef" :defaultParams="{
:columns="columns" terms: [
:request="query" {
:defaultParams="{ column: 'parentId',
terms: [ value: detail?.id || '',
{ termType: 'eq',
column: 'parentId', },
value: detail?.id || '', ],
termType: 'eq', }"
}, :rowSelection="{
], selectedRowKeys: _selectedRowKeys,
}" onChange: onSelectChange,
:rowSelection="{ }"
selectedRowKeys: _selectedRowKeys, @cancelSelect="cancelSelect"
onChange: onSelectChange, :params="params"
}" :model="'TABLE'"
@cancelSelect="cancelSelect" >
:params="params" <template #rightExtraRender>
:model="'TABLE'" <j-space>
> <PermissionButton
<template #headerTitle> type="primary"
<j-space> v-if="
detail?.accessProvider === 'official-edge-gateway'
"
hasPermission="device/Instance:update"
@click="
_current = {};
childVisible = true;
"
>新增并绑定</PermissionButton
>
<PermissionButton
type="primary"
@click="visible = true"
hasPermission="device/Instance:update"
>
绑定</PermissionButton
>
<PermissionButton
type="primary"
hasPermission="device/Instance:update"
:popConfirm="{
title: '确定解绑吗?',
onConfirm: handleUnBind,
}"
>批量解除</PermissionButton
>
</j-space>
</template>
<template #registryTime="slotProps">
{{
slotProps.registryTime
? moment(slotProps.registryTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}
</template>
<template #state="slotProps">
<j-badge
:text="slotProps.state.text"
:status="statusMap.get(slotProps.state.value)"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton <PermissionButton
type="primary" :disabled="i.disabled"
v-if=" :popConfirm="i.popConfirm"
detail?.accessProvider === :tooltip="{
'official-edge-gateway' ...i.tooltip,
"
hasPermission="device/Instance:update"
@click="
_current = {};
childVisible = true;
"
>新增并绑定</PermissionButton
>
<PermissionButton
type="primary"
@click="visible = true"
hasPermission="device/Instance:update"
>
绑定</PermissionButton
>
<PermissionButton
type="primary"
hasPermission="device/Instance:update"
:popConfirm="{
title: '确定解绑吗?',
onConfirm: handleUnBind,
}" }"
>批量解除</PermissionButton @click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'device/Instance:' + i.key"
> >
</j-space> <template #icon><AIcon :type="i.icon" /></template>
</template> </PermissionButton>
<template #registryTime="slotProps"> </template>
{{ </j-space>
slotProps.registryTime </template>
? moment(slotProps.registryTime).format( </JProTable>
'YYYY-MM-DD HH:mm:ss', <BindChildDevice v-if="visible" @change="closeBindDevice" />
) </div>
: ''
}}
</template>
<template #state="slotProps">
<j-badge
:text="slotProps.state.text"
:status="statusMap.get(slotProps.state.value)"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'device/Instance:' + i.key"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
<BindChildDevice v-if="visible" @change="closeBindDevice" />
</div>
</j-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -147,10 +146,10 @@ const columns = [
dataIndex: 'id', dataIndex: 'id',
key: 'id', key: 'id',
ellipsis: true, ellipsis: true,
search:{ search: {
type:'string', type: 'string',
defaultTermType: 'eq' defaultTermType: 'eq',
} },
}, },
{ {
title: '设备名称', title: '设备名称',
@ -303,12 +302,8 @@ const closeChildSave = () => {
}; };
</script> </script>
<style scoped lang="less"> <style lang="less">
.child-device-search { .device-child-device-search {
border-bottom: 1px solid #f0f0f0; padding: 0px;
}
:deep(._jtable-body_1eyxz_1 ._jtable-body-header_1eyxz_6) {
justify-content: flex-end;
} }
</style> </style>

View File

@ -1,96 +1,84 @@
<template> <template>
<j-card> <div class="diagnose">
<div class="diagnose"> <div
<div class="diagnose-header"
class="diagnose-header" :style="{ background: headerColorMap.get(topState) }"
:style="{ background: headerColorMap.get(topState) }" >
> <div class="diagnose-top">
<div class="diagnose-top"> <div class="diagnose-img">
<div class="diagnose-img"> <div
<div v-if="topState === 'loading'"
v-if="topState === 'loading'" style="width: 100%; height: 100%; position: relative"
style=" >
width: 100%;
height: 100%;
position: relative;
"
>
<img
:src="headerImgMap.get(topState)"
style="
height: 100%;
position: absolute;
z-index: 2;
"
/>
<img
:src="getImage('/diagnose/loading-1.png')"
class="diagnose-loading"
style="height: 100%"
/>
</div>
<img <img
v-else
:src="headerImgMap.get(topState)" :src="headerImgMap.get(topState)"
style="height: 100%; position: absolute; z-index: 2"
/>
<img
:src="getImage('/diagnose/loading-1.png')"
class="diagnose-loading"
style="height: 100%" style="height: 100%"
/> />
</div> </div>
<div class="diagnose-text"> <img
<div class="diagnose-title"> v-else
{{ headerTitleMap.get(topState) }} :src="headerImgMap.get(topState)"
</div> style="height: 100%"
<div class="diagnose-desc">
<template v-if="topState !== 'loading'">{{
headerDescMap.get(topState)
}}</template>
<template v-else>已诊断{{ count }}</template>
</div>
</div>
</div>
<div class="diagnose-progress">
<j-progress
:percent="percent"
:showInfo="false"
size="small"
:strokeColor="progressMap.get(topState)"
style="width: 100%"
/> />
</div> </div>
<div class="diagnose-radio"> <div class="diagnose-text">
<div <div class="diagnose-title">
class="diagnose-radio-item" {{ headerTitleMap.get(topState) }}
:class=" </div>
item.key === 'message' && topState !== 'success' <div class="diagnose-desc">
? 'disabled' <template v-if="topState !== 'loading'">{{
: '' headerDescMap.get(topState)
" }}</template>
v-for="item in tabList" <template v-else>已诊断{{ count }}</template>
:key="item.key"
:style="
activeKey === item.key ? { ...activeStyle } : {}
"
@click="onTabChange(item.key)"
>
{{ item.text }}
</div> </div>
</div> </div>
</div> </div>
<div> <div class="diagnose-progress">
<template v-if="!first"> <j-progress
<Message v-show="activeKey === 'message'" /> :percent="percent"
</template> :showInfo="false"
<template v-if="flag"> size="small"
<Status :strokeColor="progressMap.get(topState)"
v-show="activeKey !== 'message'" style="width: 100%"
:providerType="providerType" />
@countChange="countChange" </div>
@percentChange="percentChange" <div class="diagnose-radio">
@stateChange="stateChange" <div
/> class="diagnose-radio-item"
</template> :class="
item.key === 'message' && topState !== 'success'
? 'disabled'
: ''
"
v-for="item in tabList"
:key="item.key"
:style="activeKey === item.key ? { ...activeStyle } : {}"
@click="onTabChange(item.key)"
>
{{ item.text }}
</div>
</div> </div>
</div> </div>
</j-card> <div>
<template v-if="!first">
<Message v-show="activeKey === 'message'" />
</template>
<template v-if="flag">
<Status
v-show="activeKey !== 'message'"
:providerType="providerType"
@countChange="countChange"
@percentChange="percentChange"
@stateChange="stateChange"
/>
</template>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -160,7 +148,7 @@ const countChange = (num: number) => {
}; };
const init = () => { const init = () => {
flag.value = true flag.value = true;
activeKey.value = 'status'; activeKey.value = 'status';
const provider = instanceStore.current?.accessProvider; const provider = instanceStore.current?.accessProvider;
if (provider === 'fixed-media' || provider === 'gb28181-2016') { if (provider === 'fixed-media' || provider === 'gb28181-2016') {
@ -175,16 +163,16 @@ const init = () => {
providerType.value = 'network'; providerType.value = 'network';
} }
topState.value = 'loading'; topState.value = 'loading';
} };
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(() => {
init() init();
}, 500) }, 500);
}); });
onUnmounted(() => { onUnmounted(() => {
flag.value = false flag.value = false;
}); });
</script> </script>

View File

@ -1,6 +1,6 @@
<template> <template>
<j-spin :spinning="loading" v-if="metadata.properties.length"> <j-spin :spinning="loading" v-if="metadata.properties.length">
<j-card> <j-card :bordered="false" style="padding: 0">
<template #extra> <template #extra>
<j-space> <j-space>
<j-button @click="visible = true">批量映射</j-button> <j-button @click="visible = true">批量映射</j-button>
@ -188,7 +188,7 @@
:edgeId="instanceStore.current.parentId" :edgeId="instanceStore.current.parentId"
/> />
</j-spin> </j-spin>
<j-card v-else> <j-card v-else :bordered="false" style="padding: 0">
<JEmpty description="暂无数据,请配置物模型" style="margin: 10% 0" /> <JEmpty description="暂无数据,请配置物模型" style="margin: 10% 0" />
</j-card> </j-card>
</template> </template>

View File

@ -1,23 +1,21 @@
<template> <template>
<j-card> <j-empty
<j-empty v-if="!metadata || (metadata && !metadata.functions.length)"
v-if="!metadata || (metadata && !metadata.functions.length)" style="margin-top: 50px"
style="margin-top: 50px" >
> <template #description>
<template #description> 请配置对应产品的
请配置对应产品的 <!-- <a @click="emits('onJump', 'Metadata')">物模型属性功能</a> -->
<!-- <a @click="emits('onJump', 'Metadata')">物模型属性功能</a> --> <a @click="onJump">物模型属性功能</a>
<a @click="onJump">物模型属性功能</a>
</template>
</j-empty>
<template v-else>
<j-tabs v-model:activeKey="activeKey">
<j-tab-pane key="Simple" tab="精简模式" />
<j-tab-pane key="Advance" tab="高级模式" />
</j-tabs>
<component :is="tabs[activeKey]" />
</template> </template>
</j-card> </j-empty>
<template v-else>
<j-tabs v-model:activeKey="activeKey">
<j-tab-pane key="Simple" tab="精简模式" />
<j-tab-pane key="Advance" tab="高级模式" />
</j-tabs>
<component :is="tabs[activeKey]" />
</template>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1,90 +1,88 @@
<template> <template>
<j-card> <j-descriptions bordered>
<j-descriptions bordered> <template #title>
<template #title> 设备信息
设备信息 <PermissionButton
<PermissionButton type="link"
type="link" @click="visible = true"
@click="visible = true" hasPermission="device/Instance:update"
hasPermission="device/Instance:update"
>
<template #icon><AIcon type="EditOutlined" /></template>
编辑
</PermissionButton>
</template>
<j-descriptions-item label="设备ID">{{
instanceStore.current?.id
}}</j-descriptions-item>
<j-descriptions-item label="产品名称">{{
instanceStore.current?.productName
}}</j-descriptions-item>
<j-descriptions-item label="产品分类">{{
instanceStore.current?.classifiedName
}}</j-descriptions-item>
<j-descriptions-item label="设备类型">{{
instanceStore.current?.deviceType?.text
}}</j-descriptions-item>
<j-descriptions-item label="固件版本">{{
instanceStore.current?.firmwareInfo?.version
}}</j-descriptions-item>
<j-descriptions-item label="连接协议">{{
instanceStore.current?.transport
}}</j-descriptions-item>
<j-descriptions-item label="消息协议">{{
instanceStore.current?.protocolName
}}</j-descriptions-item>
<j-descriptions-item label="创建时间">{{
instanceStore.current?.createTime
? moment(instanceStore.current?.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</j-descriptions-item>
<j-descriptions-item label="注册时间">{{
instanceStore.current?.registerTime
? moment(instanceStore.current?.registerTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</j-descriptions-item>
<j-descriptions-item label="最后上线时间">{{
instanceStore.current?.onlineTime
? moment(instanceStore.current?.onlineTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</j-descriptions-item>
<j-descriptions-item
label="父设备"
v-if="
instanceStore.current?.deviceType?.value === 'childrenDevice'
"
>{{ instanceStore.current?.parentId }}</j-descriptions-item
> >
<j-descriptions-item label="说明">{{ <template #icon><AIcon type="EditOutlined" /></template>
instanceStore.current?.description 编辑
}}</j-descriptions-item> </PermissionButton>
</j-descriptions> </template>
<Config /> <j-descriptions-item label="设备ID">{{
<Tags instanceStore.current?.id
}}</j-descriptions-item>
<j-descriptions-item label="产品名称">{{
instanceStore.current?.productName
}}</j-descriptions-item>
<j-descriptions-item label="产品分类">{{
instanceStore.current?.classifiedName
}}</j-descriptions-item>
<j-descriptions-item label="设备类型">{{
instanceStore.current?.deviceType?.text
}}</j-descriptions-item>
<j-descriptions-item label="固件版本">{{
instanceStore.current?.firmwareInfo?.version
}}</j-descriptions-item>
<j-descriptions-item label="连接协议">{{
instanceStore.current?.transport
}}</j-descriptions-item>
<j-descriptions-item label="消息协议">{{
instanceStore.current?.protocolName
}}</j-descriptions-item>
<j-descriptions-item label="创建时间">{{
instanceStore.current?.createTime
? moment(instanceStore.current?.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</j-descriptions-item>
<j-descriptions-item label="注册时间">{{
instanceStore.current?.registerTime
? moment(instanceStore.current?.registerTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</j-descriptions-item>
<j-descriptions-item label="最后上线时间">{{
instanceStore.current?.onlineTime
? moment(instanceStore.current?.onlineTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</j-descriptions-item>
<j-descriptions-item
label="父设备"
v-if=" v-if="
instanceStore.current?.tags && instanceStore.current?.deviceType?.value === 'childrenDevice'
instanceStore.current?.tags.length > 0
" "
/> >{{ instanceStore.current?.parentId }}</j-descriptions-item
<Relation >
v-if=" <j-descriptions-item label="说明">{{
instanceStore.current?.relations && instanceStore.current?.description
instanceStore.current?.relations.length > 0 }}</j-descriptions-item>
" </j-descriptions>
/> <Config />
<Save <Tags
v-if="visible" v-if="
:data="instanceStore.current" instanceStore.current?.tags &&
@close="visible = false" instanceStore.current?.tags.length > 0
@save="saveBtn" "
/> />
</j-card> <Relation
v-if="
instanceStore.current?.relations &&
instanceStore.current?.relations.length > 0
"
/>
<Save
v-if="visible"
:data="instanceStore.current"
@close="visible = false"
@save="saveBtn"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,51 +1,47 @@
<template> <template>
<j-card> <pro-search
<pro-search :columns="columns"
:columns="columns" target="device-instance-log"
target="device-instance-log" @search="handleSearch"
@search="handleSearch" type="simple"
type="simple" class="device-log-search"
class="search" />
/> <JProTable
<JProTable ref="instanceRefLog"
ref="instanceRefLog" :columns="columns"
:columns="columns" :request="(e: Record<string, any>) => queryLog(instanceStore.current.id, e)"
:request="(e: Record<string, any>) => queryLog(instanceStore.current.id, e)" model="TABLE"
model="TABLE" :defaultParams="{ sorts: [{ name: 'timestamp', order: 'desc' }] }"
:defaultParams="{ sorts: [{ name: 'timestamp', order: 'desc' }] }" :params="params"
:params="params" :bodyStyle="{ padding: 0 }"
:bodyStyle="{ padding: 0 }" >
> <template #type="slotProps">
<template #type="slotProps"> {{ slotProps?.type?.text }}
{{ slotProps?.type?.text }} </template>
</template> <template #timestamp="slotProps">
<template #timestamp="slotProps"> {{
{{ slotProps.timestamp
slotProps.timestamp ? moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss')
? moment(slotProps.timestamp).format( : ''
'YYYY-MM-DD HH:mm:ss', }}
) </template>
: '' <template #action="slotProps">
}} <j-space>
</template> <template
<template #action="slotProps"> v-for="i in getActions(slotProps, 'table')"
<j-space> :key="i.key"
<template >
v-for="i in getActions(slotProps, 'table')" <j-button
:key="i.key" @click="i.onClick"
type="link"
style="padding: 0px"
> >
<j-button <template #icon><AIcon :type="i.icon" /></template>
@click="i.onClick" </j-button>
type="link" </template>
style="padding: 0px" </j-space>
> </template>
<template #icon><AIcon :type="i.icon" /></template> </JProTable>
</j-button>
</template>
</j-space>
</template>
</JProTable>
</j-card>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -125,11 +121,7 @@ const getActions = (
onClick: () => { onClick: () => {
let content = ''; let content = '';
try { try {
content = JSON.stringify( content = JSON.stringify(JSON.parse(data.content), null, 2);
JSON.parse(data.content),
null,
2,
);
} catch (error) { } catch (error) {
content = data.content; content = data.content;
} }
@ -139,7 +131,7 @@ const getActions = (
content: h(Textarea, { content: h(Textarea, {
bordered: false, bordered: false,
rows: 15, rows: 15,
value: content value: content,
}), }),
}); });
}, },
@ -152,8 +144,8 @@ const handleSearch = (_params: any) => {
}; };
</script> </script>
<style lang="less" scoped> <style lang="less">
.search { .device-log-search {
padding: 0; padding: 0;
} }
</style> </style>

View File

@ -1,187 +1,262 @@
<template> <template>
<j-card> <div>
<div> <div class="top">
<div class="top"> <div class="top-left">
<div class="top-left"> <div>
<div> <AIcon type="ExclamationCircleOutlined" />
<AIcon type="ExclamationCircleOutlined" /> <template v-if="topTitle === 'rest'">
<template v-if="topTitle === 'rest'"> 当前数据解析内容已脱离产品影响
当前数据解析内容已脱离产品影响 <PermissionButton
<PermissionButton type="link" hasPermission="device/Instance:update" @click="rest()"> type="link"
重置 hasPermission="device/Instance:update"
</PermissionButton> @click="rest()"
后将继承产品数据解析内容 >
重置
</PermissionButton>
后将继承产品数据解析内容
</template>
<template v-else>
当前数据解析内容继承自产品,
<PermissionButton
type="link"
hasPermission="device/Instance:update"
@click="readOnly = false"
:style="color"
>
修改
</PermissionButton>
后将脱离产品影响
</template>
</div>
</div>
<div>
脚本语言:
<j-select
:defaultValue="'JavaScript'"
style="width: 200px; margin-left: 5px"
>
<j-select-option value="JavaScript"
>JavaScript(ECMAScript 5)</j-select-option
>
</j-select>
<AIcon
type="ExpandOutlined"
style="margin-left: 20px"
@click="toggle"
/>
</div>
</div>
<div class="edit" ref="el">
<div
v-show="readOnly"
class="edit-only"
@click="
() => {
message.warning({
key: 1,
content: () => '请点击上方修改字样,用以编辑脚本',
style: {
marginTop: '260px',
},
});
}
"
></div>
<j-monaco-editor
language="javascript"
style="height: 100%"
theme="vs"
v-model:modelValue="editorValue"
/>
</div>
<div class="bottom">
<div style="width: 49.5%">
<div class="bottom-title">
<div class="bottom-title-text">模拟输入</div>
<div class="bottom-title-topic">
<template
v-if="instanceStore.current.transport === 'MQTT'"
>
<div style="margin-right: 5px">Topic:</div>
<j-auto-complete
placeholder="请输入Topic"
style="width: 300px"
:options="topicList"
:allowClear="true"
:filterOption="filterOption"
v-model:value="topic"
/>
</template> </template>
<template v-else> <template v-else>
当前数据解析内容继承自产品, <div style="margin-right: 5px">URL:</div>
<PermissionButton type="link" hasPermission="device/Instance:update" @click="readOnly = false" <j-input
:style="color"> placeholder="请输入URL"
修改 v-model:value="url"
</PermissionButton> style="width: 300px"
后将脱离产品影响 ></j-input>
</template> </template>
</div> </div>
</div> </div>
<div> <j-textarea
脚本语言: :rows="5"
<j-select :defaultValue="'JavaScript'" style="width: 200px;margin-left: 5px;"> placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串"
<j-select-option value="JavaScript">JavaScript(ECMAScript 5)</j-select-option> style="margin-top: 10px"
</j-select> v-model:value="simulation"
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" /> />
</div>
</div> </div>
<div class="edit" ref="el"> <div style="width: 49.5%">
<div v-show="readOnly" class="edit-only" @click="() => { <div class="bottom-title">
message.warning({ <div class="bottom-title-text">运行结果</div>
key: 1,
content: () => '请点击上方修改字样,用以编辑脚本',
style: {
marginTop: '260px'
}
})
}"></div>
<j-monaco-editor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
</div>
<div class="bottom">
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">模拟输入</div>
<div class="bottom-title-topic">
<template v-if="instanceStore.current.transport === 'MQTT'">
<div style="margin-right: 5px;">Topic:</div>
<j-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
:allowClear="true" :filterOption="filterOption" v-model:value="topic" />
</template>
<template v-else>
<div style="margin-right: 5px;">URL:</div>
<j-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></j-input>
</template>
</div>
</div>
<j-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
v-model:value="simulation" />
</div>
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">运行结果</div>
</div>
<j-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
</div> </div>
<j-textarea
:autoSize="{ minRows: 5 }"
:style="resStyle"
v-model:value="result"
/>
</div> </div>
</div> </div>
<div style="margin-top: 10px;margin-left: 10px;"> </div>
<PermissionButton type="primary" hasPermission="device/Instance:update" :loading="loading" <div style="margin-top: 10px; margin-left: 10px">
:disabled="isDisabled" @click="debug()" :tooltip="{ <PermissionButton
title: '需输入脚本和模拟数据后再点击', type="primary"
}"> hasPermission="device/Instance:update"
调试 :loading="loading"
</PermissionButton> :disabled="isDisabled"
<PermissionButton hasPermission="device/Instance:update" :loading="loading" :disabled="!isTest" @click="save()" @click="debug()"
:style="{ marginLeft: '10px' }" :tooltip="{ :tooltip="{
title: isTest ? '' : '请先调试', title: '需输入脚本和模拟数据后再点击',
}"> }"
保存 >
</PermissionButton> 调试
</div> </PermissionButton>
</j-card> <PermissionButton
hasPermission="device/Instance:update"
:loading="loading"
:disabled="!isTest"
@click="save()"
:style="{ marginLeft: '10px' }"
:tooltip="{
title: isTest ? '' : '请先调试',
}"
>
保存
</PermissionButton>
</div>
</template> </template>
<script setup lang='ts' name="Parsing"> <script setup lang='ts' name="Parsing">
import PermissionButton from '@/components/PermissionButton/index.vue' import PermissionButton from '@/components/PermissionButton/index.vue';
// import MonacoEditor from '@/components/MonacoEditor/index.vue'; // import MonacoEditor from '@/components/MonacoEditor/index.vue';
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core';
import { useInstanceStore } from '@/store/instance'; import { useInstanceStore } from '@/store/instance';
import { import {
deviceCode, deviceCode,
getProtocal, getProtocal,
testCode, testCode,
saveDeviceCode, saveDeviceCode,
delDeviceCode delDeviceCode,
} from '@/api/device/instance' } from '@/api/device/instance';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import { isBoolean } from 'lodash'; import { isBoolean } from 'lodash';
const defaultValue = const defaultValue =
'//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n'; '//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n';
const el = ref<HTMLElement | null>(null) const el = ref<HTMLElement | null>(null);
const { toggle } = useFullscreen(el) const { toggle } = useFullscreen(el);
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const topTitle = ref<string>('');
const topTitle = ref<string>('') const readOnly = ref<boolean>(true);
const readOnly = ref<boolean>(true) const url = ref<string>('');
const url = ref<string>('') const topic = ref<string>('');
const topic = ref<string>('') const topicList = ref([]);
const topicList = ref([]) const simulation = ref<string>('');
const simulation = ref<string>('') const resultValue = ref<any>({});
const resultValue = ref<any>({}) const loading = ref<boolean>(false);
const loading = ref<boolean>(false) const isTest = ref<boolean>(false);
const isTest = ref<boolean>(false) const editorValue = ref<string>('');
const editorValue = ref<string>('')
const color = computed(() => ({ const color = computed(() => ({
color: readOnly.value ? '#415ed1' : '#a6a6a6' color: readOnly.value ? '#415ed1' : '#a6a6a6',
})) }));
const resStyle = computed(() => (isBoolean(resultValue.value.success) ? { const resStyle = computed(() =>
'margin-top': '10px', isBoolean(resultValue.value.success)
'border-color': resultValue.value.success ? 'green' : 'red' ? {
} : { 'margin-top': '10px',
'margin-top': '10px', 'border-color': resultValue.value.success ? 'green' : 'red',
})) }
const filterOption = (inputValue: any, option: any) => option!.value.indexOf(inputValue) !== -1 : {
'margin-top': '10px',
},
);
const filterOption = (inputValue: any, option: any) =>
option!.value.indexOf(inputValue) !== -1;
const isDisabled = computed(() => simulation.value === '') const isDisabled = computed(() => simulation.value === '');
const result = computed(() => resultValue.value.success ? JSON.stringify(resultValue.value.outputs?.[0]) : resultValue.value.reason) const result = computed(() =>
resultValue.value.success
? JSON.stringify(resultValue.value.outputs?.[0])
: resultValue.value.reason,
);
// //
const rest = async () => { const rest = async () => {
const res = await delDeviceCode(instanceStore.current.productId, instanceStore.current.id) const res = await delDeviceCode(
instanceStore.current.productId,
instanceStore.current.id,
);
if (res.status === 200) { if (res.status === 200) {
getDeviceCode(); getDeviceCode();
message.success('操作成功') message.success('操作成功');
} }
}; };
//topic //topic
const getTopic = async () => { const getTopic = async () => {
const res: any = await getProtocal(instanceStore.current.protocol, instanceStore.current.transport) const res: any = await getProtocal(
instanceStore.current.protocol,
instanceStore.current.transport,
);
if (res.status === 200) { if (res.status === 200) {
const item = res.result.routes?.map((items: any) => ({ const item = res.result.routes?.map((items: any) => ({
value: items.topic, value: items.topic,
})); }));
// setTopicList(item); // setTopicList(item);
topicList.value = item topicList.value = item;
} }
}; };
// //
const getDeviceCode = async () => { const getDeviceCode = async () => {
const res: any = await deviceCode(instanceStore.current.productId, instanceStore.current.id) const res: any = await deviceCode(
instanceStore.current.productId,
instanceStore.current.id,
);
if (res.status === 200) { if (res.status === 200) {
const item = res.result?.configuration?.script ? res.result?.configuration?.script : defaultValue const item = res.result?.configuration?.script
? res.result?.configuration?.script
: defaultValue;
if (res.result?.deviceId) { if (res.result?.deviceId) {
readOnly.value = false readOnly.value = false;
topTitle.value = 'rest' topTitle.value = 'rest';
editorValue.value = item editorValue.value = item;
} else { } else {
readOnly.value = true readOnly.value = true;
topTitle.value = 'edit' topTitle.value = 'edit';
editorValue.value = item editorValue.value = item;
} }
} }
} };
// //
const test = async (dataTest: any) => { const test = async (dataTest: any) => {
loading.value = true loading.value = true;
const res = await testCode(dataTest) const res = await testCode(dataTest);
if (res.status === 200) { if (res.status === 200) {
loading.value = false loading.value = false;
resultValue.value = res?.result resultValue.value = res?.result;
} else { } else {
loading.value = false loading.value = false;
} }
}; };
@ -193,15 +268,18 @@ const save = async () => {
script: editorValue.value, script: editorValue.value,
lang: 'javascript', lang: 'javascript',
}, },
} };
const res = await saveDeviceCode(instanceStore.current.productId, instanceStore.current.id, item) const res = await saveDeviceCode(
instanceStore.current.productId,
instanceStore.current.id,
item,
);
if (res.status === 200) { if (res.status === 200) {
message.success('保存成功'); message.success('保存成功');
getDeviceCode(); getDeviceCode();
} }
}; };
const debug = () => { const debug = () => {
if (instanceStore.current.transport === 'MQTT') { if (instanceStore.current.transport === 'MQTT') {
if (topic.value !== '') { if (topic.value !== '') {
@ -215,8 +293,8 @@ const debug = () => {
}, },
provider: 'jsr223', provider: 'jsr223',
payload: simulation.value, payload: simulation.value,
}) });
isTest.value = true isTest.value = true;
} else { } else {
message.error('请输入topic'); message.error('请输入topic');
} }
@ -233,19 +311,17 @@ const debug = () => {
}, },
payload: simulation.value, payload: simulation.value,
}); });
isTest.value = true isTest.value = true;
} else { } else {
message.error('请输入url'); message.error('请输入url');
} }
} }
} };
onMounted(() => { onMounted(() => {
getDeviceCode() getDeviceCode();
getTopic() getTopic();
}) });
</script> </script>
<style scoped lang='less'> <style scoped lang='less'>

View File

@ -1,37 +1,31 @@
<template> <template>
<j-card> <div class="property-box">
<div class="property-box"> <div class="property-box-left">
<div class="property-box-left"> <j-input-search
<j-input-search v-model:value="value"
v-model:value="value" placeholder="请输入事件名称"
placeholder="请输入事件名称" style="width: 200px; margin-bottom: 10px"
style="width: 200px; margin-bottom: 10px" @search="onSearch"
@search="onSearch" :allowClear="true"
:allowClear="true" />
/> <j-tabs
<j-tabs tab-position="left"
tab-position="left" style="height: 600px"
style="height: 600px" v-if="tabList.length"
v-if="tabList.length" v-model:activeKey="activeKey"
v-model:activeKey="activeKey" :tabBarStyle="{ width: '200px' }"
:tabBarStyle="{ width: '200px' }" @change="tabChange"
@change="tabChange" >
> <j-tab-pane v-for="i in tabList" :key="i.key" :tab="i.tab" />
<j-tab-pane </j-tabs>
v-for="i in tabList" <JEmpty v-else style="margin: 180px 0" />
:key="i.key"
:tab="i.tab"
/>
</j-tabs>
<JEmpty v-else style="margin: 180px 0" />
</div>
<div class="property-box-right">
<Event v-if="type === 'event'" :data="data" />
<Property v-else-if="type === 'property'" :data="properties" />
<JEmpty v-else style="margin: 220px 0" />
</div>
</div> </div>
</j-card> <div class="property-box-right">
<Event v-if="type === 'event'" :data="data" />
<Property v-else-if="type === 'property'" :data="properties" />
<JEmpty v-else style="margin: 220px 0" />
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -51,28 +45,32 @@ const tabList = ref<{ key: string; tab: string; type: 'property' | 'event' }[]>(
], ],
); );
const type = ref<string>('property'); const type = ref<string>('property');
const data = ref<Record<string, any>>({}) const data = ref<Record<string, any>>({});
const value = ref<string>(''); const value = ref<string>('');
const instanceStore = useInstanceStore() const instanceStore = useInstanceStore();
const metadata = JSON.parse(instanceStore.current?.metadata || '{}') const metadata = JSON.parse(instanceStore.current?.metadata || '{}');
const properties = metadata.properties const properties = metadata.properties;
const events = metadata.events const events = metadata.events;
watch(() => events, (newVal) => { watch(
if(events && newVal.length){ () => events,
newVal.map((item: any) => { (newVal) => {
tabList.value.push({ if (events && newVal.length) {
...item, newVal.map((item: any) => {
key: item.id, tabList.value.push({
tab: item.name, ...item,
type: 'event', key: item.id,
}) tab: item.name,
}) type: 'event',
} });
}, { });
deep: true, }
immediate: true },
}) {
deep: true,
immediate: true,
},
);
const onSearch = () => { const onSearch = () => {
const arr = [ const arr = [
@ -87,33 +85,32 @@ const onSearch = () => {
key: item.id, key: item.id,
tab: item.name, tab: item.name,
type: 'event', type: 'event',
} };
}) }),
] ];
if(value.value){ if (value.value) {
const li = arr.filter((i: any) => { const li = arr.filter((i: any) => {
return i?.tab.indexOf(value.value) !== -1; return i?.tab.indexOf(value.value) !== -1;
}) });
tabList.value = _.cloneDeep(li) tabList.value = _.cloneDeep(li);
} else { } else {
tabList.value = _.cloneDeep(arr) tabList.value = _.cloneDeep(arr);
} }
const dt = tabList.value?.[0] const dt = tabList.value?.[0];
if (dt) { if (dt) {
data.value = dt data.value = dt;
type.value = dt.type; type.value = dt.type;
} else { } else {
type.value = '' 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);
if (dt) { if (dt) {
data.value = dt data.value = dt;
type.value = dt.type; type.value = dt.type;
} }
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -1,6 +1,6 @@
<template> <template>
<j-spin :spinning="loading" v-if="metadata.properties.length"> <j-spin :spinning="loading" v-if="metadata.properties.length">
<j-card> <j-card :bordered="false" borderStyle="padding: 0">
<template #extra> <template #extra>
<j-space> <j-space>
<j-button @click="visible = true">批量映射</j-button> <j-button @click="visible = true">批量映射</j-button>
@ -107,7 +107,7 @@
:metaData="modelRef.dataSource" :metaData="modelRef.dataSource"
/> />
</j-spin> </j-spin>
<j-card v-else> <j-card v-else :bordered="false" borderStyle="padding: 0">
<JEmpty description='暂无数据,请配置物模型' style="margin: 10% 0" /> <JEmpty description='暂无数据,请配置物模型' style="margin: 10% 0" />
</j-card> </j-card>
</template> </template>

View File

@ -8,7 +8,9 @@
<template #title> <template #title>
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<j-tooltip :title="instanceStore.current?.name"> <j-tooltip :title="instanceStore.current?.name">
<div class="deviceDetailHead">{{ instanceStore.current?.name }}</div> <div class="deviceDetailHead">
{{ instanceStore.current?.name }}
</div>
</j-tooltip> </j-tooltip>
<j-divider type="vertical" /> <j-divider type="vertical" />
<j-space> <j-space>
@ -95,11 +97,15 @@
style="margin-right: 20px; cursor: pointer" style="margin-right: 20px; cursor: pointer"
/> />
</template> </template>
<component <FullPage>
:is="tabs[instanceStore.tabActiveKey]" <j-card :bordered="false">
v-bind="{ type: 'device' }" <component
@onJump="onTabChange" :is="tabs[instanceStore.tabActiveKey]"
/> v-bind="{ type: 'device' }"
@onJump="onTabChange"
/>
</j-card>
</FullPage>
</page-container> </page-container>
</template> </template>
@ -121,12 +127,12 @@ import { message } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { getWebSocket } from '@/utils/websocket'; import { getWebSocket } from '@/utils/websocket';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import {useRouterParams} from "@/utils/hooks/useParams"; import { useRouterParams } from '@/utils/hooks/useParams';
const menuStory = useMenuStore(); const menuStory = useMenuStore();
const route = useRoute(); const route = useRoute();
const routerParams = useRouterParams() const routerParams = useRouterParams();
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const statusMap = new Map(); const statusMap = new Map();
@ -266,18 +272,18 @@ const getDetail = () => {
// ); // );
const getDetailFn = async () => { const getDetailFn = async () => {
const _id = route.params?.id const _id = route.params?.id;
if (_id) { if (_id) {
await instanceStore.refresh(String(_id)); await instanceStore.refresh(String(_id));
getStatus(String(_id)); getStatus(String(_id));
list.value = [...initList]; list.value = [...initList];
getDetail(); getDetail();
} }
instanceStore.tabActiveKey = routerParams.params.value.tab || 'Info'; instanceStore.tabActiveKey = routerParams.params.value.tab || 'Info';
} };
onMounted(() => { onMounted(() => {
getDetailFn() getDetailFn();
}); });
const onBack = () => { const onBack = () => {

View File

@ -5,37 +5,42 @@
target="device-instance" target="device-instance"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="instanceRef" <JProTable
:columns="columns" ref="instanceRef"
:request="query" :columns="columns"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :request="query"
:rowSelection=" :defaultParams="{
isCheck sorts: [{ name: 'createTime', order: 'desc' }],
? { }"
selectedRowKeys: _selectedRowKeys, :rowSelection="
onChange: onSelectChange, isCheck
} ? {
: false selectedRowKeys: _selectedRowKeys,
" onChange: onSelectChange,
:params="params" }
> : false
<template #headerTitle> "
<j-space> :params="params"
<PermissionButton >
type="primary" <template #headerTitle>
@click="handleAdd" <j-space>
hasPermission="device/Instance:add" <PermissionButton
> type="primary"
<template #icon><AIcon type="PlusOutlined" /></template> @click="handleAdd"
新增 hasPermission="device/Instance:add"
</PermissionButton> >
<BatchDropdown <template #icon
v-model:isCheck="isCheck" ><AIcon type="PlusOutlined"
:actions="batchActions" /></template>
@change="onCheckChange" 新增
/> </PermissionButton>
<!-- <j-dropdown> <BatchDropdown
v-model:isCheck="isCheck"
:actions="batchActions"
@change="onCheckChange"
/>
<!-- <j-dropdown>
<j-button <j-button
>批量操作 <AIcon type="DownOutlined" >批量操作 <AIcon type="DownOutlined"
/></j-button> /></j-button>
@ -141,116 +146,128 @@
</j-menu> </j-menu>
</template> </template>
</j-dropdown> --> </j-dropdown> -->
</j-space> </j-space>
</template> </template>
<template #card="slotProps"> <template #card="slotProps">
<CardBox <CardBox
:value="slotProps" :value="slotProps"
@click="handleClick" @click="handleClick"
:actions="getActions(slotProps, 'card')" :actions="getActions(slotProps, 'card')"
:active="_selectedRowKeys.includes(slotProps.id)" :active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state?.value" :status="slotProps.state?.value"
:statusText="slotProps.state?.text" :statusText="slotProps.state?.text"
:statusNames="{ :statusNames="{
online: 'processing', online: 'processing',
offline: 'error', offline: 'error',
notActive: 'warning', notActive: 'warning',
}" }"
>
<template #img>
<img
:src="getImage('/device/instance/device-card.png')"
/>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row style="margin-top: 20px">
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>{{ slotProps.deviceType?.text }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
产品名称
</div>
<Ellipsis style="width: 100%">
{{ slotProps.productName }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'device/Instance:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
/>
</template>
<template #createTime="slotProps">
<span>{{
slotProps?.createTime ? dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') : ''
}}</span>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #img>
:disabled="i.disabled" <img
:popConfirm="i.popConfirm" :width="88"
:tooltip="{ :height="88"
...i.tooltip, :src="
}" slotProps?.photoUrl ||
@click="i.onClick" getImage('/device/instance/device-card.png')
type="link" "
style="padding: 0 5px" />
:danger="i.key === 'delete'" </template>
:hasPermission=" <template #content>
i.key === 'view' <Ellipsis style="width: calc(100% - 100px)">
? true <span style="font-size: 16px; font-weight: 600">
: 'device/Instance:' + i.key {{ slotProps.name }}
" </span>
</Ellipsis>
<j-row style="margin-top: 20px">
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>{{ slotProps.deviceType?.text }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
产品名称
</div>
<Ellipsis style="width: 100%">
{{ slotProps.productName }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'device/Instance:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
/>
</template>
<template #createTime="slotProps">
<span>{{
slotProps?.createTime
? dayjs(slotProps.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
: ''
}}</span>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> <PermissionButton
</PermissionButton> :disabled="i.disabled"
</template> :popConfirm="i.popConfirm"
</j-space> :tooltip="{
</template> ...i.tooltip,
</JProTable> }"
@click="i.onClick"
type="link"
style="padding: 0 5px"
:danger="i.key === 'delete'"
:hasPermission="
i.key === 'view'
? true
: 'device/Instance:' + i.key
"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
</page-container> </page-container>
<Import <Import
v-if="importVisible" v-if="importVisible"
@ -867,7 +884,7 @@ const handleSearch = (_params: any) => {
) { ) {
return { return {
...item2, ...item2,
column: 'productId$product-info' column: 'productId$product-info',
}; };
} }
return item2; return item2;

View File

@ -1,56 +1,56 @@
<!-- 配置信息 --> <!-- 配置信息 -->
<template> <template>
<j-card style="min-height: 100%"> <j-descriptions bordered>
<j-descriptions bordered> <template #title>
<template #title> <div style="display: flex">
<div style="display: flex"> <h3>配置信息</h3>
<h3>配置信息</h3> <!-- <div style="margin: 0 0px 0 15px; color: #1d39c4">
<!-- <div style="margin: 0 0px 0 15px; color: #1d39c4">
<AIcon type="EditOutlined" @click="editConfig" /> <AIcon type="EditOutlined" @click="editConfig" />
</div> --> </div> -->
<PermissionButton <PermissionButton
type="link" type="link"
@click="editConfig" @click="editConfig"
hasPermission="device/Product:update" hasPermission="device/Product:update"
> >
<template #icon><AIcon type="EditOutlined" /></template> <template #icon><AIcon type="EditOutlined" /></template>
</PermissionButton> </PermissionButton>
</div> </div>
</template> </template>
<j-descriptions-item label="ID"> <j-descriptions-item label="ID">
<Ellipsis>{{ productStore.current.id }}</Ellipsis> <Ellipsis>{{ productStore.current.id }}</Ellipsis>
</j-descriptions-item> </j-descriptions-item>
<j-descriptions-item label="产品分类"> <j-descriptions-item label="产品分类">
<Ellipsis>{{ productStore.current.classifiedName }}</Ellipsis> <Ellipsis>{{ productStore.current.classifiedName }}</Ellipsis>
</j-descriptions-item> </j-descriptions-item>
<j-descriptions-item label="设备类型">{{ <j-descriptions-item label="设备类型">{{
productStore.current.deviceType?.text productStore.current.deviceType?.text
}}</j-descriptions-item> }}</j-descriptions-item>
<j-descriptions-item label="接入方式"> <j-descriptions-item label="接入方式">
<j-button type="link" @click="changeTables">{{ <PermissionButton
type="link"
@click="changeTables"
hasPermission="device/Product:update"
>{{
productStore.current.accessName productStore.current.accessName
? productStore.current.accessName ? productStore.current.accessName
: '配置接入方式' : '配置接入方式'
}}</j-button> }}</PermissionButton
</j-descriptions-item> >
<j-descriptions-item label="创建时间">{{ </j-descriptions-item>
dayjs(productStore.current.createTime).format( <j-descriptions-item label="创建时间">{{
'YYYY-MM-DD HH:mm:ss', dayjs(productStore.current.createTime).format('YYYY-MM-DD HH:mm:ss')
) }}</j-descriptions-item>
}}</j-descriptions-item> <j-descriptions-item label="更新时间">{{
<j-descriptions-item label="更新时间">{{ dayjs(productStore.current.modifyTime).format('YYYY-MM-DD HH:mm:ss')
dayjs(productStore.current.modifyTime).format( }}</j-descriptions-item>
'YYYY-MM-DD HH:mm:ss',
) <j-descriptions-item label="说明" :span="3">
}}</j-descriptions-item> {{ productStore.current.describe }}
</j-descriptions-item>
</j-descriptions>
<j-descriptions-item label="说明" :span="3">
{{ productStore.current.describe }}
</j-descriptions-item>
</j-descriptions>
</j-card>
<!-- 编辑 --> <!-- 编辑 -->
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" /> <Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
</template> </template>
@ -60,6 +60,8 @@ import { useProductStore } from '@/store/product';
import Save from '../../Save/index.vue'; import Save from '../../Save/index.vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useMenuStore } from '@/store/menu';
const menuSotre = useMenuStore();
const productStore = useProductStore(); const productStore = useProductStore();
const route = useRoute(); const route = useRoute();
const saveRef = ref(); const saveRef = ref();

View File

@ -1,138 +1,193 @@
<template> <template>
<j-card> <div>
<div> <div class="top">
<div class="top"> <div>
<div> 脚本语言:
脚本语言: <j-select
<j-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;"> :defaultValue="'JavaScript'"
<j-select-option value="JavaScript">JavaScript(ECMAScript 5)</j-select-option> style="width: 200; margin-left: 5px"
</j-select> >
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" /> <j-select-option value="JavaScript"
</div> >JavaScript(ECMAScript 5)</j-select-option
</div> >
<div class="edit" ref="el"> </j-select>
<j-monaco-editor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" /> <AIcon
</div> type="ExpandOutlined"
<div class="bottom"> style="margin-left: 20px"
<div style="width: 49.5%;"> @click="toggle"
<div class="bottom-title"> />
<div class="bottom-title-text">模拟输入</div>
<div class="bottom-title-topic">
<template v-if="productStore.current.transportProtocol === 'MQTT'">
<div style="margin-right: 5px;">Topic:</div>
<j-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
:allowClear="true" :filterOption="(inputValue: any, option: any) =>
option!.value.indexOf(inputValue) !== -1" v-model:value="topic" />
</template>
<template v-else>
<div style="margin-right: 5px;">URL:</div>
<j-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></j-input>
</template>
</div>
</div>
<j-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
v-model:value="simulation" />
</div>
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">运行结果</div>
</div>
<j-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
</div>
</div> </div>
</div> </div>
<div style="margin-top: 10px;margin-left: 10px;"> <div class="edit" ref="el">
<PermissionButton type="primary" hasPermission="device/Instance:update" :loading="loading" <j-monaco-editor
:disabled="isDisabled" @click="debug()" :tooltip="{ language="javascript"
title: '需输入脚本和模拟数据后再点击', style="height: 100%"
}"> theme="vs"
调试 v-model:modelValue="editorValue"
</PermissionButton> />
<PermissionButton hasPermission="device/Instance:update" :loading="loading" :disabled="!isTest" @click="save()"
:style="{ marginLeft: '10px' }" :tooltip="{
title: isTest ? '' : '请先调试',
}">
保存
</PermissionButton>
</div> </div>
</j-card> <div class="bottom">
<div style="width: 49.5%">
<div class="bottom-title">
<div class="bottom-title-text">模拟输入</div>
<div class="bottom-title-topic">
<template
v-if="
productStore.current.transportProtocol ===
'MQTT'
"
>
<div style="margin-right: 5px">Topic:</div>
<j-auto-complete
placeholder="请输入Topic"
style="width: 300px"
:options="topicList"
:allowClear="true"
:filterOption="(inputValue: any, option: any) =>
option!.value.indexOf(inputValue) !== -1"
v-model:value="topic"
/>
</template>
<template v-else>
<div style="margin-right: 5px">URL:</div>
<j-input
placeholder="请输入URL"
v-model:value="url"
style="width: 300px"
></j-input>
</template>
</div>
</div>
<j-textarea
:rows="5"
placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串"
style="margin-top: 10px"
v-model:value="simulation"
/>
</div>
<div style="width: 49.5%">
<div class="bottom-title">
<div class="bottom-title-text">运行结果</div>
</div>
<j-textarea
:autoSize="{ minRows: 5 }"
:style="resStyle"
v-model:value="result"
/>
</div>
</div>
</div>
<div style="margin-top: 10px; margin-left: 10px">
<PermissionButton
type="primary"
hasPermission="device/Instance:update"
:loading="loading"
:disabled="isDisabled"
@click="debug()"
:tooltip="{
title: '需输入脚本和模拟数据后再点击',
}"
>
调试
</PermissionButton>
<PermissionButton
hasPermission="device/Instance:update"
:loading="loading"
:disabled="!isTest"
@click="save()"
:style="{ marginLeft: '10px' }"
:tooltip="{
title: isTest ? '' : '请先调试',
}"
>
保存
</PermissionButton>
</div>
</template> </template>
<script setup lang='ts' name="DataAnalysis"> <script setup lang='ts' name="DataAnalysis">
import PermissionButton from '@/components/PermissionButton/index.vue' import PermissionButton from '@/components/PermissionButton/index.vue';
// import MonacoEditor from '@/components/MonacoEditor/index.vue'; // import MonacoEditor from '@/components/MonacoEditor/index.vue';
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core';
import { useProductStore } from '@/store/product'; import { useProductStore } from '@/store/product';
import { import {
productCode, productCode,
getProtocal, getProtocal,
testCode, testCode,
saveProductCode, saveProductCode,
} from '@/api/device/instance' } from '@/api/device/instance';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import { isBoolean } from 'lodash'; import { isBoolean } from 'lodash';
const defaultValue = const defaultValue =
'//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n'; '//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n';
const el = ref<HTMLElement | null>(null) const el = ref<HTMLElement | null>(null);
const { toggle } = useFullscreen(el) const { toggle } = useFullscreen(el);
const productStore = useProductStore() const productStore = useProductStore();
const url = ref<string>('');
const topic = ref<string>('');
const topicList = ref([]);
const simulation = ref<string>('');
const resultValue = ref<any>({});
const loading = ref<boolean>(false);
const isTest = ref<boolean>(false);
const editorValue = ref<string>('');
const url = ref<string>('') const resStyle = computed(() =>
const topic = ref<string>('') isBoolean(resultValue.value.success)
const topicList = ref([]) ? {
const simulation = ref<string>('') 'margin-top': '10px',
const resultValue = ref<any>({}) 'border-color': resultValue.value.success ? 'green' : 'red',
const loading = ref<boolean>(false) }
const isTest = ref<boolean>(false) : {
const editorValue = ref<string>('') 'margin-top': '10px',
},
);
const resStyle = computed(() => (isBoolean(resultValue.value.success) ? { const isDisabled = computed(() => simulation.value === '');
'margin-top': '10px',
'border-color': resultValue.value.success ? 'green' : 'red'
} : {
'margin-top': '10px',
}))
const isDisabled = computed(() => simulation.value === '')
const result = computed(() => resultValue.value.success ? JSON.stringify(resultValue.value.outputs?.[0]) : resultValue.value.reason)
const result = computed(() =>
resultValue.value.success
? JSON.stringify(resultValue.value.outputs?.[0])
: resultValue.value.reason,
);
//topic //topic
const getTopic = async () => { const getTopic = async () => {
const res: any = await getProtocal(productStore.current.messageProtocol, productStore.current.transportProtocol) const res: any = await getProtocal(
productStore.current.messageProtocol,
productStore.current.transportProtocol,
);
if (res.status === 200) { if (res.status === 200) {
const item = res.result.routes?.map((items: any) => ({ const item = res.result.routes?.map((items: any) => ({
value: items.topic, value: items.topic,
})); }));
topicList.value = item topicList.value = item;
} }
}; };
// //
const getProductCode = async () => { const getProductCode = async () => {
const res: any = await productCode(productStore.current.id) const res: any = await productCode(productStore.current.id);
if (res.status === 200) { if (res.status === 200) {
if(res.result){ if (res.result) {
editorValue.value = res.result?.configuration?.script editorValue.value = res.result?.configuration?.script;
}else{ } else {
editorValue.value = defaultValue editorValue.value = defaultValue;
} }
} }
} };
// //
const test = async (dataTest: any) => { const test = async (dataTest: any) => {
loading.value = true loading.value = true;
const res = await testCode(dataTest) const res = await testCode(dataTest);
if (res.status === 200) { if (res.status === 200) {
loading.value = false loading.value = false;
resultValue.value = res?.result resultValue.value = res?.result;
} else { } else {
loading.value = false loading.value = false;
} }
}; };
@ -144,15 +199,14 @@ const save = async () => {
script: editorValue.value, script: editorValue.value,
lang: 'javascript', lang: 'javascript',
}, },
} };
const res = await saveProductCode(productStore.current.id, item) const res = await saveProductCode(productStore.current.id, item);
if (res.status === 200) { if (res.status === 200) {
message.success('保存成功'); message.success('保存成功');
getProductCode(); getProductCode();
} }
}; };
const debug = () => { const debug = () => {
if (productStore.current.transportProtocol === 'MQTT') { if (productStore.current.transportProtocol === 'MQTT') {
if (topic.value !== '') { if (topic.value !== '') {
@ -166,8 +220,8 @@ const debug = () => {
}, },
provider: 'jsr223', provider: 'jsr223',
payload: simulation.value, payload: simulation.value,
}) });
isTest.value = true isTest.value = true;
} else { } else {
message.error('请输入topic'); message.error('请输入topic');
} }
@ -184,19 +238,17 @@ const debug = () => {
}, },
payload: simulation.value, payload: simulation.value,
}); });
isTest.value = true isTest.value = true;
} else { } else {
message.error('请输入url'); message.error('请输入url');
} }
} }
} };
onMounted(() => { onMounted(() => {
getProductCode() getProductCode();
getTopic() getTopic();
}) });
</script> </script>
<style scoped lang='less'> <style scoped lang='less'>

View File

@ -1,36 +1,26 @@
<!-- 设备接入 --> <!-- 设备接入 -->
<template> <template>
<j-card style="min-height: 100%"> <div v-if="productStore.current.accessId === undefined || null">
<div v-if="productStore.current.accessId === undefined || null"> <j-empty :image="simpleImage">
<j-empty :image="simpleImage"> <template #description>
<template #description> <span
<span v-if="
v-if=" permissionStore.hasPermission('device/Product:update')
permissionStore.hasPermission( "
'device/Product:update', >
) && 请先<j-button type="link" @click="showModal">选择</j-button
permissionStore.hasPermission( >设备接入网关用以提供设备接入能力
'link/AccessConfig:add', </span>
) && <span v-else>请联系管理员配置产品接入方式</span>
permissionStore.hasPermission( </template>
'link/AccessConfig:update', </j-empty>
) </div>
" <div v-else>
> <j-row :gutter="24">
请先<j-button type="link" @click="showModal" <j-col :span="12">
>选择</j-button <Title data="接入方式">
>设备接入网关用以提供设备接入能力 <template #extra>
</span> <!-- <j-tooltip
<span v-else>请联系管理员配置产品接入方式</span>
</template>
</j-empty>
</div>
<div v-else>
<j-row :gutter="24">
<j-col :span="12">
<Title data="接入方式">
<template #extra>
<!-- <j-tooltip
:title=" :title="
productStore.current?.count && productStore.current?.count &&
productStore.current?.count > 0 productStore.current?.count > 0
@ -51,269 +41,229 @@
>更换</j-button >更换</j-button
> >
</j-tooltip> --> </j-tooltip> -->
<PermissionButton <PermissionButton
style="margin: 0 0 0 20px" style="margin: 0 0 0 20px"
type="primary" type="primary"
size="small" size="small"
:tooltip="{ :tooltip="{
title: title:
productStore.current?.count && productStore.current?.count &&
productStore.current?.count > 0 productStore.current?.count > 0
? '产品下有设备实例时不能更换接入方式' ? '产品下有设备实例时不能更换接入方式'
: !(permissionStore.hasPermission( : '',
'device/Product:update', }"
) && :disabled="
permissionStore.hasPermission( productStore.current?.count &&
'link/AccessConfig:add', productStore.current?.count > 0
) && "
permissionStore.hasPermission( ghost
'link/AccessConfig:update', @click="showDevice"
)) hasPermission="device/Product:update"
? '暂无权限,请联系管理员' >
: '', 更换
}" </PermissionButton>
:disabled=" </template>
(productStore.current?.count && </Title>
productStore.current?.count > 0) || <div>
!(
permissionStore.hasPermission(
'device/Product:update',
) &&
permissionStore.hasPermission(
'link/AccessConfig:add',
) &&
permissionStore.hasPermission(
'link/AccessConfig:update',
)
)
"
ghost
@click="showDevice"
>
更换
</PermissionButton>
</template>
</Title>
<div> <div>
<div> {{ access?.name }}
{{ access?.name }} </div>
<div>
{{
access?.description ||
dataSource.find(
(item) => item?.id === access?.provider,
)?.description
}}
</div>
</div>
<div class="item-style">
<Title data="消息协议"></Title>
<div>
{{ access?.protocolDetail?.name }}
</div>
<!-- 显示md文件内容 -->
<div v-if="config?.document" v-html="markdownToHtml"></div>
</div>
<div class="item-style">
<Title data="连接信息"></Title>
<div v-if="access?.channelInfo?.addresses.length > 0">
<div
v-for="item in access?.channelInfo?.addresses"
:key="item.address"
>
<j-badge
:color="item.health === -1 ? 'red' : 'green'"
:text="item.address"
>
</j-badge>
</div> </div>
<div> </div>
<div v-else>{{ '暂无连接信息' }}</div>
</div>
<Title
v-if="metadata?.name"
:data="metadata?.name"
class="config"
>
<template #extra>
<j-tooltip title="此配置来自于产品接入方式所选择的协议">
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
</Title>
<j-form ref="formRef" :model="formData.data" layout="vertical">
<j-form-item
:name="item.property"
v-for="item in metadata.properties"
:key="item"
:label="item.name"
:rules="[
{
required: !!item?.type?.expands?.required,
message: `${
item.type.type === 'enum'
? '请选择'
: '请输入'
}${item.name}`,
},
]"
>
<j-input
placeholder="请输入"
v-if="item.type.type === 'string'"
v-model:value="formData.data[item.property]"
></j-input>
<j-input-password
placeholder="请输入"
v-if="item.type.type === 'password'"
v-model:value="formData.data[item.property]"
></j-input-password>
<j-select
placeholder="请选择"
v-if="item.type.type === 'enum'"
v-model:value="formData.data[item.name]"
>
<j-select-option
v-for="el in item?.type?.type === 'enum' &&
item?.type?.elements
? item?.type?.elements
: []"
:key="el"
:value="el.value"
>
{{ el.text }}
</j-select-option>
</j-select>
</j-form-item>
</j-form>
<Title data="存储策略">
<template #extra>
<j-tooltip
title="若修改存储策略,需要手动做数据迁移,平台只能搜索最新存储策略中的数据"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
</Title>
<j-form layout="vertical">
<j-form-item>
<j-select ref="select" v-model:value="form.storePolicy">
<j-select-option
v-for="(item, index) in storageList"
:key="index"
:value="item.id"
>{{ item.name }}</j-select-option
>
</j-select>
</j-form-item>
</j-form>
<PermissionButton
type="primary"
@click="submitDevice"
hasPermission="device/Instance:update"
>保存</PermissionButton
>
</j-col>
<j-col
:span="12"
v-if="config?.routes && config?.routes?.length > 0"
>
<div class="info">
<div>
<div style="font-weight: 600; margin: 0 0 10 px 0">
{{ {{
access?.description || access?.provider === 'mqtt-server-gateway' ||
dataSource.find( access?.provider === 'mqtt-client-gateway'
(item) => item?.id === access?.provider, ? 'topic'
)?.description : 'URL信息'
}} }}
</div> </div>
</div> <j-table
<div class="item-style"> :columns="
<Title data="消息协议"></Title> config.id === 'MQTT' ? columnsMQTT : columnsHTTP
<div> "
{{ access?.protocolDetail?.name }} :data-source="config?.routes"
</div> :pagination="false"
<!-- 显示md文件内容 --> :scroll="{ y: 500 }"
<div
v-if="config?.document"
v-html="markdownToHtml"
></div>
</div>
<div class="item-style">
<Title data="连接信息"></Title>
<div v-if="access?.channelInfo?.addresses.length > 0">
<div
v-for="item in access?.channelInfo?.addresses"
:key="item.address"
>
<j-badge
:color="
item.health === -1 ? 'red' : 'green'
"
:text="item.address"
>
</j-badge>
</div>
</div>
<div v-else>{{ '暂无连接信息' }}</div>
</div>
<Title
v-if="metadata?.name"
:data="metadata?.name"
class="config"
>
<template #extra>
<j-tooltip
title="此配置来自于产品接入方式所选择的协议"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
</Title>
<j-form
ref="formRef"
:model="formData.data"
layout="vertical"
>
<j-form-item
:name="item.property"
v-for="item in metadata.properties"
:key="item"
:label="item.name"
:rules="[
{
required: !!item?.type?.expands?.required,
message: `${
item.type.type === 'enum'
? '请选择'
: '请输入'
}${item.name}`,
},
]"
> >
<j-input <template #bodyCell="{ text, column, record }">
placeholder="请输入" <template v-if="column.key === 'topic'">
v-if="item.type.type === 'string'" <j-tooltip
v-model:value="formData.data[item.property]" placement="topLeft"
></j-input> :title="text"
<j-input-password
placeholder="请输入"
v-if="item.type.type === 'password'"
v-model:value="formData.data[item.property]"
></j-input-password>
<j-select
placeholder="请选择"
v-if="item.type.type === 'enum'"
v-model:value="formData.data[item.name]"
>
<j-select-option
v-for="el in item?.type?.type === 'enum' &&
item?.type?.elements
? item?.type?.elements
: []"
:key="el"
:value="el.value"
>
{{ el.text }}
</j-select-option>
</j-select>
</j-form-item>
</j-form>
<Title data="存储策略">
<template #extra>
<j-tooltip
title="若修改存储策略,需要手动做数据迁移,平台只能搜索最新存储策略中的数据"
>
<AIcon
type="QuestionCircleOutlined"
style="margin-left: 2px"
/>
</j-tooltip>
</template>
</Title>
<j-form layout="vertical">
<j-form-item>
<j-select
ref="select"
v-model:value="form.storePolicy"
>
<j-select-option
v-for="(item, index) in storageList"
:key="index"
:value="item.id"
>{{ item.name }}</j-select-option
>
</j-select>
</j-form-item>
</j-form>
<PermissionButton
type="primary"
@click="submitDevice"
hasPermission="device/Instance:update"
>保存</PermissionButton
>
</j-col>
<j-col
:span="12"
v-if="config?.routes && config?.routes?.length > 0"
>
<div class="info">
<div>
<div style="font-weight: 600; margin: 0 0 10 px 0">
{{
access?.provider ===
'mqtt-server-gateway' ||
access?.provider === 'mqtt-client-gateway'
? 'topic'
: 'URL信息'
}}
</div>
<j-table
:columns="
config.id === 'MQTT'
? columnsMQTT
: columnsHTTP
"
:data-source="config?.routes"
:pagination="false"
:scroll="{ y: 500 }"
>
<template #bodyCell="{ text, column, record }">
<template v-if="column.key === 'topic'">
<j-tooltip
placement="topLeft"
:title="text"
>
<div class="ellipsis-style">
{{ text }}
</div>
</j-tooltip>
</template>
<template v-if="column.key === 'stream'">
<div>{{ getStream(record) }}</div>
</template>
<template
v-if="column.key === 'description'"
> >
<j-tooltip <div class="ellipsis-style">
placement="topLeft" {{ text }}
:title="text" </div>
> </j-tooltip>
<div class="ellipsis-style">
{{ text }}
</div>
</j-tooltip>
</template>
<template v-if="column.key === 'address'">
<j-tooltip
placement="topLeft"
:title="text"
>
<div class="ellipsis-style">
{{ text }}
</div>
</j-tooltip>
</template>
<template v-if="column.key === 'example'">
<j-tooltip
placement="topLeft"
:title="text"
>
<div class="ellipsis-style">
{{ text }}
</div>
</j-tooltip>
</template>
</template> </template>
</j-table> <template v-if="column.key === 'stream'">
</div> <div>{{ getStream(record) }}</div>
</template>
<template v-if="column.key === 'description'">
<j-tooltip
placement="topLeft"
:title="text"
>
<div class="ellipsis-style">
{{ text }}
</div>
</j-tooltip>
</template>
<template v-if="column.key === 'address'">
<j-tooltip
placement="topLeft"
:title="text"
>
<div class="ellipsis-style">
{{ text }}
</div>
</j-tooltip>
</template>
<template v-if="column.key === 'example'">
<j-tooltip
placement="topLeft"
:title="text"
>
<div class="ellipsis-style">
{{ text }}
</div>
</j-tooltip>
</template>
</template>
</j-table>
</div> </div>
</j-col> </div>
</j-row> </j-col>
</div> </j-row>
</j-card> </div>
<!-- 选择设备 --> <!-- 选择设备 -->
<j-modal <j-modal
title="设备接入配置" title="设备接入配置"
@ -349,8 +299,11 @@
:gridColumns="[2]" :gridColumns="[2]"
> >
<template #headerTitle> <template #headerTitle>
<j-button type="primary" @click="add" <PermissionButton
><plus-outlined />新增</j-button type="primary"
@click="add"
hasPermission="link/AccessConfig:add"
>新增</PermissionButton
> >
</template> </template>
<template #deviceType="slotProps"> <template #deviceType="slotProps">
@ -1010,7 +963,6 @@ const submitDevice = async () => {
const result: any = {}; const result: any = {};
flatObj(values, result); flatObj(values, result);
const { storePolicy, ...extra } = result; const { storePolicy, ...extra } = result;
console.log({ ...extra });
const id = productStore.current?.id; const id = productStore.current?.id;
const resp = await modify(id || '', { const resp = await modify(id || '', {
id: id, id: id,
@ -1046,7 +998,6 @@ const add = () => {
if (url) { if (url) {
const tab: any = window.open(`${origin}/#${url}?view=false`); const tab: any = window.open(`${origin}/#${url}?view=false`);
tab.onTabSaveSuccess = (value: any) => { tab.onTabSaveSuccess = (value: any) => {
console.log(value);
if (value.status === 200) { if (value.status === 200) {
tableRef.value.reload(); tableRef.value.reload();
handleClick(value.result); handleClick(value.result);

View File

@ -90,11 +90,19 @@
>应用配置</PermissionButton >应用配置</PermissionButton
> >
</template> </template>
<component <FullPage>
:is="tabs[productStore.tabActiveKey]" <j-card :bordered="false">
:class="productStore.tabActiveKey === 'Metadata' ? 'metedata' : ''" <component
v-bind="{ type: 'product' }" :is="tabs[productStore.tabActiveKey]"
/> :class="
productStore.tabActiveKey === 'Metadata'
? 'metedata'
: ''
"
v-bind="{ type: 'product' }"
/>
</j-card>
</FullPage>
</page-container> </page-container>
</template> </template>

View File

@ -5,152 +5,167 @@
target="product-manage" target="product-manage"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
:columns="columns" <JProTable
:request="queryProductList" :columns="columns"
ref="tableRef" :request="queryProductList"
:defaultParams="{ ref="tableRef"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
}" sorts: [{ name: 'createTime', order: 'desc' }],
:params="params" }"
> :params="params"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
type="primary" <PermissionButton
@click="add" type="primary"
hasPermission="device/Product:add" @click="add"
> hasPermission="device/Product:add"
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
<j-upload
name="file"
accept=".json"
:showUploadList="false"
:before-upload="beforeUpload"
>
<PermissionButton hasPermission="device/Product:import"
>导入</PermissionButton
> >
</j-upload> <template #icon
</j-space> ><AIcon type="PlusOutlined"
</template> /></template>
<template #deviceType="slotProps"> 新增
<div>{{ slotProps.deviceType.text }}</div> </PermissionButton>
</template> <j-upload
<template #card="slotProps"> name="file"
<CardBox accept=".json"
:value="slotProps" :showUploadList="false"
:actions="getActions(slotProps, 'card')" :before-upload="beforeUpload"
v-bind="slotProps" >
:active="_selectedRowKeys.includes(slotProps.id)" <PermissionButton
:status="slotProps.state" hasPermission="device/Product:import"
@click="handleView(slotProps.id)" >导入</PermissionButton
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
:statusNames="{
1: 'processing',
0: 'error',
}"
>
<template #img>
<slot name="img">
<img
:src="
slotProps.photoUrl ||
getImage('/device-product.png')
"
class="productImg"
/>
</slot>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)"
><span
style="font-weight: 600; font-size: 16px"
> >
{{ slotProps.name }} </j-upload>
</span></Ellipsis </j-space>
> </template>
<j-row> <template #deviceType="slotProps">
<j-col :span="12"> <div>{{ slotProps.deviceType.text }}</div>
<div class="card-item-content-text"> </template>
设备类型 <template #card="slotProps">
</div> <CardBox
<div>{{ slotProps?.deviceType?.text }}</div> :value="slotProps"
</j-col> :actions="getActions(slotProps, 'card')"
<j-col :span="12"> v-bind="slotProps"
<div class="card-item-content-text"> :active="_selectedRowKeys.includes(slotProps.id)"
接入方式 :status="slotProps.state"
</div> @click="handleView(slotProps.id)"
<Ellipsis :statusText="slotProps.state === 1 ? '正常' : '禁用'"
><div> :statusNames="{
{{ 1: 'processing',
slotProps?.accessName 0: 'error',
? slotProps?.accessName }"
: '未接入'
}}
</div></Ellipsis
>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="item.key ==='view' ? true : 'device/Product:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:text="slotProps.state === 1 ? '正常' : '禁用'"
:status="slotProps.state"
:statusNames="{
1: 'processing',
0: 'error',
}"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #img>
:disabled="i.disabled" <slot name="img">
:popConfirm="i.popConfirm" <img
:hasPermission="i.key === 'view' ? true : 'device/Product:' + i.key" :src="
:tooltip="{ slotProps.photoUrl ||
...i.tooltip, getImage('/device-product.png')
}" "
@click="i.onClick" class="productImg"
type="link" />
style="padding: 0px" </slot>
:danger="i.key === 'delete'" </template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)"
><span
style="font-weight: 600; font-size: 16px"
>
{{ slotProps.name }}
</span></Ellipsis
>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>{{ slotProps?.deviceType?.text }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
接入方式
</div>
<Ellipsis
><div>
{{
slotProps?.accessName
? slotProps?.accessName
: '未接入'
}}
</div></Ellipsis
>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="
item.key === 'view'
? true
: 'device/Product:' + item.key
"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:text="slotProps.state === 1 ? '正常' : '禁用'"
:status="slotProps.state"
:statusNames="{
1: 'processing',
0: 'error',
}"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> <PermissionButton
</PermissionButton> :disabled="i.disabled"
</template> :popConfirm="i.popConfirm"
</j-space> :hasPermission="
</template> i.key === 'view'
</JProTable> ? true
: 'device/Product:' + i.key
"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:danger="i.key === 'delete'"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<!-- 新增编辑 --> <!-- 新增编辑 -->
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" /> <Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
</page-container> </page-container>
@ -181,8 +196,8 @@ import { typeOptions } from '@/components/Search/util';
import Save from './Save/index.vue'; import Save from './Save/index.vue';
import { useMenuStore } from 'store/menu'; import { useMenuStore } from 'store/menu';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import {useRouterParams} from "@/utils/hooks/useParams"; import { useRouterParams } from '@/utils/hooks/useParams';
import { accessConfigTypeFilter } from '@/utils/setting' import { accessConfigTypeFilter } from '@/utils/setting';
/** /**
* 表格数据 * 表格数据
*/ */
@ -196,21 +211,21 @@ const columns = [
dataIndex: 'id', dataIndex: 'id',
key: 'id', key: 'id',
scopedSlots: true, scopedSlots: true,
width:200, width: 200,
ellipsis: true, ellipsis: true,
}, },
{ {
title: '产品名称', title: '产品名称',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width:220, width: 220,
ellipsis: true, ellipsis: true,
}, },
{ {
title: '接入方式', title: '接入方式',
dataIndex: 'accessName', dataIndex: 'accessName',
key: 'accessName', key: 'accessName',
width:220, width: 220,
ellipsis: true, ellipsis: true,
}, },
{ {
@ -219,7 +234,7 @@ const columns = [
key: 'deviceType', key: 'deviceType',
scopedSlots: true, scopedSlots: true,
ellipsis: true, ellipsis: true,
width:120, width: 120,
}, },
{ {
title: '状态', title: '状态',
@ -227,7 +242,7 @@ const columns = [
key: 'state', key: 'state',
scopedSlots: true, scopedSlots: true,
ellipsis: true, ellipsis: true,
width:90, width: 90,
}, },
{ {
title: '说明', title: '说明',
@ -434,7 +449,7 @@ const query = reactive({
key: 'id', key: 'id',
search: { search: {
type: 'string', type: 'string',
defaultTermType: 'eq' defaultTermType: 'eq',
}, },
}, },
{ {
@ -446,8 +461,8 @@ const query = reactive({
options: () => { options: () => {
return new Promise((resolve) => { return new Promise((resolve) => {
getProviders().then((resp: any) => { getProviders().then((resp: any) => {
const data = resp.result || [] const data = resp.result || [];
resolve(accessConfigTypeFilter(data)) resolve(accessConfigTypeFilter(data));
}); });
}); });
}, },
@ -592,9 +607,9 @@ const saveRef = ref();
const handleSearch = (e: any) => { const handleSearch = (e: any) => {
params.value = e; params.value = e;
}; };
const routerParams = useRouterParams() const routerParams = useRouterParams();
onMounted(() => { onMounted(() => {
if(routerParams.params?.value.save){ if (routerParams.params?.value.save) {
add(); add();
} }
}); });

View File

@ -1,5 +1,4 @@
<template> <template>
<j-card>
<div class='device-detail-metadata' style="position: relative;"> <div class='device-detail-metadata' style="position: relative;">
<div class="tips"> <div class="tips">
<j-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device' <j-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'
@ -44,7 +43,6 @@
<Import v-if="visible" v-model:visible="visible" :type="type" @close="visible = false" /> <Import v-if="visible" v-model:visible="visible" :type="type" @close="visible = false" />
<Cat v-model:visible="cat" @close="cat = false" :type="type" /> <Cat v-model:visible="cat" @close="cat = false" :type="type" />
</div> </div>
</j-card>
</template> </template>
<script setup lang="ts" name="Metadata"> <script setup lang="ts" name="Metadata">
import PermissionButton from '@/components/PermissionButton/index.vue' import PermissionButton from '@/components/PermissionButton/index.vue'

View File

@ -5,147 +5,166 @@
target="edge-device" target="edge-device"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="edgeDeviceRef" <JProTable
:columns="columns" ref="edgeDeviceRef"
:request="query" :columns="columns"
:defaultParams="defaultParams" :request="query"
:params="params" :defaultParams="defaultParams"
:gridColumn="3" :params="params"
> :gridColumn="3"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
type="primary"
@click="handleAdd"
hasPermission="edge/Device:add"
>
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
<PermissionButton
@click="importVisible = true"
hasPermission="edge/Device:import"
>
<template #icon
><AIcon type="ImportOutlined"
/></template>
导入
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
>
<template #img>
<img
:src="getImage('/device/instance/device-card.png')"
/>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span
style="font-size: 16px; font-weight: 600"
@click.stop="handleView(slotProps.id)"
>
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row style="margin-top: 20px">
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>{{ slotProps.deviceType?.text }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
产品名称
</div>
<Ellipsis style="width: 100%">
{{ slotProps.productName }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton <PermissionButton
:disabled="item.disabled" type="primary"
:popConfirm="item.popConfirm" @click="handleAdd"
:tooltip="{ hasPermission="edge/Device:add"
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'edge/Device:' + item.key"
> >
<AIcon <template #icon
type="DeleteOutlined" ><AIcon type="PlusOutlined"
v-if="item.key === 'delete'" /></template>
新增
</PermissionButton>
<PermissionButton
@click="importVisible = true"
hasPermission="edge/Device:import"
>
<template #icon
><AIcon type="ImportOutlined"
/></template>
导入
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
>
<template #img>
<img
:src="
getImage('/device/instance/device-card.png')
"
/> />
<template v-else> </template>
<AIcon :type="item.icon" /> <template #content>
<span>{{ item?.text }}</span> <Ellipsis style="width: calc(100% - 100px)">
</template> <span
</PermissionButton> style="font-size: 16px; font-weight: 600"
</template> @click.stop="handleView(slotProps.id)"
</CardBox> >
</template> {{ slotProps.name }}
<template #state="slotProps"> </span>
<BadgeStatus </Ellipsis>
:status="slotProps.state?.value" <j-row style="margin-top: 20px">
:text="slotProps.state?.text" <j-col :span="12">
:statusNames="{ <div class="card-item-content-text">
online: 'processing', 设备类型
offline: 'error', </div>
notActive: 'warning', <div>{{ slotProps.deviceType?.text }}</div>
}" </j-col>
/> <j-col :span="12">
</template> <div class="card-item-content-text">
<template #registryTime="slotProps"> 产品名称
<span>{{ </div>
dayjs(slotProps.registryTime).format('YYYY-MM-DD HH:mm:ss') <Ellipsis style="width: 100%">
}}</span> {{ slotProps.productName }}
</template> </Ellipsis>
<template #action="slotProps"> </j-col>
<j-space> </j-row>
<template </template>
v-for="i in getActions(slotProps, 'table')" <template #actions="item">
:key="i.key" <PermissionButton
> :disabled="item.disabled"
<PermissionButton :popConfirm="item.popConfirm"
:disabled="i.disabled" :tooltip="{
:popConfirm="i.popConfirm" ...item.tooltip,
:tooltip="{ }"
...i.tooltip, @click="item.onClick"
}" :hasPermission="'edge/Device:' + item.key"
@click="i.onClick" >
type="link" <AIcon
style="padding: 0 5px" type="DeleteOutlined"
:danger="i.key === 'delete'" v-if="item.key === 'delete'"
:hasPermission="i.key === 'view' ? true : 'edge/Device:' + i.key" />
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
/>
</template>
<template #registryTime="slotProps">
<span>{{
dayjs(slotProps.registryTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}</span>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> <PermissionButton
</PermissionButton> :disabled="i.disabled"
</template> :popConfirm="i.popConfirm"
</j-space> :tooltip="{
</template> ...i.tooltip,
</JProTable> }"
@click="i.onClick"
type="link"
style="padding: 0 5px"
:danger="i.key === 'delete'"
:hasPermission="
i.key === 'view'
? true
: 'edge/Device:' + i.key
"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<Save <Save
v-if="visible" v-if="visible"
:data="current" :data="current"
@close="visible = false" @close="visible = false"
@save="saveBtn" @save="saveBtn"
/> />
<Import @save="onRefresh" @close="importVisible = false" v-if="importVisible" type="official-edge-gateway" /> <Import
@save="onRefresh"
@close="importVisible = false"
v-if="importVisible"
type="official-edge-gateway"
/>
</page-container> </page-container>
</template> </template>
@ -192,18 +211,18 @@ const visible = ref<boolean>(false);
const current = ref<Record<string, any>>({}); const current = ref<Record<string, any>>({});
const transformData = (arr: any[]): any[] => { const transformData = (arr: any[]): any[] => {
if(Array.isArray(arr) && arr.length){ if (Array.isArray(arr) && arr.length) {
return (arr || []).map((item: any) => { return (arr || []).map((item: any) => {
return { return {
...item, ...item,
id: `classifiedId is ${item.id}`, id: `classifiedId is ${item.id}`,
children: transformData(item.children) children: transformData(item.children),
} };
}) });
} else { } else {
return [] return [];
} }
} };
const columns = [ const columns = [
{ {
@ -471,9 +490,9 @@ const saveBtn = () => {
}; };
const onRefresh = () => { const onRefresh = () => {
importVisible.value = false importVisible.value = false;
edgeDeviceRef.value?.reload(); edgeDeviceRef.value?.reload();
} };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -5,131 +5,141 @@
target="edge-resource" target="edge-resource"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="edgeResourceRef" <JProTable
:columns="columns" ref="edgeResourceRef"
:request="query" :columns="columns"
:defaultParams="defaultParams" :request="query"
:params="params" :defaultParams="defaultParams"
> :params="params"
<template #card="slotProps"> >
<CardBox <template #card="slotProps">
:value="slotProps" <CardBox
@click="handleView(slotProps)" :value="slotProps"
:actions="getActions(slotProps, 'card')" @click="handleView(slotProps)"
:status="slotProps.state?.value" :actions="getActions(slotProps, 'card')"
:statusText="slotProps.state?.text" :status="slotProps.state?.value"
:statusNames="{ :statusText="slotProps.state?.text"
enabled: 'processing', :statusNames="{
disabled: 'error', enabled: 'processing',
}" disabled: 'error',
> }"
<template #img>
<img
:src="getImage('/device/instance/device-card.png')"
/>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row style="margin-top: 20px">
<j-col :span="12">
<div class="card-item-content-text">
通讯协议
</div>
<Ellipsis>{{
options.find(
(i) => i.value === slotProps.category,
)?.label || slotProps.category
}}</Ellipsis>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
所属边缘网关
</div>
<Ellipsis style="width: 100%">
{{ slotProps.sourceName }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'edge/Resource:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
/>
</template>
<template #sourceId="slotProps">
{{ slotProps.sourceName }}
</template>
<template #category="slotProps">
{{
options.find((i) => i.value === slotProps.category)
?.label || slotProps.category
}}
</template>
<template #createTime="slotProps">
<span>{{
dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
}}</span>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #img>
:disabled="i.disabled" <img
:popConfirm="i.popConfirm" :src="
:tooltip="{ getImage('/device/instance/device-card.png')
...i.tooltip, "
}" />
@click="i.onClick" </template>
type="link" <template #content>
style="padding: 0 5px" <Ellipsis style="width: calc(100% - 100px)">
:danger="i.key === 'delete'" <span style="font-size: 16px; font-weight: 600">
:hasPermission=" {{ slotProps.name }}
i.key === 'view' </span>
? true </Ellipsis>
: 'edge/Resource:' + i.key <j-row style="margin-top: 20px">
" <j-col :span="12">
<div class="card-item-content-text">
通讯协议
</div>
<Ellipsis>{{
options.find(
(i) =>
i.value === slotProps.category,
)?.label || slotProps.category
}}</Ellipsis>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
所属边缘网关
</div>
<Ellipsis style="width: 100%">
{{ slotProps.sourceName }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'edge/Resource:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
/>
</template>
<template #sourceId="slotProps">
{{ slotProps.sourceName }}
</template>
<template #category="slotProps">
{{
options.find((i) => i.value === slotProps.category)
?.label || slotProps.category
}}
</template>
<template #createTime="slotProps">
<span>{{
dayjs(slotProps.createTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}</span>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> <PermissionButton
</PermissionButton> :disabled="i.disabled"
</template> :popConfirm="i.popConfirm"
</j-space> :tooltip="{
</template> ...i.tooltip,
</JProTable> }"
@click="i.onClick"
type="link"
style="padding: 0 5px"
:danger="i.key === 'delete'"
:hasPermission="
i.key === 'view'
? true
: 'edge/Resource:' + i.key
"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<Save <Save
v-if="visible" v-if="visible"
:data="current" :data="current"

View File

@ -264,6 +264,7 @@ const saveCurrentData = () => {
port: modalForm.port, port: modalForm.port,
publicHost: modalForm.publicHost, publicHost: modalForm.publicHost,
publicPort: modalForm.publicPort, publicPort: modalForm.publicPort,
maxMessageSize:8192,
}, },
}); });
// //

View File

@ -652,7 +652,13 @@ const saveData = () => {
if (resp?.status === 200) { if (resp?.status === 200) {
onlyMessage('操作成功', 'success'); onlyMessage('操作成功', 'success');
history.back(); if (route.query.save) {
// @ts-ignore
window?.onSaveSuccess(resp.result);
window.close();
} else {
history.back();
}
} }
}); });
}; };

View File

@ -2,149 +2,153 @@
<template> <template>
<page-container> <page-container>
<pro-search :columns="columns" target="media" @search="handleSearch" /> <pro-search :columns="columns" target="media" @search="handleSearch" />
<FullPage>
<JProTable <JProTable
ref="listRef" ref="listRef"
model="table" model="table"
:columns="columns" :columns="columns"
:request="(e:any) => CascadeApi.queryBindChannel(route?.query.id as string, e)" :request="(e:any) => CascadeApi.queryBindChannel(route?.query.id as string, e)"
:defaultParams="{ :defaultParams="{
pageSize: 10, pageSize: 10,
sorts: [{ name: 'name', order: 'desc' }], sorts: [{ name: 'name', order: 'desc' }],
}" }"
:params="params" :params="params"
:rowSelection="{ :rowSelection="{
selectedRowKeys: _selectedRowKeys, selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange, onChange: onSelectChange,
}" }"
@cancelSelect="_selectedRowKeys = []" @cancelSelect="_selectedRowKeys = []"
:pagination="{ :pagination="{
showSizeChanger: true, showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
}" }"
> >
<template #headerTitle> <template #headerTitle>
<h3>通道列表</h3> <h3>通道列表</h3>
</template> </template>
<template #rightExtraRender> <template #rightExtraRender>
<j-space>
<j-button type="primary" @click="bindVis = true">
绑定通道
</j-button>
<j-popconfirm
title="确认解绑?"
@confirm="handleMultipleUnbind"
>
<j-button> 批量解绑 </j-button>
</j-popconfirm>
</j-space>
</template>
<template #gbChannelIdHeader="title">
<j-tooltip
title="国标级联有16位、20位两种格式。在当前页面修改不会修改视频设备-通道页面中的国标ID"
>
<j-space> <j-space>
<span>{{ title }}</span> <j-button type="primary" @click="bindVis = true">
<AIcon type="InfoCircleOutlined" /> 绑定通道
</j-space>
</j-tooltip>
</template>
<template #channelId="slotProps">
<j-space>
<Ellipsis style="width: 150px">
{{ slotProps.gbChannelId }}
</Ellipsis>
<j-popover
v-model:visible="slotProps.popVis"
trigger="click"
>
<template #title>
<div class="header">
<span>编辑国标ID</span>
<AIcon
type="CloseOutlined"
@click="handleClose(slotProps)"
/>
</div>
</template>
<template #content>
<div class="simple-form">
<j-input
v-model:value="gbID"
@change="validField(slotProps)"
placeholder="请输入国标ID"
/>
<div
class="error"
v-if="valid && !valid?.passed"
>
<!-- {{ valid?.reason }} -->
该国标ID在同一设备下已存在
</div>
</div>
<j-button
type="primary"
@click="handleSave(slotProps)"
:loading="loading"
style="width: 100%"
>
保存
</j-button>
</template>
<j-button type="link" @click="slotProps.popVis = true">
<AIcon type="EditOutlined" />
</j-button> </j-button>
</j-popover>
</j-space>
</template>
<template #status="slotProps">
<j-space>
<j-badge
:status="
slotProps.status.value === 'online'
? 'success'
: 'error'
"
:text="slotProps.status.text"
></j-badge>
</j-space>
</template>
<template #action="slotProps">
<j-space :size="16">
<j-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<j-popconfirm <j-popconfirm
v-if="i.popConfirm" title="确认解绑?"
v-bind="i.popConfirm" @confirm="handleMultipleUnbind"
:disabled="i.disabled"
> >
<j-button <j-button> 批量解绑 </j-button>
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></j-button>
</j-popconfirm> </j-popconfirm>
<j-button </j-space>
style="padding: 0" </template>
type="link" <template #gbChannelIdHeader="title">
v-else <j-tooltip
@click="i.onClick && i.onClick(slotProps)" title="国标级联有16位、20位两种格式。在当前页面修改不会修改视频设备-通道页面中的国标ID"
>
<j-space>
<span>{{ title }}</span>
<AIcon type="InfoCircleOutlined" />
</j-space>
</j-tooltip>
</template>
<template #channelId="slotProps">
<j-space>
<Ellipsis style="width: 150px">
{{ slotProps.gbChannelId }}
</Ellipsis>
<j-popover
v-model:visible="slotProps.popVis"
trigger="click"
> >
<template #title>
<div class="header">
<span>编辑国标ID</span>
<AIcon
type="CloseOutlined"
@click="handleClose(slotProps)"
/>
</div>
</template>
<template #content>
<div class="simple-form">
<j-input
v-model:value="gbID"
@change="validField(slotProps)"
placeholder="请输入国标ID"
/>
<div
class="error"
v-if="valid && !valid?.passed"
>
<!-- {{ valid?.reason }} -->
该国标ID在同一设备下已存在
</div>
</div>
<j-button
type="primary"
@click="handleSave(slotProps)"
:loading="loading"
style="width: 100%"
>
保存
</j-button>
</template>
<j-button <j-button
type="link"
@click="slotProps.popVis = true"
>
<AIcon type="EditOutlined" />
</j-button>
</j-popover>
</j-space>
</template>
<template #status="slotProps">
<j-space>
<j-badge
:status="
slotProps.status.value === 'online'
? 'success'
: 'error'
"
:text="slotProps.status.text"
></j-badge>
</j-space>
</template>
<template #action="slotProps">
<j-space :size="16">
<j-tooltip
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
v-bind="i.tooltip"
>
<j-popconfirm
v-if="i.popConfirm"
v-bind="i.popConfirm"
:disabled="i.disabled" :disabled="i.disabled"
>
<j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
><AIcon :type="i.icon"
/></j-button>
</j-popconfirm>
<j-button
style="padding: 0" style="padding: 0"
type="link" type="link"
><AIcon :type="i.icon" v-else
/></j-button> @click="i.onClick && i.onClick(slotProps)"
</j-button> >
</j-tooltip> <j-button
</j-space> :disabled="i.disabled"
</template> style="padding: 0"
</JProTable> type="link"
><AIcon :type="i.icon"
/></j-button>
</j-button>
</j-tooltip>
</j-space>
</template>
</JProTable>
</FullPage>
<BindChannel v-model:visible="bindVis" @submit="listRef.reload()" /> <BindChannel v-model:visible="bindVis" @submit="listRef.reload()" />
</page-container> </page-container>

View File

@ -5,139 +5,146 @@
target="media-cascade" target="media-cascade"
@search="handleSearch" @search="handleSearch"
/> />
<FullPage>
<JProTable <JProTable
ref="listRef" ref="listRef"
:columns="columns" :columns="columns"
:request="(e:any) => lastValueFrom(e)" :request="(e:any) => lastValueFrom(e)"
:defaultParams="{ :defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
:params="params" :params="params"
:gridColumn="2" :gridColumn="2"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton <PermissionButton
type="primary" type="primary"
@click="handleAdd" @click="handleAdd"
hasPermission="media/Cascade:add" hasPermission="media/Cascade:add"
>
<template #icon><AIcon type="PlusOutlined" />新增</template>
</PermissionButton>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:showStatus="true"
:status="slotProps.status?.value"
:statusText="slotProps.status?.text"
:statusNames="{
enabled: 'success',
disabled: 'error',
}"
>
<template #img>
<slot name="img">
<img
:src="
getImage('/device/instance/device-card.png')
"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<p>通道数量{{ slotProps.count || 0 }}</p>
<Ellipsis>
<j-badge
:text="`sip:${slotProps.sipConfigs[0]?.sipId}@${slotProps.sipConfigs[0]?.hostAndPort}`"
:status="
slotProps.status?.value === 'enabled'
? 'success'
: 'error'
"
/>
</Ellipsis>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'media/Cascade:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #sipId="slotProps">
{{ slotProps.sipConfigs[0]?.sipId }}
</template>
<template #publicHost="slotProps">
{{ slotProps.sipConfigs[0]?.publicHost }}
</template>
<template #count="slotProps">
{{ slotProps.count || 0 }}
</template>
<template #status="slotProps">
<j-badge
:text="slotProps.status?.text"
:status="
slotProps.status?.value === 'enabled'
? 'success'
: 'error'
"
/>
</template>
<template #onlineStatus="slotProps">
<j-badge
:text="slotProps.onlineStatus?.text"
:status="
slotProps.onlineStatus?.value === 'online'
? 'success'
: 'error'
"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #icon
:danger="i.key === 'delete'" ><AIcon type="PlusOutlined" />新增</template
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'media/Cascade:' + i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> </PermissionButton>
</PermissionButton> </template>
</template> <template #card="slotProps">
</j-space> <CardBox
</template> :value="slotProps"
</JProTable> :actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:showStatus="true"
:status="slotProps.status?.value"
:statusText="slotProps.status?.text"
:statusNames="{
enabled: 'success',
disabled: 'error',
}"
>
<template #img>
<slot name="img">
<img
:src="
getImage(
'/device/instance/device-card.png',
)
"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<p>通道数量{{ slotProps.count || 0 }}</p>
<Ellipsis>
<j-badge
:text="`sip:${slotProps.sipConfigs[0]?.sipId}@${slotProps.sipConfigs[0]?.hostAndPort}`"
:status="
slotProps.status?.value === 'enabled'
? 'success'
: 'error'
"
/>
</Ellipsis>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'media/Cascade:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #sipId="slotProps">
{{ slotProps.sipConfigs[0]?.sipId }}
</template>
<template #publicHost="slotProps">
{{ slotProps.sipConfigs[0]?.publicHost }}
</template>
<template #count="slotProps">
{{ slotProps.count || 0 }}
</template>
<template #status="slotProps">
<j-badge
:text="slotProps.status?.text"
:status="
slotProps.status?.value === 'enabled'
? 'success'
: 'error'
"
/>
</template>
<template #onlineStatus="slotProps">
<j-badge
:text="slotProps.onlineStatus?.text"
:status="
slotProps.onlineStatus?.value === 'online'
? 'success'
: 'error'
"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'media/Cascade:' + i.key"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<Publish v-model:visible="publishVis" :data="currentData" /> <Publish v-model:visible="publishVis" :data="currentData" />
</page-container> </page-container>

View File

@ -24,101 +24,109 @@
target="channel" target="channel"
@search="handleSearch" @search="handleSearch"
/> />
<FullPage>
<JProTable <JProTable
ref="listRef" ref="listRef"
model="table" model="table"
:columns="columns" :columns="columns"
:request="(e:any) => ChannelApi.list(e, route?.query.id as string)" :request="(e:any) => ChannelApi.list(e, route?.query.id as string)"
:defaultParams="{ :defaultParams="{
pageSize: 10, pageSize: 10,
sorts: [{ name: 'notifyTime', order: 'desc' }], sorts: [{ name: 'notifyTime', order: 'desc' }],
}" }"
:params="params" :params="params"
:pagination="{ :pagination="{
showSizeChanger: true, showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
}" }"
> >
<template #headerTitle> <template #headerTitle>
<j-tooltip <j-tooltip
v-if="route?.query.type === 'gb28181-2016'" v-if="route?.query.type === 'gb28181-2016'"
title="接入方式为GB/T28281时不支持新增" title="接入方式为GB/T28281时不支持新增"
>
<j-button type="primary" disabled> 新增 </j-button>
</j-tooltip>
<PermissionButton
type="primary"
@click="handleAdd"
hasPermission="media/Device:add"
>
<template #icon
><AIcon type="PlusOutlined" />新增</template
> >
</PermissionButton> <j-button type="primary" disabled>
</template> 新增
<template #status="slotProps"> </j-button>
<j-space> </j-tooltip>
<j-badge <PermissionButton
:status=" v-else
slotProps.status.value === 'online' type="primary"
? 'success' @click="handleAdd"
: 'error' hasPermission="media/Device:add"
"
:text="slotProps.status.text"
></j-badge>
</j-space>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #icon
v-if=" ><AIcon type="PlusOutlined" />新增</template
i.key !== 'play' && i.key !== 'backPlay'
"
:danger="i.key === 'delete'"
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'media/Device:' + i.key"
> >
<template #icon </PermissionButton>
><AIcon :type="i.icon" </template>
/></template> <template #status="slotProps">
</PermissionButton> <j-space>
<!-- 回放/播放不要权限控制 --> <j-badge
<template v-else> :status="
<j-tooltip :key="i.key" v-bind="i.tooltip"> slotProps.status.value === 'online'
<j-button ? 'success'
style="padding: 0px" : 'error'
type="link" "
@click=" :text="slotProps.status.text"
i.onClick && ></j-badge>
i.onClick(slotProps) </j-space>
" </template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
v-if="
i.key !== 'play' &&
i.key !== 'backPlay'
"
:danger="i.key === 'delete'"
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'media/Device:' + i.key"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
<!-- 回放/播放不要权限控制 -->
<template v-else>
<j-tooltip
:key="i.key"
v-bind="i.tooltip"
> >
<j-button <j-button
:disabled="i.disabled" style="padding: 0px"
style="padding: 0"
type="link" type="link"
@click="
i.onClick &&
i.onClick(slotProps)
"
> >
<AIcon :type="i.icon" /> <j-button
:disabled="i.disabled"
style="padding: 0"
type="link"
>
<AIcon :type="i.icon" />
</j-button>
</j-button> </j-button>
</j-button> </j-tooltip>
</j-tooltip> </template>
</template> </template>
</template> </j-space>
</j-space> </template>
</template> </JProTable>
</JProTable> </FullPage>
</div> </div>
</div> </div>

View File

@ -1,186 +1,191 @@
<!-- 回放 --> <!-- 回放 -->
<template> <template>
<page-container> <page-container>
<div class="playback-warp"> <FullPage>
<!-- 播放器/进度条 --> <div class="playback-warp">
<div class="playback-left"> <!-- 播放器/进度条 -->
<LivePlayer <div class="playback-left">
ref="player" <LivePlayer
autoplay ref="player"
:url="url" autoplay
className="playback-media" :url="url"
:live="type === 'local'" className="playback-media"
:on-play=" :live="type === 'local'"
() => { :on-play="
isEnded = false; () => {
playStatus = 1; isEnded = false;
} playStatus = 1;
"
:on-pause="
() => {
playStatus = 2;
}
"
:on-ended="
() => {
playStatus = 0;
if (playTimeNode && isEnded) {
isEnded = true;
playTimeNode.onNextPlay();
} }
} "
" :on-pause="
:on-error=" () => {
() => { playStatus = 2;
playStatus = 0; }
} "
" :on-ended="
:on-time-update=" () => {
(e: any) => { playStatus = 0;
playTime = e; if (playTimeNode && isEnded) {
} isEnded = true;
" playTimeNode.onNextPlay();
/> }
<TimeLine }
ref="playTimeNode" "
:type="type" :on-error="
:data="historyList" () => {
:date-time="time" playStatus = 0;
:on-change="handleTimeLineChange" }
:play-status="playStatus" "
:play-time="playNowTime + playTime * 1000" :on-time-update="
:local-to-server="cloudTime" (e: any) => {
/> playTime = e;
</div> }
<div class="playback-right"> "
<j-spin :spinning="loading">
<j-tooltip placement="topLeft">
<template #title>
<div>云端存储在服务器中</div>
<div>本地存储在设备本地</div>
</template>
<div>类型: <AIcon type="QuestionCircleOutlined" /></div>
</j-tooltip>
<RadioCard
layout="horizontal"
:options="[
{
label: '云端',
value: 'cloud',
logo: getImage('/media/cloud.png'),
},
{
label: '本地',
value: 'local',
logo: getImage('/local.png'),
disabled: deviceType === 'fixed-media',
},
]"
:checkStyle="true"
v-model="type"
/> />
<div class="playback-calendar"> <TimeLine
<j-calendar ref="playTimeNode"
v-model:value="time" :type="type"
:fullscreen="false" :data="historyList"
:disabledDate=" :date-time="time"
(currentDate: Dayjs) => currentDate > dayjs(new Date()) :on-change="handleTimeLineChange"
" :play-status="playStatus"
@change="handlePanelChange" :play-time="playNowTime + playTime * 1000"
:local-to-server="cloudTime"
/>
</div>
<div class="playback-right">
<j-spin :spinning="loading">
<j-tooltip placement="topLeft">
<template #title>
<div>云端存储在服务器中</div>
<div>本地存储在设备本地</div>
</template>
<div>
类型: <AIcon type="QuestionCircleOutlined" />
</div>
</j-tooltip>
<RadioCard
layout="horizontal"
:options="[
{
label: '云端',
value: 'cloud',
logo: getImage('/media/cloud.png'),
},
{
label: '本地',
value: 'local',
logo: getImage('/local.png'),
disabled: deviceType === 'fixed-media',
},
]"
:checkStyle="true"
v-model="type"
/> />
</div> <div class="playback-calendar">
<div <j-calendar
class="playback-list" v-model:value="time"
:class="{ 'no-list': !historyList.length }" :fullscreen="false"
> :disabledDate="
<j-empty (currentDate: Dayjs) => currentDate > dayjs(new Date())
v-if="!historyList.length" "
description="暂无数据" @change="handlePanelChange"
/> />
<j-list </div>
v-else <div
class="playback-list-items" class="playback-list"
itemLayout="horizontal" :class="{ 'no-list': !historyList.length }"
:dataSource="historyList"
> >
<template #renderItem="{ item }"> <j-empty
<j-list-item> v-if="!historyList.length"
<template #actions> description="暂无数据"
<j-tooltip />
key="play-btn" <j-list
:title=" v-else
(item.startTime || class="playback-list-items"
item.mediaStartTime) === itemLayout="horizontal"
playNowTime && :dataSource="historyList"
playStatus === 1 >
? '暂停' <template #renderItem="{ item }">
: '播放' <j-list-item>
" <template #actions>
> <j-tooltip
<a key="play-btn"
@click=" :title="
handlePlay( (item.startTime ||
item.startTime || item.mediaStartTime) ===
item.mediaStartTime, playNowTime &&
) playStatus === 1
? '暂停'
: '播放'
" "
> >
<AIcon <a
:type=" @click="
(item.startTime || handlePlay(
item.mediaStartTime) === item.startTime ||
playNowTime && item.mediaStartTime,
playStatus === 1 )
? 'PauseCircleOutlined' "
: 'PlayCircleOutlined' >
<AIcon
:type="
(item.startTime ||
item.mediaStartTime) ===
playNowTime &&
playStatus === 1
? 'PauseCircleOutlined'
: 'PlayCircleOutlined'
"
/>
</a>
</j-tooltip>
<j-tooltip
key="download"
:title="
type !== 'local'
? '下载录像文件'
: item.isServer
? '查看'
: '下载到云端'
"
>
<IconNode
:type="type"
:item="item"
:on-cloud-view="cloudView"
:on-down-load="
() =>
downloadClick(item)
" "
/> />
</a> </j-tooltip>
</j-tooltip> </template>
<j-tooltip
key="download"
:title="
type !== 'local'
? '下载录像文件'
: item.isServer
? '查看'
: '下载到云端'
"
>
<IconNode
:type="type"
:item="item"
:on-cloud-view="cloudView"
:on-down-load="
() => downloadClick(item)
"
/>
</j-tooltip>
</template>
<div> <div>
{{ {{
dayjs( dayjs(
item.startTime || item.startTime ||
item.mediaStartTime, item.mediaStartTime,
).format('HH:mm:ss') ).format('HH:mm:ss')
}} }}
~ ~
{{ {{
dayjs( dayjs(
item.endTime || item.endTime ||
item.mediaEndTime, item.mediaEndTime,
).format('HH:mm:ss') ).format('HH:mm:ss')
}} }}
</div> </div>
</j-list-item> </j-list-item>
</template> </template>
<div></div> <div></div>
</j-list> </j-list>
</div> </div>
</j-spin> </j-spin>
</div>
</div> </div>
</div> </FullPage>
</page-container> </page-container>
</template> </template>
@ -216,7 +221,7 @@ const deviceType = ref('');
/** /**
* 查询本地视频 * 查询本地视频
* @param date * @param date
*/ */
const queryLocalRecords = async (date: Dayjs) => { const queryLocalRecords = async (date: Dayjs) => {
playStatus.value = 0; playStatus.value = 0;

View File

@ -5,146 +5,159 @@
target="notice-config" target="notice-config"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="listRef" <JProTable
:columns="columns" ref="listRef"
:request="DeviceApi.list" :columns="columns"
:defaultParams="{ :request="DeviceApi.list"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
}" sorts: [{ name: 'createTime', order: 'desc' }],
:params="params" }"
:gridColumn="4" :params="params"
> :gridColumn="4"
<template #headerTitle> >
<PermissionButton <template #headerTitle>
type="primary" <PermissionButton
@click="handleAdd" type="primary"
hasPermission="media/Device:add" @click="handleAdd"
> hasPermission="media/Device:add"
<template #icon><AIcon type="PlusOutlined" />新增</template>
</PermissionButton>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:showStatus="true"
:status="slotProps.state.value"
:statusText="slotProps.state.text"
:statusNames="{ online: 'processing', offline: 'error' }"
>
<template #img>
<slot name="img">
<img :src="getImage('/device-media.png')" />
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">厂商</div>
<div>{{ slotProps.manufacturer }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
通道数量
</div>
<div>{{ slotProps.channelNumber }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">型号</div>
<div>{{ slotProps.model }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
接入方式
</div>
<div>
{{ providerType[slotProps.provider] }}
</div>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="
'media/Device:' +
(item.key !== 'updateChannel'
? item.key
: 'update')
"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #channelNumber="slotProps">
{{ slotProps.channelNumber || 0 }}
</template>
<template #provider="slotProps">
{{ providerType[slotProps.provider] }}
</template>
<template #productId="slotProps">
{{ getProductName(slotProps.productId) }}
</template>
<template #state="slotProps">
<j-badge
:text="slotProps.state?.text"
:status="
slotProps.state?.value === 'online'
? 'success'
: 'error'
"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<PermissionButton <template #icon
:danger="i.key === 'delete'" ><AIcon type="PlusOutlined" />新增</template
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="
'media/Device:' +
(i.key !== 'updateChannel' &&
i.key !== 'viewDevice'
? i.key
: 'update')
"
> >
<template #icon><AIcon :type="i.icon" /></template> </PermissionButton>
</PermissionButton> </template>
</template> <template #card="slotProps">
</j-space> <CardBox
</template> :value="slotProps"
</JProTable> :actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:showStatus="true"
:status="slotProps.state.value"
:statusText="slotProps.state.text"
:statusNames="{
online: 'processing',
offline: 'error',
}"
>
<template #img>
<slot name="img">
<img :src="getImage('/device-media.png')" />
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
厂商
</div>
<div>{{ slotProps.manufacturer }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
通道数量
</div>
<div>{{ slotProps.channelNumber }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
型号
</div>
<div>{{ slotProps.model }}</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
接入方式
</div>
<div>
{{ providerType[slotProps.provider] }}
</div>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="
'media/Device:' +
(item.key !== 'updateChannel'
? item.key
: 'update')
"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #channelNumber="slotProps">
{{ slotProps.channelNumber || 0 }}
</template>
<template #provider="slotProps">
{{ providerType[slotProps.provider] }}
</template>
<template #productId="slotProps">
{{ getProductName(slotProps.productId) }}
</template>
<template #state="slotProps">
<j-badge
:text="slotProps.state?.text"
:status="
slotProps.state?.value === 'online'
? 'success'
: 'error'
"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="
'media/Device:' +
(i.key !== 'updateChannel' &&
i.key !== 'viewDevice'
? i.key
: 'update')
"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
</page-container> </page-container>
</template> </template>

View File

@ -1,23 +1,28 @@
<template> <template>
<page-container> <page-container>
<j-card class="splitScreen"> <FullPage>
<div class="split-screen"> <j-card class="splitScreen">
<LeftTree @onSelect="mediaStart" /> <div class="split-screen">
<div class="right-content"> <LeftTree @onSelect="mediaStart" />
<ScreenPlayer <div class="right-content">
ref="player" <ScreenPlayer
:id="deviceId" ref="player"
:channelId="channelId" :id="deviceId"
:onMouseUp="(id, cId) => channelApi.ptzStop(id, cId)" :channelId="channelId"
:onMouseDown=" :onMouseUp="
(id, cId, type) => channelApi.ptzTool(id, cId, type) (id, cId) => channelApi.ptzStop(id, cId)
" "
:historyHandle="(dId, cId) => getMediaUrl(dId, cId)" :onMouseDown="
showScreen (id, cId, type) =>
/> channelApi.ptzTool(id, cId, type)
"
:historyHandle="(dId, cId) => getMediaUrl(dId, cId)"
showScreen
/>
</div>
</div> </div>
</div> </j-card>
</j-card> </FullPage>
</page-container> </page-container>
</template> </template>
@ -32,8 +37,8 @@ const player = ref();
/** /**
* 获取视频链接 * 获取视频链接
* @param dId * @param dId
* @param cId * @param cId
*/ */
const getMediaUrl = (dId: string, cId: string): string => { const getMediaUrl = (dId: string, cId: string): string => {
return channelApi.ptzStart(dId, cId, 'mp4'); return channelApi.ptzStart(dId, cId, 'mp4');
@ -41,7 +46,7 @@ const getMediaUrl = (dId: string, cId: string): string => {
/** /**
* 点击左侧摄像头, 播放对应视频 * 点击左侧摄像头, 播放对应视频
* @param e * @param e
*/ */
const mediaStart = (e: { cId: string; dId: string }) => { const mediaStart = (e: { cId: string; dId: string }) => {
channelId.value = e.cId; channelId.value = e.cId;

View File

@ -47,7 +47,7 @@
total: dataSource.length, total: dataSource.length,
current: current, current: current,
pageSize: pageSize, pageSize: pageSize,
pageSizeOptions: ['5', '10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
showTotal: (total: number, range: number) => `${range[0]} - ${range[1]} 条/总共 ${total}`, showTotal: (total: number, range: number) => `${range[0]} - ${range[1]} 条/总共 ${total}`,
@ -399,7 +399,7 @@ const getTableData = (terms?: any) => {
* 前端分页 * 前端分页
*/ */
const current = ref(1); const current = ref(1);
const pageSize = ref(5); const pageSize = ref(10);
const handleTableChange = (pagination: any) => { const handleTableChange = (pagination: any) => {
current.value = pagination.current; current.value = pagination.current;
pageSize.value = pagination.pageSize; pageSize.value = pagination.pageSize;

View File

@ -6,185 +6,198 @@
target="notice-config" target="notice-config"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="configRef" <JProTable
:columns="columns" ref="configRef"
:request="ConfigApi.list" :columns="columns"
:defaultParams="{ :request="ConfigApi.list"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
}" sorts: [{ name: 'createTime', order: 'desc' }],
:params="params" }"
:gridColumn="3" :params="params"
> :gridColumn="3"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
type="primary"
@click="handleAdd"
hasPermission="notice/Config:add"
>
新增
</PermissionButton>
<j-upload
name="file"
accept=".json"
:showUploadList="false"
:before-upload="beforeUpload"
>
<PermissionButton hasPermission="notice/Config:import">
导入
</PermissionButton>
</j-upload>
<j-popconfirm
title="确认导出?"
ok-text="确定"
cancel-text="取消"
@confirm="handleExport"
>
<PermissionButton hasPermission="notice/Config:export">
导出
</PermissionButton>
</j-popconfirm>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:showStatus="false"
:statusNames="{}"
:value="slotProps"
:actions="getActions(slotProps, 'card')"
>
<template #img>
<slot name="img">
<img
:src="
getLogo(slotProps.type, slotProps.provider)
"
class="logo"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">说明</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<j-dropdown
placement="bottomRight"
v-if="item.key === 'others'"
>
<j-button>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</j-button>
<template #overlay>
<j-menu>
<j-menu-item
v-for="(o, i) in item.children"
:key="i"
>
<PermissionButton
type="link"
@click="o.onClick"
:hasPermission="`notice/Config:${o.key}`"
>
<template #icon>
<AIcon :type="o.icon" />
</template>
<span>{{ o.text }}</span>
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
<j-popconfirm
v-else-if="item.key === 'delete'"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<PermissionButton
:disabled="item.disabled"
:hasPermission="`notice/Config:${item.key}`"
>
<template #icon>
<AIcon type="DeleteOutlined" />
</template>
</PermissionButton>
</j-popconfirm>
<template v-else>
<PermissionButton
:disabled="item.disabled"
@click="item.onClick"
:hasPermission="`notice/Config:${item.key}`"
>
<template #icon>
<AIcon :type="item.icon" />
</template>
<span>{{ item.text }}</span>
</PermissionButton>
</template>
</j-tooltip>
</template>
</CardBox>
</template>
<template #type="slotProps">
<span> {{ getMethodTxt(slotProps.type) }}</span>
</template>
<template #provider="slotProps">
<span>
{{ getProviderTxt(slotProps.type, slotProps.provider) }}
</span>
</template>
<template #description="slotProps">
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton <PermissionButton
:danger="i.key === 'delete'" type="primary"
:disabled="i.disabled" @click="handleAdd"
:popConfirm="i.popConfirm" hasPermission="notice/Config:add"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'notice/Config:' + i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> 新增
</PermissionButton> </PermissionButton>
</template> <j-upload
</j-space> name="file"
</template> accept=".json"
</JProTable> :showUploadList="false"
:before-upload="beforeUpload"
>
<PermissionButton
hasPermission="notice/Config:import"
>
导入
</PermissionButton>
</j-upload>
<j-popconfirm
title="确认导出?"
ok-text="确定"
cancel-text="取消"
@confirm="handleExport"
>
<PermissionButton
hasPermission="notice/Config:export"
>
导出
</PermissionButton>
</j-popconfirm>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:showStatus="false"
:statusNames="{}"
:value="slotProps"
:actions="getActions(slotProps, 'card')"
>
<template #img>
<slot name="img">
<img
:src="
getLogo(
slotProps.type,
slotProps.provider,
)
"
class="logo"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
说明
</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<j-dropdown
placement="bottomRight"
v-if="item.key === 'others'"
>
<j-button>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</j-button>
<template #overlay>
<j-menu>
<j-menu-item
v-for="(o, i) in item.children"
:key="i"
>
<PermissionButton
type="link"
@click="o.onClick"
:hasPermission="`notice/Config:${o.key}`"
>
<template #icon>
<AIcon :type="o.icon" />
</template>
<span>{{ o.text }}</span>
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
<j-popconfirm
v-else-if="item.key === 'delete'"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<PermissionButton
:disabled="item.disabled"
:hasPermission="`notice/Config:${item.key}`"
>
<template #icon>
<AIcon type="DeleteOutlined" />
</template>
</PermissionButton>
</j-popconfirm>
<template v-else>
<PermissionButton
:disabled="item.disabled"
@click="item.onClick"
:hasPermission="`notice/Config:${item.key}`"
>
<template #icon>
<AIcon :type="item.icon" />
</template>
<span>{{ item.text }}</span>
</PermissionButton>
</template>
</j-tooltip>
</template>
</CardBox>
</template>
<template #type="slotProps">
<span> {{ getMethodTxt(slotProps.type) }}</span>
</template>
<template #provider="slotProps">
<span>
{{ getProviderTxt(slotProps.type, slotProps.provider) }}
</span>
</template>
<template #description="slotProps">
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:danger="i.key === 'delete'"
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'notice/Config:' + i.key"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<Debug v-model:visible="debugVis" :data="currentConfig" /> <Debug v-model:visible="debugVis" :data="currentConfig" />
<Log v-model:visible="logVis" :data="currentConfig" /> <Log v-model:visible="logVis" :data="currentConfig" />

View File

@ -783,6 +783,7 @@ import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable'; import { TOKEN_KEY } from '@/utils/variable';
import { phoneRegEx } from '@/utils/validate'; import { phoneRegEx } from '@/utils/validate';
import type { Rule } from 'ant-design-vue/es/form'; import type { Rule } from 'ant-design-vue/es/form';
import { cloneDeep } from 'lodash-es';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
@ -1005,7 +1006,8 @@ const formRules = ref({
trigger: 'change', trigger: 'change',
validator(_rule: Rule, value: string) { validator(_rule: Rule, value: string) {
if (!value) return Promise.resolve(); if (!value) return Promise.resolve();
if (!phoneRegEx(value)) return Promise.reject('该字段不是有效的手机号'); if (!phoneRegEx(value))
return Promise.reject('该字段不是有效的手机号');
return Promise.resolve(); return Promise.resolve();
}, },
}, },
@ -1028,78 +1030,19 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
formRules.value, formRules.value,
); );
// markdown //
watch( watch(
() => formData.value.template.markdown?.title, [
(val) => { () => formData.value.template.markdown?.title,
if (!val) { () => formData.value.template.link?.title,
formData.value.variableDefinitions = []; () => formData.value.template.subject,
return; () => formData.value.template.message,
} () => formData.value.template.ttsmessage,
() => formData.value.template.body,
],
() => {
variableReg(); variableReg();
}, },
{ deep: true },
);
// link
watch(
() => formData.value.template.link?.title,
(val) => {
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
);
//
watch(
() => formData.value.template.subject,
(val) => {
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
);
//
watch(
() => formData.value.template.message,
(val) => {
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
);
//
watch(
() => formData.value.template.ttsmessage,
(val) => {
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
);
// webhook
watch(
() => formData.value.template.body,
(val) => {
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
); );
/** /**
@ -1176,7 +1119,7 @@ const handleMessageTypeChange = () => {
content: formData.value.template.message as string, content: formData.value.template.message as string,
}; };
} }
formData.value.variableDefinitions = []; // formData.value.variableDefinitions = [];
}; };
/** /**
@ -1186,7 +1129,10 @@ const getDetail = async () => {
if (route.params.id !== ':id') { if (route.params.id !== ':id') {
const res = await templateApi.detail(route.params.id as string); const res = await templateApi.detail(route.params.id as string);
// formData.value = res.result; // formData.value = res.result;
Object.assign(formData.value, res.result); // Object.assign(formData.value, res.result);
console.log('res.result: ', res.result);
formData.value = cloneDeep(res.result);
console.log('formData.value: ', formData.value);
} }
}; };
getDetail(); getDetail();

View File

@ -6,189 +6,198 @@
target="notice-config" target="notice-config"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
ref="configRef" <JProTable
:columns="columns" ref="configRef"
:request="TemplateApi.list" :columns="columns"
:defaultParams="{ :request="TemplateApi.list"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
}" sorts: [{ name: 'createTime', order: 'desc' }],
:params="params" }"
:gridColumn="3" :params="params"
> :gridColumn="3"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
type="primary"
@click="handleAdd"
hasPermission="notice/Template:add"
>
新增
</PermissionButton>
<j-upload
name="file"
accept=".json"
:showUploadList="false"
:before-upload="beforeUpload"
>
<PermissionButton <PermissionButton
hasPermission="notice/Template:import" type="primary"
@click="handleAdd"
hasPermission="notice/Template:add"
> >
导入 新增
</PermissionButton> </PermissionButton>
</j-upload> <j-upload
<j-popconfirm name="file"
title="确认导出?" accept=".json"
ok-text="确定" :showUploadList="false"
cancel-text="取消" :before-upload="beforeUpload"
@confirm="handleExport" >
<PermissionButton
hasPermission="notice/Template:import"
>
导入
</PermissionButton>
</j-upload>
<j-popconfirm
title="确认导出?"
ok-text="确定"
cancel-text="取消"
@confirm="handleExport"
>
<PermissionButton
hasPermission="notice/Template:export"
>
导出
</PermissionButton>
</j-popconfirm>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:showStatus="false"
:value="slotProps"
:actions="getActions(slotProps, 'card')"
:statusNames="{}"
> >
<PermissionButton <template #img>
hasPermission="notice/Template:export" <slot name="img">
> <img
导出 :src="
</PermissionButton> getLogo(
</j-popconfirm> slotProps.type,
</j-space> slotProps.provider,
</template> )
<template #card="slotProps"> "
<CardBox class="logo"
:showStatus="false" />
:value="slotProps" </slot>
:actions="getActions(slotProps, 'card')" </template>
:statusNames="{}" <template #content>
> <h3 class="card-item-content-title">
<template #img> {{ slotProps.name }}
<slot name="img"> </h3>
<img <j-row>
:src=" <j-col :span="12">
getLogo(slotProps.type, slotProps.provider) <div class="card-item-content-text">
" 通知方式
class="logo" </div>
/> <div>
</slot> {{ getMethodTxt(slotProps.type) }}
</template> </div>
<template #content> </j-col>
<h3 class="card-item-content-title"> <j-col :span="12">
{{ slotProps.name }} <div class="card-item-content-text">
</h3> 说明
<j-row> </div>
<j-col :span="12"> <Ellipsis>
<div class="card-item-content-text"> {{ slotProps.description }}
通知方式 </Ellipsis>
</div> </j-col>
<div> </j-row>
{{ getMethodTxt(slotProps.type) }} </template>
</div> <template #actions="item">
</j-col> <j-tooltip
<j-col :span="12"> v-bind="item.tooltip"
<div class="card-item-content-text">说明</div> :title="item.disabled && item.tooltip.title"
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<j-dropdown
placement="bottomRight"
v-if="item.key === 'others'"
> >
<j-button> <j-dropdown
<AIcon :type="item.icon" /> placement="bottomRight"
<span>{{ item.text }}</span> v-if="item.key === 'others'"
</j-button>
<template #overlay>
<j-menu>
<j-menu-item
v-for="(o, i) in item.children"
:key="i"
>
<PermissionButton
type="link"
@click="o.onClick"
:hasPermission="`notice/Template:${o.key}`"
>
<template #icon>
<AIcon :type="o.icon" />
</template>
<span>{{ o.text }}</span>
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
<j-popconfirm
v-else-if="item.key === 'delete'"
v-bind="item.popConfirm"
:disabled="item.disabled"
>
<PermissionButton
:disabled="item.disabled"
:hasPermission="`notice/Template:${item.key}`"
> >
<template #icon> <j-button>
<AIcon type="DeleteOutlined" />
</template>
</PermissionButton>
</j-popconfirm>
<template v-else>
<PermissionButton
:disabled="item.disabled"
@click="item.onClick"
:hasPermission="`notice/Template:${item.key}`"
>
<template #icon>
<AIcon :type="item.icon" /> <AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</j-button>
<template #overlay>
<j-menu>
<j-menu-item
v-for="(o, i) in item.children"
:key="i"
>
<PermissionButton
type="link"
@click="o.onClick"
:hasPermission="`notice/Template:${o.key}`"
>
<template #icon>
<AIcon :type="o.icon" />
</template>
<span>{{ o.text }}</span>
</PermissionButton>
</j-menu-item>
</j-menu>
</template> </template>
<span>{{ item.text }}</span> </j-dropdown>
</PermissionButton> <j-popconfirm
</template> v-else-if="item.key === 'delete'"
</j-tooltip> v-bind="item.popConfirm"
</template> :disabled="item.disabled"
</CardBox> >
</template> <PermissionButton
<template #type="slotProps"> :disabled="item.disabled"
<span> {{ getMethodTxt(slotProps.type) }}</span> :hasPermission="`notice/Template:${item.key}`"
</template> >
<template #provider="slotProps"> <template #icon>
<span> <AIcon type="DeleteOutlined" />
{{ getProviderTxt(slotProps.type, slotProps.provider) }} </template>
</span> </PermissionButton>
</template> </j-popconfirm>
<template #description="slotProps"> <template v-else>
<Ellipsis> <PermissionButton
{{ slotProps.description }} :disabled="item.disabled"
</Ellipsis> @click="item.onClick"
</template> :hasPermission="`notice/Template:${item.key}`"
<template #action="slotProps"> >
<j-space :size="16"> <template #icon>
<template <AIcon :type="item.icon" />
v-for="i in getActions(slotProps, 'table')" </template>
:key="i.key" <span>{{ item.text }}</span>
> </PermissionButton>
<PermissionButton </template>
:danger="i.key === 'delete'" </j-tooltip>
:disabled="i.disabled" </template>
:popConfirm="i.popConfirm" </CardBox>
:tooltip="{ </template>
...i.tooltip, <template #type="slotProps">
}" <span> {{ getMethodTxt(slotProps.type) }}</span>
@click="i.onClick" </template>
type="link" <template #provider="slotProps">
style="padding: 0px" <span>
:hasPermission="'notice/Template:' + i.key" {{ getProviderTxt(slotProps.type, slotProps.provider) }}
</span>
</template>
<template #description="slotProps">
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
> >
<template #icon><AIcon :type="i.icon" /></template> <PermissionButton
</PermissionButton> :danger="i.key === 'delete'"
</template> :disabled="i.disabled"
</j-space> :popConfirm="i.popConfirm"
</template> :tooltip="{
</JProTable> ...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'notice/Template:' + i.key"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<Debug v-model:visible="debugVis" :data="currentConfig" /> <Debug v-model:visible="debugVis" :data="currentConfig" />
<Log v-model:visible="logVis" :data="currentConfig" /> <Log v-model:visible="logVis" :data="currentConfig" />

View File

@ -1,58 +1,79 @@
<template> <template>
<page-container :tabList="list" @tabChange="onTabChange" :tabActiveKey="tab"> <page-container
<div v-if="tab=='config'"> :tabList="list"
@tabChange="onTabChange"
:tabActiveKey="tab"
>
<div v-if="tab == 'config'">
<j-row :gutter="24"> <j-row :gutter="24">
<j-col :span="14"> <j-col :span="14">
<div class="alarm-level"> <FullPage>
<j-card <div class="alarm-level">
:headStyle="{ borderBottom: 'none', padding: 0 }" <j-card
:bodyStyle="{ padding: 0 }" :headStyle="{
:bordered="false" borderBottom: 'none',
> padding: 0,
<template #title> }"
<div class="alarmLevelTitle">告警级别配置</div> :bodyStyle="{ padding: 0 }"
</template> :bordered="false"
<div
v-for="(item, i) in levels"
:key="i"
class="alarmInputItem"
> >
<div> <template #title>
<img <div class="alarmLevelTitle">
:src=" 告警级别配置
getImage(`/alarm/alarm${i + 1}.png`) </div>
" </template>
alt="" <div
/> v-for="(item, i) in levels"
<span>{{ `级别${i + 1}` }}</span> :key="i"
class="alarmInputItem"
>
<div>
<img
:src="
getImage(
`/alarm/alarm${i + 1}.png`,
)
"
alt=""
/>
<span>{{ `级别${i + 1}` }}</span>
</div>
<div>
<j-input
type="text"
v-model:value="item.title"
:maxlength="64"
></j-input>
</div>
</div> </div>
<div> </j-card>
<j-input <!-- <j-button
type="text"
v-model:value="item.title"
:maxlength="64"
></j-input>
</div>
</div>
</j-card>
<!-- <j-button
type="primary" type="primary"
size="middle" size="middle"
@click="handleSaveLevel" @click="handleSaveLevel"
>保存</j-button >保存</j-button
> --> > -->
<PermissionButton type="primary" size="middle" @click="handleSaveLevel" hasPermission="rule-engine/Alarm/Config:update">保存</PermissionButton> <PermissionButton
</div> type="primary"
size="middle"
@click="handleSaveLevel"
hasPermission="rule-engine/Alarm/Config:update"
>保存</PermissionButton
>
</div>
</FullPage>
</j-col> </j-col>
<j-col :span="10"> <j-col :span="10">
<div class="description"> <FullPage>
<h1>功能说明</h1> <div class="description">
<div> <h1>功能说明</h1>
1告警级别用于描述告警的严重程度请根据业务管理方式进行自定义 <div>
1告警级别用于描述告警的严重程度请根据业务管理方式进行自定义
</div>
<div>2告警级别将会在告警配置中被引用</div>
<div>3最多可配置5个级别</div>
</div> </div>
<div>2告警级别将会在告警配置中被引用</div> </FullPage>
<div>3最多可配置5个级别</div>
</div>
</j-col> </j-col>
</j-row> </j-row>
</div> </div>
@ -65,7 +86,7 @@ import { getImage } from '@/utils/comm';
import { queryLevel, saveLevel } from '@/api/rule-engine/config'; import { queryLevel, saveLevel } from '@/api/rule-engine/config';
import { LevelItem } from './typing'; import { LevelItem } from './typing';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import Io from './Io/index.vue' import Io from './Io/index.vue';
const list = ref([ const list = ref([
{ {
key: 'config', key: 'config',
@ -77,7 +98,7 @@ const list = ref([
}, },
]); ]);
let levels = ref<LevelItem[]>([]); let levels = ref<LevelItem[]>([]);
let tab = ref<'io'|'config'|string>('config'); let tab = ref<'io' | 'config' | string>('config');
const getAlarmLevel = () => { const getAlarmLevel = () => {
queryLevel().then((res: any) => { queryLevel().then((res: any) => {
if (res.status == 200) { if (res.status == 200) {
@ -133,9 +154,10 @@ const onTabChange = (e: string) => {
font-size: 14px; font-size: 14px;
background-color: #fff; background-color: #fff;
h1 { h1 {
margin: 16px 0; margin: 16px 0;
color: rgba(#000, 0.85); color: rgba(#000, 0.85);
font-weight: bold; font-weight: bold;
font-size: 14px;} font-size: 14px;
}
} }
</style> </style>

View File

@ -1,67 +1,74 @@
<template> <template>
<div> <FullPage>
<j-form layout="vertical" :rules="rule" :model="form" ref="formRef"> <div>
<j-row :gutter="24"> <j-form layout="vertical" :rules="rule" :model="form" ref="formRef">
<j-col :span="12"> <j-row :gutter="24">
<j-form-item label="名称" name="name"> <j-col :span="12">
<j-input <j-form-item label="名称" name="name">
placeholder="请输入名称" <j-input
v-model:value="form.name" placeholder="请输入名称"
></j-input> </j-form-item v-model:value="form.name"
></j-col> ></j-input> </j-form-item
<j-col :span="12"> ></j-col>
<j-form-item label="类型" name="targetType"> <j-col :span="12">
<j-select <j-form-item label="类型" name="targetType">
:options="options" <j-select
v-model:value="form.targetType" :options="options"
:disabled="selectDisable" v-model:value="form.targetType"
></j-select> :disabled="selectDisable"
</j-form-item> ></j-select>
</j-col> </j-form-item>
</j-row> </j-col>
<j-form-item label="级别" name="level"> </j-row>
<j-radio-group v-model:value="form.level" class="levelSelect"> <j-form-item label="级别" name="level">
<j-radio-button <j-radio-group
v-for="(item, index) in levelOption" v-model:value="form.level"
:key="index" class="levelSelect"
:value="item.value"
> >
<div <j-radio-button
style=" v-for="(item, index) in levelOption"
text-align: center; :key="index"
margin-top: 10px; :value="item.value"
font-size: 15px;
width: 90%;
"
> >
<img <div
:src="getImage(`/alarm/alarm${index + 1}.png`)" style="
style="height: 40px" text-align: center;
alt="" margin-top: 10px;
/>{{ item.label }} font-size: 15px;
</div> width: 90%;
</j-radio-button> "
</j-radio-group> >
</j-form-item> <img
<j-form-item label="说明" name="description"> :src="
<j-textarea getImage(`/alarm/alarm${index + 1}.png`)
v-model:value="form.description" "
showCount style="height: 40px"
:maxlength="200" alt=""
></j-textarea> />{{ item.label }}
</j-form-item> </div>
<PermissionButton </j-radio-button>
type="primary" </j-radio-group>
:loading="loading" </j-form-item>
@click="handleSave" <j-form-item label="说明" name="description">
:hasPermission="[ <j-textarea
'rule-engine/Alarm/Configuration:add', v-model:value="form.description"
'rule-engine/Alarm/Configuration:update', showCount
]" :maxlength="200"
>保存</PermissionButton ></j-textarea>
> </j-form-item>
</j-form> <PermissionButton
</div> type="primary"
:loading="loading"
@click="handleSave"
:hasPermission="[
'rule-engine/Alarm/Configuration:add',
'rule-engine/Alarm/Configuration:update',
]"
>保存</PermissionButton
>
</j-form>
</div>
</FullPage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,86 +1,90 @@
<template> <template>
<JProTable <FullPage>
model="CARD" <JProTable
:request="query" model="CARD"
:defaultParams="{ :request="query"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
terms, sorts: [{ name: 'createTime', order: 'desc' }],
}" terms,
ref="actionRef" }"
> ref="actionRef"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
type="primary"
@click="showModal"
hasPermission="rule-engine/Alarm/Configuration:add"
>
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
started: 'processing',
disable: 'error',
}"
>
<template #type>
<span
><img
:height="16"
:src="typeMap.get(slotProps.triggerType)?.icon"
style="margin-right: 5px"
/>{{ typeMap.get(slotProps.triggerType)?.text }}</span
>
</template>
<template #img>
<img :src="typeMap.get(slotProps.triggerType)?.img" />
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<Ellipsis :lineClamp="2">
<div class="subTitle">
说明{{
slotProps?.description ||
typeMap.get(slotProps.triggerType)?.tip
}}
</div>
</Ellipsis>
</template>
<template #actions="item">
<PermissionButton <PermissionButton
:disabled="item.disabled" type="primary"
:popConfirm="item.popConfirm" @click="showModal"
:tooltip="{ hasPermission="rule-engine/Alarm/Configuration:add"
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'rule-engine/Scene:' + item.key"
> >
<AIcon <template #icon><AIcon type="PlusOutlined" /></template>
type="DeleteOutlined" 新增
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton> </PermissionButton>
</template> </j-space>
</CardBox> </template>
</template> <template #card="slotProps">
</JProTable> <CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
started: 'processing',
disable: 'error',
}"
>
<template #type>
<span
><img
:height="16"
:src="typeMap.get(slotProps.triggerType)?.icon"
style="margin-right: 5px"
/>{{
typeMap.get(slotProps.triggerType)?.text
}}</span
>
</template>
<template #img>
<img :src="typeMap.get(slotProps.triggerType)?.img" />
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
<Ellipsis :lineClamp="2">
<div class="subTitle">
说明{{
slotProps?.description ||
typeMap.get(slotProps.triggerType)?.tip
}}
</div>
</Ellipsis>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'rule-engine/Scene:' + item.key"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
</JProTable>
</FullPage>
<Save <Save
:id="id" :id="id"
:type="configurationData.current?.targetType" :type="configurationData.current?.targetType"

View File

@ -6,172 +6,182 @@
target="device-instance" target="device-instance"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
:columns="columns" <JProTable
:request="queryList" :columns="columns"
:gridColumn="3" :request="queryList"
:gridColumns="[1, 2, 3]" :gridColumn="3"
ref="tableRef" :gridColumns="[1, 2, 3]"
:defaultParams="{ ref="tableRef"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
}" sorts: [{ name: 'createTime', order: 'desc' }],
:params="params" }"
> :params="params"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
type="primary"
@click="add"
hasPermission="rule-engine/Alarm/Configuration:add"
>
<template #icon
><AIcon type="PlusOutlined"
/></template>
新增
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
@click="
() => {
menuStory.jumpPage(
'rule-engine/Alarm/Configuration/Save',
{},
{ id: slotProps.id },
);
}
"
>
<template #img>
<slot name="img">
<img
:src="getImage('/alarm/alarm-config.png')"
/>
</slot>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-weight: 600; font-size: 16px">
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row>
<j-col :span="12">
<div class="content-des-title">
关联场景联动
</div>
<Ellipsis
><div>
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
</div></Ellipsis
>
</j-col>
<j-col :span="12">
<div class="content-des-title">
告警级别
</div>
<div>
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level }}
</div>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton <PermissionButton
:disabled="item.disabled" type="primary"
:popConfirm="item.popConfirm" @click="add"
:tooltip="{ ...item.tooltip }" hasPermission="rule-engine/Alarm/Configuration:add"
@click="item.onClick"
:hasPermission="
'rule-engine/Alarm/Configuration:' +
item.key
"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #targetType="slotProps">
<span>{{ map[slotProps.targetType] }}</span>
</template>
<template #level="slotProps">
<j-tooltip
placement="topLeft"
:title="(Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level"
>
<div class="ellipsis">
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level }}
</div>
</j-tooltip>
</template>
<template #sceneId="slotProps">
<span
>{{(slotProps?.scene || []).map((item: any) => item?.name).join(',') || ''}}</span
>
</template>
<template #state="slotProps">
<BadgeStatus
:text="
slotProps.state?.value === 'enabled'
? '正常'
: '禁用'
"
:status="slotProps.state?.value"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="
'rule-engine/Alarm/Configuration:' + i.key
"
:danger="i.key === 'delete'"
> >
<template #icon <template #icon
><AIcon :type="i.icon" ><AIcon type="PlusOutlined"
/></template> /></template>
新增
</PermissionButton> </PermissionButton>
</template> </j-space>
</j-space> </template>
</template> <template #card="slotProps">
</JProTable> <CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
@click="
() => {
menuStory.jumpPage(
'rule-engine/Alarm/Configuration/Save',
{},
{ id: slotProps.id },
);
}
"
>
<template #img>
<slot name="img">
<img
:src="
getImage('/alarm/alarm-config.png')
"
/>
</slot>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span
style="
font-weight: 600;
font-size: 16px;
"
>
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row>
<j-col :span="12">
<div class="content-des-title">
关联场景联动
</div>
<Ellipsis
><div>
{{ (slotProps?.scene || []).map((item: any) => item?.name).join(',') || '' }}
</div></Ellipsis
>
</j-col>
<j-col :span="12">
<div class="content-des-title">
告警级别
</div>
<div>
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level }}
</div>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{ ...item.tooltip }"
@click="item.onClick"
:hasPermission="
'rule-engine/Alarm/Configuration:' +
item.key
"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #targetType="slotProps">
<span>{{ map[slotProps.targetType] }}</span>
</template>
<template #level="slotProps">
<j-tooltip
placement="topLeft"
:title="(Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level"
>
<div class="ellipsis">
{{ (Store.get('default-level') || []).find((item: any) => item?.level === slotProps.level)?.title ||
slotProps.level }}
</div>
</j-tooltip>
</template>
<template #sceneId="slotProps">
<span
>{{(slotProps?.scene || []).map((item: any) => item?.name).join(',') || ''}}</span
>
</template>
<template #state="slotProps">
<BadgeStatus
:text="
slotProps.state?.value === 'enabled'
? '正常'
: '禁用'
"
:status="slotProps.state?.value"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="
'rule-engine/Alarm/Configuration:' +
i.key
"
:danger="i.key === 'delete'"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
</div> </div>
</page-container> </page-container>
</template> </template>

View File

@ -5,48 +5,57 @@
target="alarm-log-detail" target="alarm-log-detail"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
:columns="columns" <JProTable
model="TABLE" :columns="columns"
:request="queryList" model="TABLE"
:params="params" :request="queryList"
:defaultParams="{ :params="params"
terms, :defaultParams="{
sorts: [{ name: 'alarmTime', order: 'desc' }], terms,
}" sorts: [{ name: 'alarmTime', order: 'desc' }],
> }"
<template #alarmTime="slotProps">{{ >
dayjs(slotProps.alarmTime).format('YYYY-MM-DD HH:mm:ss') <template #alarmTime="slotProps">{{
}}</template> dayjs(slotProps.alarmTime).format('YYYY-MM-DD HH:mm:ss')
<template #action="slotProps"> }}</template>
<j-space <template #action="slotProps">
><template <j-space
v-for="i in getActions(slotProps, 'table')" ><template
:key="i.key" v-for="i in getActions(slotProps, 'table')"
> :key="i.key"
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
> >
<template #icon><AIcon :type="i.icon"/></template> <PermissionButton
</PermissionButton> :disabled="i.disabled"
</template> :popConfirm="i.popConfirm"
</j-space> :tooltip="{
</template> ...i.tooltip,
</JProTable> }"
<Info v-if="visiable" :data="current" @close="close" :description="description"/> @click="i.onClick"
type="link"
style="padding: 0px"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<Info
v-if="visiable"
:data="current"
@close="close"
:description="description"
/>
</page-container> </page-container>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { detail, queryHistoryList } from '@/api/rule-engine/log'; import { detail, queryHistoryList } from '@/api/rule-engine/log';
import { detail as configurationDetail} from '@/api/rule-engine/configuration' import { detail as configurationDetail } from '@/api/rule-engine/configuration';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { ActionsType } from '@/components/Table/index.vue'; import type { ActionsType } from '@/components/Table/index.vue';
@ -54,10 +63,10 @@ import { message } from 'jetlinks-ui-components';
import { useAlarmStore } from '@/store/alarm'; import { useAlarmStore } from '@/store/alarm';
import Info from './info.vue'; import Info from './info.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import {useRouterParams} from "@/utils/hooks/useParams"; import { useRouterParams } from '@/utils/hooks/useParams';
const route = useRoute(); const route = useRoute();
const id = route.params?.id; const id = route.params?.id;
const { params: routerParams } = useRouterParams() const { params: routerParams } = useRouterParams();
let visiable = ref(false); let visiable = ref(false);
let description = ref<string>(); let description = ref<string>();
const columns = [ const columns = [
@ -170,11 +179,11 @@ watchEffect(async () => {
key: 'targetName', key: 'targetName',
}); });
} }
configurationDetail(res.result?.alarmConfigId).then((res:any)=>{ configurationDetail(res.result?.alarmConfigId).then((res: any) => {
if(res.status === 200){ if (res.status === 200) {
description.value = res.result?.description; description.value = res.result?.description;
} }
}) });
} }
}); });
const handleSearch = (_params: any) => { const handleSearch = (_params: any) => {
@ -185,15 +194,15 @@ const handleSearch = (_params: any) => {
* 关闭模态弹窗 * 关闭模态弹窗
*/ */
const close = () => { const close = () => {
visiable.value = false visiable.value = false;
} };
watchEffect(()=>{ watchEffect(() => {
current.value = details.value; current.value = details.value;
if(routerParams.value.detail && details.value){ if (routerParams.value.detail && details.value) {
visiable.value = true; visiable.value = true;
} }
}) });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
</style> </style>

View File

@ -5,39 +5,43 @@
target="bind-channel" target="bind-channel"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
model="TABLE" <JProTable
:columns="columns" model="TABLE"
:defaultParams="{ :columns="columns"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
terms, sorts: [{ name: 'createTime', order: 'desc' }],
}" terms,
:request="queryHandleHistory" }"
:params="params" :request="queryHandleHistory"
> :params="params"
<template #headerTitle> >
<h3>记录列表</h3> <template #headerTitle>
</template> <h3>记录列表</h3>
<template #handleTime="slotsProps"> </template>
<span> <template #handleTime="slotsProps">
{{ <span>
dayjs(slotsProps.handleTime).format( {{
'YYYY-MM-DD HH:mm:ss', dayjs(slotsProps.handleTime).format(
) 'YYYY-MM-DD HH:mm:ss',
}} )
</span> }}
</template> </span>
<template #handleType="slotProps"> </template>
<span>{{ slotProps.handleType.text }}</span> <template #handleType="slotProps">
</template> <span>{{ slotProps.handleType.text }}</span>
<template #alarmTime="slotProps"> </template>
<span> <template #alarmTime="slotProps">
{{ <span>
dayjs(slotProps.alarmTime).format('YYYY-MM-DD HH:mm:ss') {{
}} dayjs(slotProps.alarmTime).format(
</span> 'YYYY-MM-DD HH:mm:ss',
</template> )
</JProTable> }}
</span>
</template>
</JProTable>
</FullPage>
</page-container> </page-container>
</template> </template>

View File

@ -24,113 +24,118 @@
v-if="props.type === 'org'" v-if="props.type === 'org'"
@search="search" @search="search"
/> />
<JProTable <FullPage>
:columns="columns" <JProTable
:request="handleSearch" :columns="columns"
:params="params" :request="handleSearch"
:gridColumns="[1, 1, 2]" :params="params"
:gridColumn="2" :gridColumns="[1, 1, 2]"
model="CARD" :gridColumn="2"
ref="tableRef" model="CARD"
> ref="tableRef"
<template #card="slotProps"> >
<CardBox <template #card="slotProps">
:value="slotProps" <CardBox
v-bind="slotProps" :value="slotProps"
:actions="getActions(slotProps, 'card')" v-bind="slotProps"
:statusText=" :actions="getActions(slotProps, 'card')"
data.defaultLevel.find( :statusText="
(i) => i.level === slotProps.level, data.defaultLevel.find(
)?.title || slotProps.level (i) => i.level === slotProps.level,
" )?.title || slotProps.level
:status="slotProps.level" "
:statusNames="{ :status="slotProps.level"
1: 'level1', :statusNames="{
2: 'level2', 1: 'level1',
3: 'level3', 2: 'level2',
4: 'level4', 3: 'level3',
5: 'level5', 4: 'level4',
}" 5: 'level5',
> }"
<template #img> >
<img :src="imgMap.get(slotProps.targetType)" alt="" /> <template #img>
</template> <img
<template #content> :src="imgMap.get(slotProps.targetType)"
<Ellipsis style="width: calc(100% - 100px)"> alt=""
<span style="font-weight: 500"> />
{{ slotProps.alarmName }} </template>
</span> <template #content>
</Ellipsis> <Ellipsis style="width: calc(100% - 100px)">
<j-row :gutter="24"> <span style="font-weight: 500">
<j-col :span="8" class="content-left"> {{ slotProps.alarmName }}
<div class="content-left-title">
{{ titleMap.get(slotProps.targetType) }}
</div>
<Ellipsis
><div>
{{ slotProps?.targetName }}
</div></Ellipsis
>
</j-col>
<j-col :span="8">
<div class="content-right-title">
最近告警时间
</div>
<Ellipsis
><div>
{{
dayjs(slotProps?.alarmTime).format(
'YYYY-MM-DD HH:mm:ss',
)
}}
</div></Ellipsis
>
</j-col>
<j-col :span="8">
<div class="content-right-title">状态</div>
<BadgeStatus
:status="slotProps.state.value"
:statusName="{
warning: 'warning',
normal: 'default',
}"
>
</BadgeStatus
><span
:style="
slotProps.state.value === 'warning'
? 'color: #E50012'
: 'color:black'
"
>
{{ slotProps.state.text }}
</span> </span>
</j-col> </Ellipsis>
</j-row> <j-row :gutter="24">
</template> <j-col :span="8" class="content-left">
<template #actions="item"> <div class="content-left-title">
<PermissionButton {{ titleMap.get(slotProps.targetType) }}
:disabled=" </div>
item.key === 'solve' && <Ellipsis
slotProps.state.value === 'normal' ><div>
" {{ slotProps?.targetName }}
:tooltip="{ </div></Ellipsis
...item.tooltip, >
}" </j-col>
@click="item.onClick" <j-col :span="8">
:hasPermission=" <div class="content-right-title">
item.key == 'solve' 最近告警时间
? 'rule-engine/Alarm/Log:action' </div>
: 'rule-engine/Alarm/Log:view' <Ellipsis
" ><div>
> {{
<AIcon :type="item.icon" /> dayjs(
<span>{{ item?.text }}</span> slotProps?.alarmTime,
</PermissionButton> ).format('YYYY-MM-DD HH:mm:ss')
</template> }}
</CardBox> </div></Ellipsis
</template> >
</JProTable> </j-col>
<j-col :span="8">
<div class="content-right-title">状态</div>
<BadgeStatus
:status="slotProps.state.value"
:statusName="{
warning: 'warning',
normal: 'default',
}"
>
</BadgeStatus
><span
:style="
slotProps.state.value === 'warning'
? 'color: #E50012'
: 'color:black'
"
>
{{ slotProps.state.text }}
</span>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="
item.key === 'solve' &&
slotProps.state.value === 'normal'
"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="
item.key == 'solve'
? 'rule-engine/Alarm/Log:action'
: 'rule-engine/Alarm/Log:view'
"
>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</PermissionButton>
</template>
</CardBox>
</template>
</JProTable>
</FullPage>
<SolveComponent <SolveComponent
:data="data" :data="data"
v-if="data.solveVisible" v-if="data.solveVisible"

View File

@ -6,127 +6,138 @@
target="device-instance" target="device-instance"
@search="handleSearch" @search="handleSearch"
/> />
<JProTable <FullPage>
:columns="columns" <JProTable
:request="queryList" :columns="columns"
ref="tableRef" :request="queryList"
:defaultParams="{ ref="tableRef"
sorts: [{ name: 'createTime', order: 'desc' }], :defaultParams="{
}" sorts: [{ name: 'createTime', order: 'desc' }],
:params="params" }"
> :params="params"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
type="primary"
@click="add"
hasPermission="rule-engine/Instance:add"
>
<template #icon
><AIcon type="PlusOutlined"
/></template>
新增
</PermissionButton>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
@click="openRuleEditor"
:statusNames="{
started: 'processing',
disable: 'error',
}"
>
<template #img>
<slot name="img">
<img :src="getImage('/device-product.png')" />
</slot>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span style="font-weight: 600; font-size: 16px">
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row>
<j-col :span="12">
<Ellipsis>
<div>
{{ slotProps.description }}
</div>
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton <PermissionButton
:disabled="item.disabled" type="primary"
:popConfirm="item.popConfirm" @click="add"
:tooltip="{ hasPermission="rule-engine/Instance:add"
...item.tooltip,
}"
:hasPermission="
'rule-engine/Instance:' + item.key
"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:text="
slotProps.state?.value === 'started'
? '正常'
: '禁用'
"
:status="slotProps.state?.value"
:statusNames="{
started: 'processing',
disable: 'error',
}"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'rule-engine/Instance:' + i.key"
:danger="i.key === 'delete'"
> >
<template #icon <template #icon
><AIcon :type="i.icon" ><AIcon type="PlusOutlined"
/></template> /></template>
新增
</PermissionButton> </PermissionButton>
</template> </j-space>
</j-space> </template>
</template> <template #card="slotProps">
</JProTable> <CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
@click="openRuleEditor"
:statusNames="{
started: 'processing',
disable: 'error',
}"
>
<template #img>
<slot name="img">
<img
:src="getImage('/device-product.png')"
/>
</slot>
</template>
<template #content>
<Ellipsis style="width: calc(100% - 100px)">
<span
style="
font-weight: 600;
font-size: 16px;
"
>
{{ slotProps.name }}
</span>
</Ellipsis>
<j-row>
<j-col :span="12">
<Ellipsis>
<div>
{{ slotProps.description }}
</div>
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
:hasPermission="
'rule-engine/Instance:' + item.key
"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
<template #state="slotProps">
<BadgeStatus
:text="
slotProps.state?.value === 'started'
? '正常'
: '禁用'
"
:status="slotProps.state?.value"
:statusNames="{
started: 'processing',
disable: 'error',
}"
/>
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="
'rule-engine/Instance:' + i.key
"
:danger="i.key === 'delete'"
>
<template #icon
><AIcon :type="i.icon"
/></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
<!-- 新增编辑 --> <!-- 新增编辑 -->
<Save <Save
:data="current" :data="current"
@ -151,11 +162,11 @@ import { getImage } from '@/utils/comm';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import Save from './Save/index.vue'; import Save from './Save/index.vue';
import { SystemConst } from '@/utils/consts'; import { SystemConst } from '@/utils/consts';
import {useRouterParams} from "@/utils/hooks/useParams"; import { useRouterParams } from '@/utils/hooks/useParams';
const params = ref<Record<string, any>>({}); const params = ref<Record<string, any>>({});
let visiable = ref(false); let visiable = ref(false);
const tableRef = ref<Record<string, any>>({}); const tableRef = ref<Record<string, any>>({});
const { params: routeParams } = useRouterParams() const { params: routeParams } = useRouterParams();
const query = { const query = {
columns: [ columns: [
{ {

View File

@ -278,7 +278,7 @@ watch(
} }
} }
}, },
{ immediate: true, deep: true }, { immediate: true },
); );
watch( watch(

View File

@ -154,7 +154,7 @@ const TypeList = [
}, },
{ {
label: '按变量', label: '按变量',
value: 'variable', value: 'context',
image: getImage('/scene/device-variable.png'), image: getImage('/scene/device-variable.png'),
tip: '选择设备ID为上游变量值的设备', tip: '选择设备ID为上游变量值的设备',
}, },
@ -236,7 +236,7 @@ const filterType = async (newVal: any) => {
!props.parallel && !props.parallel &&
props.name !== 0 props.name !== 0
) { ) {
const array = TypeList.filter((item) => item.value === 'variable'); const array = TypeList.filter((item) => item.value === 'context');
_list.push(...array); _list.push(...array);
} }
list.value = _list; list.value = _list;
@ -246,7 +246,7 @@ const filterType = async (newVal: any) => {
!props.parallel && !props.parallel &&
props.name !== 0 props.name !== 0
) { ) {
const array = TypeList.filter((item) => item.value === 'variable'); const array = TypeList.filter((item) => item.value === 'context');
_list.push(...array); _list.push(...array);
} }
list.value = _list; list.value = _list;
@ -287,7 +287,12 @@ const onTagChange = (val: any[], arr: any[]) => {
modelRef.source = 'fixed'; modelRef.source = 'fixed';
} }
const tagName = arr.map((i, _index) => { const tagName = arr.map((i, _index) => {
const _type = (_index !== 0 && _index !== (arr || []).length && i.type) ? (i.type === 'and' ? '并且' : '或者') : ''; const _type =
_index !== 0 && _index !== (arr || []).length && i.type
? i.type === 'and'
? '并且'
: '或者'
: '';
return `${_type}${i.name}${i.value}`; return `${_type}${i.name}${i.value}`;
}); });
emits('save', unref(modelRef), { tagName: tagName.join('') }); emits('save', unref(modelRef), { tagName: tagName.join('') });
@ -295,6 +300,8 @@ const onTagChange = (val: any[], arr: any[]) => {
const onVariableChange = (val: any, node: any) => { const onVariableChange = (val: any, node: any) => {
modelRef.deviceId = val; modelRef.deviceId = val;
modelRef.source = 'upper';
modelRef.upperKey = val;
modelRef.selectorValues = [{ value: val, name: node.description }] as any; modelRef.selectorValues = [{ value: val, name: node.description }] as any;
emits('save', unref(modelRef), { name: node.description }); emits('save', unref(modelRef), { name: node.description });
}; };
@ -333,7 +340,7 @@ watch(
return item.children.find((i: any) => i.id === param); return item.children.find((i: any) => i.id === param);
}); });
if (isVariable) { if (isVariable) {
modelRef.selector = 'variable'; modelRef.selector = 'context';
} }
} }
}, },

View File

@ -181,6 +181,7 @@ const onProductChange = (_val: any, bol: boolean) => {
JSON.parse(_val.metadata || '{}'), JSON.parse(_val.metadata || '{}'),
DeviceModel?.message, DeviceModel?.message,
); );
console.log(flag)
if (!flag) { if (!flag) {
DeviceModel.message = { DeviceModel.message = {
messageType: 'INVOKE_FUNCTION', messageType: 'INVOKE_FUNCTION',

View File

@ -6,166 +6,176 @@
target="category" target="category"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
/> />
<FullPage>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getApplyList_api" :request="getApplyList_api"
:defaultParams="{ :defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
:params="queryParams" :params="queryParams"
:gridColumn="3" :gridColumn="3"
> >
<template #headerTitle> <template #headerTitle>
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<PermissionButton <PermissionButton
:hasPermission="`${permission}:add`" :hasPermission="`${permission}:add`"
type="primary" type="primary"
@click="() => table.toSave()" @click="() => table.toSave()"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
<p style="margin: 0 0 0 30px; color: #0000008c">
<AIcon
type="ExclamationCircleOutlined"
style="margin-right: 12px"
/>
应用管理将多个应用系统的登录简化为一次登录实现多处访问集中管控的业务场景
</p>
</div>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="table.getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
enabled: 'success',
disabled: 'error',
}"
>
<template #img>
<slot name="img">
<img :src="getImage('/apply.png')" />
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
<Ellipsis>
{{ slotProps.name }}
</Ellipsis>
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
类型
</div>
<div>
{{
table.getTypeLabel(
slotProps.provider,
)
}}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
说明
</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
> >
<j-dropdown <AIcon type="PlusOutlined" />新增
placement="bottomRight" </PermissionButton>
v-if="item.key === 'others'" <p style="margin: 0 0 0 30px; color: #0000008c">
> <AIcon
<j-button> type="ExclamationCircleOutlined"
<AIcon :type="item.icon" /> style="margin-right: 12px"
<span>{{ item.text }}</span> />
</j-button> 应用管理将多个应用系统的登录简化为一次登录实现多处访问集中管控的业务场景
<template #overlay> </p>
<j-menu> </div>
<j-menu-item </template>
v-for="(o, i) in item.children" <template #card="slotProps">
:key="i" <CardBox
> :value="slotProps"
<j-button :actions="table.getActions(slotProps, 'card')"
type="link" v-bind="slotProps"
@click="o.onClick" :status="slotProps.state?.value"
> :statusText="slotProps.state?.text"
<AIcon :type="o.icon" /> :statusNames="{
<span>{{ o.text }}</span> enabled: 'success',
</j-button> disabled: 'error',
</j-menu-item> }"
</j-menu>
</template>
</j-dropdown>
<PermissionButton
v-else
:hasPermission="item.permission"
:tooltip="item.tooltip"
:pop-confirm="item.popConfirm"
@click="item.onClick"
:disabled="item.disabled"
>
<AIcon :type="item.icon" />
<span v-if="item.key !== 'delete'">{{
item.text
}}</span>
</PermissionButton>
</j-tooltip>
</template>
<template #mark>
<AIcon
type="EyeOutlined"
style="font-size: 24px"
@click="() => table.toSave(slotProps.id, true)"
/>
</template>
</CardBox>
</template>
<template #provider="slotProps">
{{ table.getTypeLabel(slotProps.provider) }}
</template>
<template #status="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
enabled: 'success',
disabled: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(slotProps, 'table')"
:hasPermission="i.permission"
type="link"
:tooltip="i.tooltip"
:pop-confirm="i.popConfirm"
@click="i.onClick"
:disabled="i.disabled"
> >
<AIcon :type="i.icon" /> <template #img>
</PermissionButton> <slot name="img">
</j-space> <img :src="getImage('/apply.png')" />
</template> </slot>
</j-pro-table> </template>
<template #content>
<h3 class="card-item-content-title">
<Ellipsis>
{{ slotProps.name }}
</Ellipsis>
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
类型
</div>
<div>
{{
table.getTypeLabel(
slotProps.provider,
)
}}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
说明
</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<j-dropdown
placement="bottomRight"
v-if="item.key === 'others'"
>
<j-button>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</j-button>
<template #overlay>
<j-menu>
<j-menu-item
v-for="(
o, i
) in item.children"
:key="i"
>
<j-button
type="link"
@click="o.onClick"
>
<AIcon :type="o.icon" />
<span>{{
o.text
}}</span>
</j-button>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
<PermissionButton
v-else
:hasPermission="item.permission"
:tooltip="item.tooltip"
:pop-confirm="item.popConfirm"
@click="item.onClick"
:disabled="item.disabled"
>
<AIcon :type="item.icon" />
<span v-if="item.key !== 'delete'">{{
item.text
}}</span>
</PermissionButton>
</j-tooltip>
</template>
<template #mark>
<AIcon
type="EyeOutlined"
style="font-size: 24px"
@click="
() => table.toSave(slotProps.id, true)
"
/>
</template>
</CardBox>
</template>
<template #provider="slotProps">
{{ table.getTypeLabel(slotProps.provider) }}
</template>
<template #status="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
enabled: 'success',
disabled: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(
slotProps,
'table',
)"
:hasPermission="i.permission"
type="link"
:tooltip="i.tooltip"
:pop-confirm="i.popConfirm"
@click="i.onClick"
:disabled="i.disabled"
>
<AIcon :type="i.icon" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
</div> </div>
<div class="dialogs"> <div class="dialogs">
<MenuDialog <MenuDialog

View File

@ -1,285 +1,313 @@
<template> <template>
<page-container> <page-container>
<div class="basis-container"> <FullPage>
<j-form <div class="basis-container">
layout="vertical" <j-form
ref="formRef" layout="vertical"
:rules="rulesFrom" ref="formRef"
:model="formValue" :rules="rulesFrom"
> :model="formValue"
<j-row :span="24" :gutter="24"> >
<j-col :span="10"> <j-row :span="24" :gutter="24">
<j-form-item label="系统名称" name="title"> <j-col :span="10">
<j-input <j-form-item label="系统名称" name="title">
v-model:value="formValue.title" <j-input
placeholder="请输入系统名称" v-model:value="formValue.title"
/> placeholder="请输入系统名称"
</j-form-item> />
<j-form-item label="主题色" name="headerTheme"> </j-form-item>
<j-select v-model:value="formValue.headerTheme"> <j-form-item label="主题色" name="headerTheme">
<j-select-option value="light"> <j-select v-model:value="formValue.headerTheme">
白色 <j-select-option value="light">
</j-select-option> 白色
<j-select-option value="dark"> </j-select-option>
黑色 <j-select-option value="dark">
</j-select-option> 黑色
</j-select> </j-select-option>
</j-form-item> </j-select>
<j-form-item> </j-form-item>
<template #label> <j-form-item>
<span>高德API Key</span> <template #label>
<j-tooltip <span>高德API Key</span>
title="配置后平台可调用高德地图GIS服务" <j-tooltip
> title="配置后平台可调用高德地图GIS服务"
<img >
class="img-style" <img
:src="getImage('/init-home/mark.png')" class="img-style"
/> :src="
</j-tooltip> getImage('/init-home/mark.png')
</template> "
<j-input />
v-model:value="formValue.apiKey" </j-tooltip>
placeholder="请输入高德API Key" </template>
/> <j-input
</j-form-item> v-model:value="formValue.apiKey"
<j-form-item name="base-path"> placeholder="请输入高德API Key"
<template #label> />
<span>base-path</span> </j-form-item>
<j-tooltip title="系统后台访问的url"> <j-form-item name="base-path">
<img <template #label>
class="img-style" <span>base-path</span>
:src="getImage('/init-home/mark.png')" <j-tooltip title="系统后台访问的url">
/> <img
</j-tooltip> class="img-style"
</template> :src="
<j-input getImage('/init-home/mark.png')
v-model:value="formValue['base-path']" "
placeholder="请输入高德API Key" />
/> </j-tooltip>
</j-form-item> </template>
<j-row :gutter="24" :span="24"> <j-input
<j-col> v-model:value="formValue['base-path']"
<j-form-item label="系统logo"> placeholder="请输入高德API Key"
<div class="upload-image-warp-logo"> />
<div class="upload-image-border-logo"> </j-form-item>
<j-upload <j-row :gutter="24" :span="24">
name="file" <j-col>
:action="action" <j-form-item label="系统logo">
:headers="headers" <div class="upload-image-warp-logo">
:showUploadList="false" <div
:beforeUpload=" class="upload-image-border-logo"
uploader.beforeLogoUpload
"
@change="
uploader.handleChangeLogo
"
:accept="uploader.imageTypes"
> >
<div <j-upload
class="upload-image-content-logo" name="file"
:action="action"
:headers="headers"
:showUploadList="false"
:beforeUpload="
uploader.beforeLogoUpload
"
@change="
uploader.handleChangeLogo
"
:accept="
uploader.imageTypes
"
> >
<div <div
class="loading-logo" class="upload-image-content-logo"
v-if="form.logoLoading" >
<div
class="loading-logo"
v-if="
form.logoLoading
"
>
<AIcon
type="LoadingOutlined"
/>
</div>
<div
class="upload-image"
style="height: 100%"
v-if="
formValue.logo
"
:style="
formValue.logo
? `background-image: url(${formValue.logo});`
: ''
"
></div>
<div
v-if="
formValue.logo
"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<AIcon
:type="
form.logoLoading
? 'LoadingOutlined'
: 'PlusOutlined'
"
/>
</div>
</div>
</j-upload>
<div v-if="form.logoLoading">
<div
class="upload-loading-mask"
> >
<AIcon <AIcon
type="LoadingOutlined" type="LoadingOutlined"
/> />
</div> </div>
<div
class="upload-image"
style="height: 100%"
v-if="formValue.logo"
:style="
formValue.logo
? `background-image: url(${formValue.logo});`
: ''
"
></div>
<div
v-if="formValue.logo"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<AIcon
:type="
form.logoLoading
? 'LoadingOutlined'
: 'PlusOutlined'
"
/>
</div>
</div> </div>
</j-upload> </div>
<div v-if="form.logoLoading"> </div>
<div class="upload-tips">
推荐尺寸200*200
</div>
<div class="upload-tips">
支持jpg,png,jfif,pjp,pjpeg,jpeg
</div>
</j-form-item>
</j-col>
<j-col>
<j-form-item>
<template #label>
<span>浏览器页签</span>
<j-tooltip
title="浏览器tab页中显示的图片元素"
>
<img
class="img-style"
:src="
getImage(
'/init-home/mark.png',
)
"
/>
</j-tooltip>
</template>
<div class="upload-image-warp-logo">
<div
class="upload-image-border-logo"
>
<j-upload
name="file"
:action="action"
:headers="headers"
:showUploadList="false"
:beforeUpload="
uploader.beforeIconUpload
"
@change="
uploader.changeIconUpload
"
:accept="uploader.iconTypes"
>
<div
class="upload-image-content-logo"
>
<div
v-if="
form.iconLoading
"
class="loading-icon"
>
<AIcon
type="LoadingOutlined"
/>
</div>
<div
class="upload-image-icon"
v-if="formValue.ico"
:style="
formValue.ico
? `background-image: url(${formValue.ico});`
: ''
"
></div>
<div
v-if="formValue.ico"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<div>
<AIcon
type="PlusOutlined"
/>
</div>
</div>
</div>
</j-upload>
</div>
</div>
<div class="upload-tips">
推荐尺寸64*64
</div>
<div class="upload-tips">
支持ico格式
</div>
</j-form-item>
</j-col>
</j-row>
</j-col>
<j-col :span="14">
<j-form-item label="登录背景图">
<div class="upload-image-warp-back">
<div class="upload-image-border-back">
<j-upload
name="file"
:action="action"
:headers="headers"
:beforeUpload="
uploader.beforeLogoUpload
"
:showUploadList="false"
@change="uploader.changeBackUpload"
:accept="uploader.imageTypes"
>
<div
class="upload-image-content-back"
>
<div <div
class="upload-loading-mask" v-if="form.backLoading"
class="loading-back"
> >
<AIcon <AIcon
type="LoadingOutlined" type="LoadingOutlined"
/> />
</div> </div>
</div>
</div>
</div>
<div class="upload-tips">
推荐尺寸200*200
</div>
<div class="upload-tips">
支持jpg,png,jfif,pjp,pjpeg,jpeg
</div>
</j-form-item>
</j-col>
<j-col>
<j-form-item>
<template #label>
<span>浏览器页签</span>
<j-tooltip
title="浏览器tab页中显示的图片元素"
>
<img
class="img-style"
:src="
getImage(
'/init-home/mark.png',
)
"
/>
</j-tooltip>
</template>
<div class="upload-image-warp-logo">
<div class="upload-image-border-logo">
<j-upload
name="file"
:action="action"
:headers="headers"
:showUploadList="false"
:beforeUpload="
uploader.beforeIconUpload
"
@change="
uploader.changeIconUpload
"
:accept="uploader.iconTypes"
>
<div <div
class="upload-image-content-logo" class="upload-image"
v-if="formValue.backgroud"
:style="
formValue.backgroud
? `background-image: url(${formValue.backgroud});`
: ''
"
></div>
<div
v-if="formValue.backgroud"
class="upload-image-mask"
> >
<div 点击修改
v-if="form.iconLoading" </div>
class="loading-icon" <div v-else>
> <div>
<AIcon <AIcon
type="LoadingOutlined" type="PlusOutlined"
/> />
</div> </div>
<div
class="upload-image-icon"
v-if="formValue.ico"
:style="
formValue.ico
? `background-image: url(${formValue.ico});`
: ''
"
></div>
<div
v-if="formValue.ico"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<div>
<AIcon
type="PlusOutlined"
/>
</div>
</div>
</div> </div>
</j-upload> </div>
</div> </j-upload>
</div> </div>
<div class="upload-tips">推荐尺寸64*64</div>
<div class="upload-tips">支持ico格式</div>
</j-form-item>
</j-col>
</j-row>
</j-col>
<j-col :span="14">
<j-form-item label="登录背景图">
<div class="upload-image-warp-back">
<div class="upload-image-border-back">
<j-upload
name="file"
:action="action"
:headers="headers"
:beforeUpload="
uploader.beforeLogoUpload
"
:showUploadList="false"
@change="uploader.changeBackUpload"
:accept="uploader.imageTypes"
>
<div class="upload-image-content-back">
<div
v-if="form.backLoading"
class="loading-back"
>
<AIcon type="LoadingOutlined" />
</div>
<div
class="upload-image"
v-if="formValue.backgroud"
:style="
formValue.backgroud
? `background-image: url(${formValue.backgroud});`
: ''
"
></div>
<div
v-if="formValue.backgroud"
class="upload-image-mask"
>
点击修改
</div>
<div v-else>
<div>
<AIcon
type="PlusOutlined"
/>
</div>
</div>
</div>
</j-upload>
</div> </div>
</div> <div class="upload-tips">
<div class="upload-tips"> 支持4M以内的图片:
支持4M以内的图片: 支持jpg,png,jfif,pjp,pjpeg,jpeg
支持jpg,png,jfif,pjp,pjpeg,jpeg </div>
</div> <div class="upload-tips">建议尺寸1400x1080</div>
<div class="upload-tips">建议尺寸1400x1080</div> </j-form-item>
</j-form-item> </j-col>
</j-col> </j-row>
</j-row> </j-form>
</j-form>
<j-button <j-button
type="primary" type="primary"
@click="form.clickSave" @click="form.clickSave"
:disabled=" :disabled="
form.saveLoading || form.saveLoading ||
form.logoLoading || form.logoLoading ||
form.iconLoading || form.iconLoading ||
form.backLoading form.backLoading
" "
> >
保存 保存
</j-button> </j-button>
</div> </div>
</FullPage>
</page-container> </page-container>
</template> </template>

View File

@ -6,133 +6,134 @@
target="category" target="category"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
/> />
<FullPage>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getDataSourceList_api" :request="getDataSourceList_api"
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ :defaultParams="{
pageSize: 10, pageSize: 10,
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
:pagination="{ :pagination="{
showSizeChanger: true, showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
}" }"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton
type="primary"
:hasPermission="`${permission}:add`"
@click="table.openDialog({})"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
enabled: 'processing',
disabled: 'error',
}"
>
</BadgeStatus>
</template>
<template #typeId="slotProps">
{{
(table.typeOptions.value.length &&
table.getTypeLabel(slotProps.typeId)) ||
''
}}
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton <PermissionButton
:hasPermission="`${permission}:update`" type="primary"
type="link" :hasPermission="`${permission}:add`"
:tooltip="{ @click="table.openDialog({})"
title: '编辑',
}"
@click="table.openDialog(slotProps)"
> >
<AIcon type="EditOutlined" /> <AIcon type="PlusOutlined" />新增
</PermissionButton> </PermissionButton>
<PermissionButton </template>
:hasPermission="`${permission}:manage`" <template #state="slotProps">
type="link" <BadgeStatus
:tooltip="{ :status="slotProps.state?.value"
title: :text="slotProps.state?.text"
slotProps?.typeId === 'rabbitmq' :statusNames="{
? '暂不支持管理功能' enabled: 'processing',
: table.getRowStatus(slotProps) disabled: 'error',
? '管理'
: '请先启用数据源',
}"
@click="
() =>
router.push(
`/system/DataSource/Management?id=${slotProps.id}`,
)
"
:disabled="
slotProps?.typeId === 'rabbitmq' ||
!table.getRowStatus(slotProps)
"
>
<AIcon type="icon-ziyuankuguanli" />
</PermissionButton>
<PermissionButton
:hasPermission="`${permission}:action`"
type="link"
:popConfirm="{
title: `确定要${
table.getRowStatus(slotProps)
? '禁用'
: '启用'
}`,
onConfirm: () =>
table.clickChangeStatus(slotProps),
}"
:tooltip="{
title: table.getRowStatus(slotProps)
? '禁用'
: '启用',
}" }"
> >
<AIcon </BadgeStatus>
:type=" </template>
table.getRowStatus(slotProps) <template #typeId="slotProps">
? 'StopOutlined' {{
: 'PlayCircleOutlined' (table.typeOptions.value.length &&
table.getTypeLabel(slotProps.typeId)) ||
''
}}
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
}"
@click="table.openDialog(slotProps)"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
:hasPermission="`${permission}:manage`"
type="link"
:tooltip="{
title:
slotProps?.typeId === 'rabbitmq'
? '暂不支持管理功能'
: table.getRowStatus(slotProps)
? '管理'
: '请先启用数据源',
}"
@click="
() =>
router.push(
`/system/DataSource/Management?id=${slotProps.id}`,
)
" "
/> :disabled="
<!-- <AIcon type="PlayCircleOutlined" /> --> slotProps?.typeId === 'rabbitmq' ||
</PermissionButton> !table.getRowStatus(slotProps)
"
>
<AIcon type="icon-ziyuankuguanli" />
</PermissionButton>
<PermissionButton
:hasPermission="`${permission}:action`"
type="link"
:popConfirm="{
title: `确定要${
table.getRowStatus(slotProps)
? '禁用'
: '启用'
}`,
onConfirm: () =>
table.clickChangeStatus(slotProps),
}"
:tooltip="{
title: table.getRowStatus(slotProps)
? '禁用'
: '启用',
}"
>
<AIcon
:type="
table.getRowStatus(slotProps)
? 'StopOutlined'
: 'PlayCircleOutlined'
"
/>
<!-- <AIcon type="PlayCircleOutlined" /> -->
</PermissionButton>
<PermissionButton <PermissionButton
:hasPermission="`${permission}:delete`" :hasPermission="`${permission}:delete`"
type="link" type="link"
:tooltip="{ :tooltip="{
title: table.getRowStatus(slotProps) title: table.getRowStatus(slotProps)
? '请先禁用,再删除' ? '请先禁用,再删除'
: '删除', : '删除',
}" }"
:danger="true" :danger="true"
:popConfirm="{ :popConfirm="{
title: `确认删除`, title: `确认删除`,
onConfirm: () => table.clickDel(slotProps), onConfirm: () => table.clickDel(slotProps),
}" }"
:disabled="table.getRowStatus(slotProps)" :disabled="table.getRowStatus(slotProps)"
> >
<AIcon type="DeleteOutlined" /> <AIcon type="DeleteOutlined" />
</PermissionButton> </PermissionButton>
</j-space> </j-space>
</template> </template>
</j-pro-table> </j-pro-table>
</FullPage>
<EditDialog <EditDialog
v-if="dialog.visible" v-if="dialog.visible"
@ -283,13 +284,13 @@ const table = {
// //
refresh: () => { refresh: () => {
tableRef.value.reload(); tableRef.value.reload();
dialog.visible = false dialog.visible = false;
dialog.selectItem = {} dialog.selectItem = {};
}, },
cancel: () => { cancel: () => {
dialog.visible = false dialog.visible = false;
dialog.selectItem = {} dialog.selectItem = {};
} },
}; };
table.getTypeOption(); table.getTypeOption();

View File

@ -5,170 +5,175 @@
target="category" target="category"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
/> />
<j-pro-table <FullPage>
ref="tableRef" <j-pro-table
:request="table.requestFun" ref="tableRef"
:gridColumn="2" :request="table.requestFun"
:params="queryParams" :gridColumn="2"
:rowSelection="{ :params="queryParams"
selectedRowKeys: table._selectedRowKeys.value, :rowSelection="{
onChange:(keys:string[])=>table._selectedRowKeys.value = [...keys], selectedRowKeys: table._selectedRowKeys.value,
onSelectNone: table.cancelSelect onChange:(keys:string[])=>table._selectedRowKeys.value = [...keys],
}" onSelectNone: table.cancelSelect
:columns="columns" }"
> :columns="columns"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
:hasPermission="`${permission}:assert`"
type="primary"
@click="table.clickAdd('handle')"
>
<AIcon type="PlusOutlined" />资产分配
</PermissionButton>
<j-dropdown trigger="hover">
<j-button>批量操作</j-button>
<template #overlay>
<j-menu>
<j-menu-item>
<PermissionButton
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否批量解除绑定`,
onConfirm: () =>
table.clickUnBind(),
}"
>
<AIcon
type="DisconnectOutlined"
/>
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton
:hasPermission="`${permission}:assert`"
@click="table.clickEdit()"
>
<AIcon type="EditOutlined" />批量编辑
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="[{ key: 1 }]"
v-bind="slotProps"
:active="
table._selectedRowKeys.value.includes(slotProps.id)
"
@click="table.onSelectChange"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
>
<template #img>
<slot name="img">
<img
:src="getImage('/device-product.png')"
style="cursor: pointer"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">ID</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{ slotProps.id }}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
资产权限
</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{
table.permissionList.value.length &&
table.getPermissLabel(
slotProps.permission,
)
}}
</div>
</j-col>
</j-row>
</template>
<template #actions>
<PermissionButton <PermissionButton
:hasPermission="`${permission}:assert`" :hasPermission="`${permission}:assert`"
@click="table.clickEdit(slotProps)" type="primary"
@click="table.clickAdd('handle')"
> >
<AIcon type="EditOutlined" /> <AIcon type="PlusOutlined" />资产分配
</PermissionButton> </PermissionButton>
<j-dropdown trigger="hover">
<j-button>批量操作</j-button>
<template #overlay>
<j-menu>
<j-menu-item>
<PermissionButton
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否批量解除绑定`,
onConfirm: () =>
table.clickUnBind(),
}"
>
<AIcon
type="DisconnectOutlined"
/>
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton
:hasPermission="`${permission}:assert`"
@click="table.clickEdit()"
>
<AIcon
type="EditOutlined"
/>
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</j-space>
</template>
<PermissionButton <template #card="slotProps">
:hasPermission="`${permission}:bind`" <CardBox
:popConfirm="{ :value="slotProps"
title: `是否解除绑定`, :actions="[{ key: 1 }]"
onConfirm: () => table.clickUnBind(slotProps), v-bind="slotProps"
}" :active="
> table._selectedRowKeys.value.includes(slotProps.id)
<AIcon type="DisconnectOutlined" /> "
</PermissionButton> @click="table.onSelectChange"
</template> :status="slotProps.state?.value"
</CardBox> :statusText="slotProps.state?.text"
</template> :statusNames="{
online: 'processing',
<template #permission="slotProps"> offline: 'error',
{{ notActive: 'warning',
table.permissionList.value.length && }"
table.getPermissLabel(slotProps.permission)
}}
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(slotProps, 'table')"
:hasPermission="i.permission"
type="link"
:tooltip="i?.tooltip"
:pop-confirm="i.popConfirm"
@click="i.onClick"
:disabled="i?.disabled"
> >
<AIcon :type="i.icon" /> <template #img>
</PermissionButton> <slot name="img">
</j-space> <img
</template> :src="getImage('/device-product.png')"
</j-pro-table> style="cursor: pointer"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">ID</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{ slotProps.id }}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
资产权限
</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{
table.permissionList.value.length &&
table.getPermissLabel(
slotProps.permission,
)
}}
</div>
</j-col>
</j-row>
</template>
<template #actions>
<PermissionButton
:hasPermission="`${permission}:assert`"
@click="table.clickEdit(slotProps)"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否解除绑定`,
onConfirm: () =>
table.clickUnBind(slotProps),
}"
>
<AIcon type="DisconnectOutlined" />
</PermissionButton>
</template>
</CardBox>
</template>
<template #permission="slotProps">
{{
table.permissionList.value.length &&
table.getPermissLabel(slotProps.permission)
}}
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
online: 'processing',
offline: 'error',
notActive: 'warning',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(slotProps, 'table')"
:hasPermission="i.permission"
type="link"
:tooltip="i?.tooltip"
:pop-confirm="i.popConfirm"
@click="i.onClick"
:disabled="i?.disabled"
>
<AIcon :type="i.icon" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
<div class="dialogs"> <div class="dialogs">
<AddDeviceOrProductDialog <AddDeviceOrProductDialog
@ -471,7 +476,7 @@ const table = {
}, },
clickAdd: (type?: string) => { clickAdd: (type?: string) => {
// : type = 'handle': , !type, // : type = 'handle': , !type,
departmentStore.setType(type) departmentStore.setType(type);
dialogs.addShow = true; dialogs.addShow = true;
}, },
clickEdit: (row?: any) => { clickEdit: (row?: any) => {

View File

@ -5,193 +5,199 @@
target="category" target="category"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
/> />
<j-pro-table <FullPage>
ref="tableRef" <j-pro-table
:request="table.requestFun" ref="tableRef"
:gridColumn="2" :request="table.requestFun"
:params="queryParams" :gridColumn="2"
:rowSelection="{ :params="queryParams"
selectedRowKeys: tableData._selectedRowKeys, :rowSelection="{
onChange:(keys:string[])=>tableData._selectedRowKeys = [...keys], selectedRowKeys: tableData._selectedRowKeys,
onSelectNone: table.cancelSelect onChange:(keys:string[])=>tableData._selectedRowKeys = [...keys],
}" onSelectNone: table.cancelSelect
:columns="columns" }"
> :columns="columns"
<template #headerTitle> >
<j-space> <template #headerTitle>
<PermissionButton <j-space>
:hasPermission="`${permission}:assert`" <PermissionButton
type="primary" :hasPermission="`${permission}:assert`"
@click="dialogs.addShow = true" type="primary"
> @click="dialogs.addShow = true"
<AIcon type="PlusOutlined" />资产分配
</PermissionButton>
<j-dropdown trigger="hover">
<j-button>批量操作</j-button>
<template #overlay>
<j-menu>
<j-menu-item>
<PermissionButton
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否批量解除绑定`,
onConfirm: () =>
table.clickUnBind(),
}"
>
<AIcon
type="DisconnectOutlined"
/>
</PermissionButton>
</j-menu-item>
<j-menu-item>
<PermissionButton
:hasPermission="`${permission}:assert`"
@click="() => table.clickEdit()"
>
<AIcon type="EditOutlined" />批量编辑
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="table.getActions(slotProps, 'card')"
v-bind="slotProps"
:active="tableData._selectedRowKeys.includes(slotProps.id)"
@click="table.onSelectChange"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
online: 'processing',
offline: 'error',
}"
>
<template #img>
<slot name="img">
<img
:src="getImage('/device-product.png')"
style="cursor: pointer"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">ID</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{ slotProps.id }}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
资产权限
</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{
tableData.permissionList.length &&
table.getPermissLabel(
slotProps.permission,
)
}}
</div>
</j-col>
</j-row>
</template>
<template #actions="item">
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
> >
<j-dropdown <AIcon type="PlusOutlined" />资产分配
placement="bottomRight" </PermissionButton>
v-if="item.key === 'others'" <j-dropdown trigger="hover">
> <j-button>批量操作</j-button>
<j-button> <template #overlay>
<AIcon :type="item.icon" /> <j-menu>
<span>{{ item.text }}</span> <j-menu-item>
</j-button> <PermissionButton
<template #overlay> :hasPermission="`${permission}:bind`"
<j-menu> :popConfirm="{
<j-menu-item title: `是否批量解除绑定`,
v-for="(o, i) in item.children" onConfirm: () =>
:key="i" table.clickUnBind(),
}"
> >
<j-button <AIcon
type="link" type="DisconnectOutlined"
@click="o.onClick" />
> </PermissionButton>
<AIcon :type="o.icon" /> </j-menu-item>
<span>{{ o.text }}</span> <j-menu-item>
</j-button> <PermissionButton
</j-menu-item> :hasPermission="`${permission}:assert`"
</j-menu> @click="() => table.clickEdit()"
</template> >
</j-dropdown> <AIcon
<PermissionButton type="EditOutlined"
v-else />
:hasPermission="item.permission" </PermissionButton>
:tooltip="item.tooltip" </j-menu-item>
:pop-confirm="item.popConfirm" </j-menu>
@click="item.onClick" </template>
:disabled="item.disabled" </j-dropdown>
> </j-space>
<AIcon :type="item.icon" /> </template>
<span v-if="item.key !== 'delete'">{{
item.text
}}</span>
</PermissionButton>
</j-tooltip>
</template>
</CardBox>
</template>
<template #permission="slotProps"> <template #card="slotProps">
{{ <CardBox
tableData.permissionList.length && :value="slotProps"
table.getPermissLabel(slotProps.permission) :actions="table.getActions(slotProps, 'card')"
}} v-bind="slotProps"
</template> :active="
<template #state="slotProps"> tableData._selectedRowKeys.includes(slotProps.id)
<BadgeStatus "
:status="slotProps.state.value" @click="table.onSelectChange"
:text="slotProps.state.text" :status="slotProps.state?.value"
:statusNames="{ :statusText="slotProps.state?.text"
online: 'processing', :statusNames="{
offline: 'error', online: 'processing',
}" offline: 'error',
></BadgeStatus> }"
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(slotProps, 'table')"
:hasPermission="i.permission"
type="link"
:tooltip="i?.tooltip"
:pop-confirm="i.popConfirm"
@click="i.onClick"
:disabled="i?.disabled"
> >
<AIcon :type="i.icon" /> <template #img>
</PermissionButton> <slot name="img">
</j-space> <img
</template> :src="getImage('/device-product.png')"
</j-pro-table> style="cursor: pointer"
/>
</slot>
</template>
<template #content>
<h3 class="card-item-content-title">
{{ slotProps.name }}
</h3>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">ID</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{ slotProps.id }}
</div>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
资产权限
</div>
<div
style="cursor: pointer"
class="card-item-content-value"
>
{{
tableData.permissionList.length &&
table.getPermissLabel(
slotProps.permission,
)
}}
</div>
</j-col>
</j-row>
</template>
<template #actions="item">
<j-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
<j-dropdown
placement="bottomRight"
v-if="item.key === 'others'"
>
<j-button>
<AIcon :type="item.icon" />
<span>{{ item.text }}</span>
</j-button>
<template #overlay>
<j-menu>
<j-menu-item
v-for="(o, i) in item.children"
:key="i"
>
<j-button
type="link"
@click="o.onClick"
>
<AIcon :type="o.icon" />
<span>{{ o.text }}</span>
</j-button>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
<PermissionButton
v-else
:hasPermission="item.permission"
:tooltip="item.tooltip"
:pop-confirm="item.popConfirm"
@click="item.onClick"
:disabled="item.disabled"
>
<AIcon :type="item.icon" />
<span v-if="item.key !== 'delete'">{{
item.text
}}</span>
</PermissionButton>
</j-tooltip>
</template>
</CardBox>
</template>
<template #permission="slotProps">
{{
tableData.permissionList.length &&
table.getPermissLabel(slotProps.permission)
}}
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state.value"
:text="slotProps.state.text"
:statusNames="{
online: 'processing',
offline: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(slotProps, 'table')"
:hasPermission="i.permission"
type="link"
:tooltip="i?.tooltip"
:pop-confirm="i.popConfirm"
@click="i.onClick"
:disabled="i?.disabled"
>
<AIcon :type="i.icon" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
<div class="dialogs"> <div class="dialogs">
<AddDeviceOrProductDialog <AddDeviceOrProductDialog

View File

@ -5,72 +5,74 @@
target="category" target="category"
@search="handleParams" @search="handleParams"
/> />
<j-pro-table <FullPage>
ref="tableRef" <j-pro-table
:columns="columns" ref="tableRef"
:request="table.requestFun" :columns="columns"
:params="queryParams" :request="table.requestFun"
:rowSelection="{ :params="queryParams"
selectedRowKeys: table._selectedRowKeys, :rowSelection="{
onChange: table.onSelectChange, selectedRowKeys: table._selectedRowKeys,
}" onChange: table.onSelectChange,
@cancelSelect="table.cancelSelect" }"
model="TABLE" @cancelSelect="table.cancelSelect"
:defaultParams="{ model="TABLE"
pageSize: 10, :defaultParams="{
}" pageSize: 10,
:pagination="{ }"
showSizeChanger: true, :pagination="{
pageSizeOptions: ['10', '20', '50', '100'], showSizeChanger: true,
}" pageSizeOptions: ['10', '20', '50', '100'],
> }"
<template #headerTitle> >
<PermissionButton <template #headerTitle>
type="primary" <PermissionButton
:hasPermission="`${permission}:bind-user`" type="primary"
@click="dialogVisible = true" :hasPermission="`${permission}:bind-user`"
style="margin-right: 15px" @click="dialogVisible = true"
> style="margin-right: 15px"
<AIcon type="PlusOutlined" />绑定用户 >
</PermissionButton> <AIcon type="PlusOutlined" />绑定用户
<div </PermissionButton>
style="display: inline-block; width: 12px; height: 1px" <div
></div> style="display: inline-block; width: 12px; height: 1px"
<PermissionButton ></div>
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否解除绑定`,
onConfirm: () => table.unBind(),
}"
>
<AIcon type="DisconnectOutlined" />批量解绑
</PermissionButton>
</template>
<template #status="slotProps">
<BadgeStatus
:status="slotProps.status"
:text="slotProps.status ? '正常' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton <PermissionButton
type="link"
:hasPermission="`${permission}:bind`" :hasPermission="`${permission}:bind`"
:popConfirm="{ :popConfirm="{
title: `是否解除绑定`, title: `是否解除绑定`,
onConfirm: () => table.unBind(slotProps), onConfirm: () => table.unBind(),
}" }"
> >
<AIcon type="DisconnectOutlined" /> <AIcon type="DisconnectOutlined" />批量解绑
</PermissionButton> </PermissionButton>
</j-space> </template>
</template> <template #status="slotProps">
</j-pro-table> <BadgeStatus
:status="slotProps.status"
:text="slotProps.status ? '正常' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
type="link"
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否解除绑定`,
onConfirm: () => table.unBind(slotProps),
}"
>
<AIcon type="DisconnectOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
<div class="dialogs"> <div class="dialogs">
<AddBindUserDialog <AddBindUserDialog
@ -150,8 +152,8 @@ const columns = [
const queryParams = ref({}); const queryParams = ref({});
const handleParams = (params: any) => { const handleParams = (params: any) => {
queryParams.value = params queryParams.value = params;
} };
// //
const tableRef = ref<Record<string, any>>({}); // const tableRef = ref<Record<string, any>>({}); //
@ -174,7 +176,7 @@ const table = reactive({
value: props.parentId, value: props.parentId,
}, },
], ],
type: 'and' type: 'and',
}, },
], ],
}; };

View File

@ -6,70 +6,73 @@
target="category" target="category"
@search="handleSearch" @search="handleSearch"
/> />
<FullPage>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="table.getList" :request="table.getList"
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
noPagination noPagination
v-model:expandedRowKeys="expandedRowKeys" v-model:expandedRowKeys="expandedRowKeys"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton
type="primary"
:hasPermission="`${permission}:add`"
@click="table.toDetails({})"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
<j-button
style="margin-left: 12px"
@click="router.push('/system/Menu/Setting')"
>菜单配置</j-button
>
</template>
<template #createTime="slotProps">
{{
dayjs(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss')
}}
</template>
<template #action="slotProps">
<j-space :size="16">
<j-tooltip>
<template #title>查看</template>
<j-button
style="padding: 0"
type="link"
@click="table.toDetails(slotProps)"
>
<AIcon type="SearchOutlined" />
</j-button>
</j-tooltip>
<PermissionButton <PermissionButton
type="link" type="primary"
:hasPermission="`${permission}:add`" :hasPermission="`${permission}:add`"
:tooltip="{ title: '新增子菜单' }" @click="table.toDetails({})"
@click="table.addChildren(slotProps)"
> >
<AIcon type="PlusCircleOutlined" /> <AIcon type="PlusOutlined" />新增
</PermissionButton> </PermissionButton>
<PermissionButton <j-button
type="link" style="margin-left: 12px"
:hasPermission="`${permission}:delete`" @click="router.push('/system/Menu/Setting')"
:tooltip="{ title: '删除' }" >菜单配置</j-button
:popConfirm="{
title: `是否删除该菜单`,
onConfirm: () => table.clickDel(slotProps),
}"
> >
<AIcon type="DeleteOutlined" /> </template>
</PermissionButton> <template #createTime="slotProps">
</j-space> {{
</template> dayjs(slotProps.createTime).format(
</j-pro-table> 'YYYY-MM-DD HH:mm:ss',
)
}}
</template>
<template #action="slotProps">
<j-space :size="16">
<j-tooltip>
<template #title>查看</template>
<j-button
style="padding: 0"
type="link"
@click="table.toDetails(slotProps)"
>
<AIcon type="SearchOutlined" />
</j-button>
</j-tooltip>
<PermissionButton
type="link"
:hasPermission="`${permission}:add`"
:tooltip="{ title: '新增子菜单' }"
@click="table.addChildren(slotProps)"
>
<AIcon type="PlusCircleOutlined" />
</PermissionButton>
<PermissionButton
type="link"
:hasPermission="`${permission}:delete`"
:tooltip="{ title: '删除' }"
:popConfirm="{
title: `是否删除该菜单`,
onConfirm: () => table.clickDel(slotProps),
}"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
</div> </div>
</page-container> </page-container>
</template> </template>

View File

@ -6,134 +6,136 @@
target="system-permission" target="system-permission"
@search="handleSearch" @search="handleSearch"
/> />
<FullPage>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getPermission_api" :request="getPermission_api"
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ :defaultParams="{
pageSize: 10, pageSize: 10,
sorts: [{ name: 'id', order: 'asc' }], sorts: [{ name: 'id', order: 'asc' }],
}" }"
:pagination="{ :pagination="{
showSizeChanger: true, showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
}" }"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton <PermissionButton
type="primary" type="primary"
:hasPermission="`${permission}:add`" :hasPermission="`${permission}:add`"
@click="table.openDialog(undefined)" @click="table.openDialog(undefined)"
> >
<AIcon type="PlusOutlined" />新增 <AIcon type="PlusOutlined" />新增
</PermissionButton> </PermissionButton>
<j-dropdown trigger="hover"> <j-dropdown trigger="hover">
<j-button>批量操作</j-button> <j-button>批量操作</j-button>
<template #overlay> <template #overlay>
<j-menu> <j-menu>
<j-menu-item> <j-menu-item>
<j-upload <j-upload
name="file" name="file"
action="#" action="#"
accept=".json" accept=".json"
:showUploadList="false" :showUploadList="false"
:before-upload="table.clickImport" :before-upload="table.clickImport"
:disabled=" :disabled="
!hasPermission( !hasPermission(
`${permission}:import`, `${permission}:import`,
) )
" "
>
<PermissionButton
:hasPermission="`${permission}:import`"
> >
导入 <PermissionButton
:hasPermission="`${permission}:import`"
>
导入
</PermissionButton>
</j-upload>
</j-menu-item>
<j-menu-item>
<PermissionButton
:hasPermission="`${permission}:export`"
:popConfirm="{
title: `确认导出?`,
onConfirm: () =>
table.clickExport(),
}"
>
导出
</PermissionButton> </PermissionButton>
</j-upload> </j-menu-item>
</j-menu-item> </j-menu>
<j-menu-item> </template>
<PermissionButton </j-dropdown>
:hasPermission="`${permission}:export`" </template>
:popConfirm="{ <template #status="slotProps">
title: `确认导出?`, <BadgeStatus
onConfirm: () => :status="slotProps.status"
table.clickExport(), :text="slotProps.status ? '启用' : '禁用'"
}" :statusNames="{
> 1: 'success',
导出 0: 'error',
</PermissionButton>
</j-menu-item>
</j-menu>
</template>
</j-dropdown>
</template>
<template #status="slotProps">
<BadgeStatus
:status="slotProps.status"
:text="slotProps.status ? '启用' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
}" }"
@click="table.openDialog(slotProps)" ></BadgeStatus>
> </template>
<AIcon type="EditOutlined" /> <template #action="slotProps">
</PermissionButton> <j-space :size="16">
<PermissionButton
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
}"
@click="table.openDialog(slotProps)"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton <PermissionButton
:hasPermission="`${permission}:action`" :hasPermission="`${permission}:action`"
type="link" type="link"
:popConfirm="{ :popConfirm="{
title: `确定要${ title: `确定要${
slotProps.status ? '禁用' : '启用' slotProps.status ? '禁用' : '启用'
}`, }`,
onConfirm: () => table.changeStatus(slotProps), onConfirm: () =>
}" table.changeStatus(slotProps),
:tooltip="{ }"
title: slotProps.status ? '禁用' : '启用', :tooltip="{
}" title: slotProps.status ? '禁用' : '启用',
> }"
<AIcon >
:type=" <AIcon
slotProps.status :type="
? 'StopOutlined' slotProps.status
: 'PlayCircleOutlined' ? 'StopOutlined'
" : 'PlayCircleOutlined'
/> "
</PermissionButton> />
</PermissionButton>
<PermissionButton <PermissionButton
:hasPermission="`${permission}:delete`" :hasPermission="`${permission}:delete`"
type="link" type="link"
:tooltip="{ :tooltip="{
title: slotProps.status title: slotProps.status
? '请先禁用,再删除' ? '请先禁用,再删除'
: '删除', : '删除',
}" }"
:popConfirm="{ :popConfirm="{
title: `确认删除`, title: `确认删除`,
onConfirm: () => table.clickDel(slotProps), onConfirm: () => table.clickDel(slotProps),
}" }"
:disabled="slotProps.status" :disabled="slotProps.status"
> >
<AIcon type="DeleteOutlined" /> <AIcon type="DeleteOutlined" />
</PermissionButton> </PermissionButton>
</j-space> </j-space>
</template> </template>
</j-pro-table> </j-pro-table>
</FullPage>
<EditDialog <EditDialog
v-if="dialog.visible" v-if="dialog.visible"

View File

@ -37,6 +37,9 @@ import {
} from '@/api/system/apiPage'; } from '@/api/system/apiPage';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { modeType } from '../typing'; import { modeType } from '../typing';
import { useDepartmentStore } from '@/store/department';
const department = useDepartmentStore();
const emits = defineEmits([ const emits = defineEmits([
'refresh', 'refresh',
'update:clickApi', 'update:clickApi',
@ -64,11 +67,6 @@ const columns = [
dataIndex: 'summary', dataIndex: 'summary',
key: 'summary', key: 'summary',
}, },
// {
// title: 'ID',
// dataIndex: 'id',
// key: 'id',
// },
]; ];
const rowSelection = { const rowSelection = {
// onSelect: (record: any) => { // onSelect: (record: any) => {
@ -95,7 +93,7 @@ const rowSelection = {
props.sourceKeys.includes(key), props.sourceKeys.includes(key),
); );
// , // ,
const otherSelectedKeys = props.sourceKeys.filter( const otherSelectedKeys = department.crossPageKeys.filter(
(key) => !currenTableKeys.includes(key), (key) => !currenTableKeys.includes(key),
); );
@ -103,6 +101,7 @@ const rowSelection = {
const removeKeys = oldSelectedKeys.filter((key) => !keys.includes(key)); const removeKeys = oldSelectedKeys.filter((key) => !keys.includes(key));
// //
const addKeys = keys.filter((key) => !oldSelectedKeys.includes(key)); const addKeys = keys.filter((key) => !oldSelectedKeys.includes(key));
//
emits('update:selectedRowKeys', [...otherSelectedKeys, ...keys]); emits('update:selectedRowKeys', [...otherSelectedKeys, ...keys]);
// / // /
@ -111,37 +110,21 @@ const rowSelection = {
changed[key] = props.tableData.find((f: any) => f.id === key); changed[key] = props.tableData.find((f: any) => f.id === key);
}); });
if (props.mode === 'appManger') { if (props.mode === 'appManger') {
emits('update:changedApis', changed); //
emits('update:changedApis', {
...department.changedApis,
...changed,
});
} }
}, },
selectedRowKeys: ref<string[]>([]), selectedRowKeys: ref<string[]>([]),
}; };
const save = async () => { const save = async () => {
// fix: #bug10828
// id
// const currenTableKeys = props.tableData.map((m: any) => m.id);
// // id
// const currentSelectedKeys = rowSelection.selectedRowKeys.value.filter(
// (key: string) => currenTableKeys.includes(key),
// );
// // , id
// const oldSelectedKeys = currenTableKeys.filter((key) =>
// props.sourceKeys.includes(key),
// );
const keys = props.selectedRowKeys; const keys = props.selectedRowKeys;
// key
const removeKeys = props.sourceKeys.filter((key) => !keys.includes(key)); const removeKeys = props.sourceKeys.filter((key) => !keys.includes(key));
// key
const addKeys = keys.filter((key) => !props.sourceKeys.includes(key)); const addKeys = keys.filter((key) => !props.sourceKeys.includes(key));
// console.log('addKeys: ', addKeys);
// console.log('removeKeys: ', removeKeys);
//
// const removeKeys = oldSelectedKeys.filter(
// (key) => !currentSelectedKeys.includes(key),
// );
// //
// const addKeys = currentSelectedKeys.filter(
// (key) => !oldSelectedKeys.includes(key),
// );
if (props.mode === 'api') { if (props.mode === 'api') {
// api // api
@ -163,14 +146,10 @@ const save = async () => {
const removeItems = removeKeys.map((key) => ({ const removeItems = removeKeys.map((key) => ({
id: key, id: key,
permissions: props.changedApis[key]?.security, permissions: props.changedApis[key]?.security,
// permissions: props.tableData.find((f: any) => f.id === key)
// ?.security,
})); }));
const addItems = addKeys.map((key) => ({ const addItems = addKeys.map((key) => ({
id: key, id: key,
permissions: props.changedApis[key]?.security, permissions: props.changedApis[key]?.security,
// permissions: props.tableData.find((f: any) => f.id === key)
// ?.security,
})); }));
Promise.all([ Promise.all([
updateOperations_api(code, '_delete', { operations: removeItems }), updateOperations_api(code, '_delete', { operations: removeItems }),

View File

@ -76,6 +76,9 @@ import LeftTree from './components/LeftTree.vue';
import ChooseApi from './components/ChooseApi.vue'; import ChooseApi from './components/ChooseApi.vue';
import ApiDoes from './components/ApiDoes.vue'; import ApiDoes from './components/ApiDoes.vue';
import ApiTest from './components/ApiTest.vue'; import ApiTest from './components/ApiTest.vue';
import { useDepartmentStore } from '@/store/department';
const department = useDepartmentStore();
const props = defineProps<{ const props = defineProps<{
mode: modeType; mode: modeType;
@ -157,12 +160,20 @@ function getSelectKeys() {
} }
} }
// watch( watch(
// () => changedApis.value, () => selectedKeys.value,
// (val: any) => { (val: any) => {
// console.log('changedApis: ', val); // console.log('selectedKeys: ', val);
// }, department.setSelectedKeys(val);
// ); },
);
watch(
() => changedApis.value,
(val: any) => {
// console.log('changedApis: ', val);
department.setChangedApis(val);
},
);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -6,60 +6,61 @@
target="category" target="category"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
/> />
<FullPage>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getRelationshipList_api" :request="getRelationshipList_api"
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ :defaultParams="{
pageSize: 10, pageSize: 10,
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
:pagination="{ :pagination="{
showSizeChanger: true, showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
}" }"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton
type="primary"
:hasPermission="`${permission}:add`"
@click="table.openDialog(undefined)"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton <PermissionButton
:hasPermission="`${permission}:update`" type="primary"
type="link" :hasPermission="`${permission}:add`"
:tooltip="{ @click="table.openDialog(undefined)"
title: '编辑',
}"
@click="table.openDialog(slotProps)"
> >
<AIcon type="EditOutlined" /> <AIcon type="PlusOutlined" />新增
</PermissionButton> </PermissionButton>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
}"
@click="table.openDialog(slotProps)"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton <PermissionButton
:danger="true" :danger="true"
:hasPermission="`${permission}:delete`" :hasPermission="`${permission}:delete`"
type="link" type="link"
:tooltip="{ title: '删除' }" :tooltip="{ title: '删除' }"
:popConfirm="{ :popConfirm="{
title: `确认删除`, title: `确认删除`,
onConfirm: () => table.clickDel(slotProps), onConfirm: () => table.clickDel(slotProps),
}" }"
:disabled="slotProps.status" :disabled="slotProps.status"
> >
<AIcon type="DeleteOutlined" /> <AIcon type="DeleteOutlined" />
</PermissionButton> </PermissionButton>
</j-space> </j-space>
</template> </template>
</j-pro-table> </j-pro-table>
</FullPage>
<EditDialog <EditDialog
v-if="dialog.visible" v-if="dialog.visible"

View File

@ -6,65 +6,66 @@
target="category" target="category"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
/> />
<FullPage>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getRoleList_api" :request="getRoleList_api"
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ :defaultParams="{
pageSize: 10, pageSize: 10,
sorts: [ sorts: [
{ name: 'createTime', order: 'desc' }, { name: 'createTime', order: 'desc' },
{ name: 'id', order: 'desc' }, { name: 'id', order: 'desc' },
], ],
}" }"
:pagination="{ :pagination="{
showSizeChanger: true, showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
}" }"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton
type="primary"
:hasPermission="`${permission}:add`"
@click="dialogVisible = true"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton <PermissionButton
:hasPermission="`${permission}:update`" type="primary"
type="link" :hasPermission="`${permission}:add`"
:tooltip="{ @click="dialogVisible = true"
title: '编辑',
}"
@click="
jumpPage(`system/Role/Detail`, {
id: slotProps.id,
})
"
> >
<AIcon type="EditOutlined" /> <AIcon type="PlusOutlined" />新增
</PermissionButton> </PermissionButton>
<PermissionButton </template>
type="link"
:hasPermission="`${permission}:delete`" <template #action="slotProps">
:tooltip="{ title: '删除' }" <j-space :size="16">
:popConfirm="{ <PermissionButton
title: `确定要删除吗`, :hasPermission="`${permission}:update`"
onConfirm: () => clickDel(slotProps), type="link"
}" :tooltip="{
> title: '编辑',
<AIcon type="DeleteOutlined" /> }"
</PermissionButton> @click="
</j-space> jumpPage(`system/Role/Detail`, {
</template> id: slotProps.id,
</j-pro-table> })
"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
type="link"
:hasPermission="`${permission}:delete`"
:tooltip="{ title: '删除' }"
:popConfirm="{
title: `确定要删除吗`,
onConfirm: () => clickDel(slotProps),
}"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
<AddDialog v-if="dialogVisible" v-model:visible="dialogVisible" /> <AddDialog v-if="dialogVisible" v-model:visible="dialogVisible" />
</div> </div>

View File

@ -6,106 +6,111 @@
target="category" target="category"
@search="handleParams" @search="handleParams"
/> />
<FullPage>
<j-pro-table <j-pro-table
ref="tableRef" ref="tableRef"
:columns="columns" :columns="columns"
:request="getUserList_api" :request="getUserList_api"
model="TABLE" model="TABLE"
:params="queryParams" :params="queryParams"
:defaultParams="{ :defaultParams="{
pageSize: 10, pageSize: 10,
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
}" }"
:pagination="{ :pagination="{
showSizeChanger: true, showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
}" }"
> >
<template #headerTitle> <template #headerTitle>
<PermissionButton
:hasPermission="`${permission}:add`"
type="primary"
@click="table.openDialog('add')"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #type="slotProps">
{{ slotProps.type.name }}
</template>
<template #status="slotProps">
<BadgeStatus
:status="slotProps.status"
:text="slotProps.status ? '正常' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}"
></BadgeStatus>
</template>
<template #action="slotProps">
<j-space :size="16">
<PermissionButton <PermissionButton
:hasPermission="`${permission}:update`" :hasPermission="`${permission}:add`"
type="link" type="primary"
:tooltip="{ @click="table.openDialog('add')"
title: '编辑',
}"
@click="table.openDialog('edit', slotProps)"
> >
<AIcon type="EditOutlined" /> <AIcon type="PlusOutlined" />新增
</PermissionButton> </PermissionButton>
<PermissionButton </template>
:hasPermission="`${permission}:action`" <template #type="slotProps">
type="link" {{ slotProps.type.name }}
:tooltip="{ </template>
title: `${slotProps.status ? '禁用' : '启用'}`, <template #status="slotProps">
<BadgeStatus
:status="slotProps.status"
:text="slotProps.status ? '正常' : '禁用'"
:statusNames="{
1: 'success',
0: 'error',
}" }"
:popConfirm="{ ></BadgeStatus>
title: `确定${ </template>
slotProps.status ? '禁用' : '启用' <template #action="slotProps">
}`, <j-space :size="16">
onConfirm: () => table.changeStatus(slotProps), <PermissionButton
}" :hasPermission="`${permission}:update`"
> type="link"
<AIcon :tooltip="{
:type=" title: '编辑',
slotProps.status }"
? 'StopOutlined' @click="table.openDialog('edit', slotProps)"
: 'PlayCircleOutlined' >
" <AIcon type="EditOutlined" />
/> </PermissionButton>
</PermissionButton> <PermissionButton
<PermissionButton :hasPermission="`${permission}:action`"
:hasPermission="`${permission}:update`" type="link"
type="link" :tooltip="{
:tooltip="{ title: `${
title: '重置密码', slotProps.status ? '禁用' : '启用'
}" }`,
@click="table.openDialog('reset', slotProps)" }"
> :popConfirm="{
<AIcon type="icon-zhongzhimima" /> title: `确定${
</PermissionButton> slotProps.status ? '禁用' : '启用'
<PermissionButton }`,
type="link" onConfirm: () =>
:hasPermission="`${permission}:delete`" table.changeStatus(slotProps),
:tooltip="{ }"
title: slotProps.status >
? '请先禁用,再删除' <AIcon
: '删除', :type="
}" slotProps.status
:popConfirm="{ ? 'StopOutlined'
title: `确认删除`, : 'PlayCircleOutlined'
onConfirm: () => table.clickDel(slotProps.id), "
}" />
:disabled="slotProps.status" </PermissionButton>
> <PermissionButton
<AIcon type="DeleteOutlined" /> :hasPermission="`${permission}:update`"
</PermissionButton> type="link"
</j-space> :tooltip="{
</template> title: '重置密码',
</j-pro-table> }"
@click="table.openDialog('reset', slotProps)"
>
<AIcon type="icon-zhongzhimima" />
</PermissionButton>
<PermissionButton
type="link"
:hasPermission="`${permission}:delete`"
:tooltip="{
title: slotProps.status
? '请先禁用,再删除'
: '删除',
}"
:popConfirm="{
title: `确认删除`,
onConfirm: () =>
table.clickDel(slotProps.id),
}"
:disabled="slotProps.status"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</j-space>
</template>
</j-pro-table>
</FullPage>
<EditUserDialog <EditUserDialog
v-if="dialog.visible" v-if="dialog.visible"
@ -263,22 +268,21 @@ type dictType = {
}; };
type modalType = '' | 'add' | 'edit' | 'reset'; type modalType = '' | 'add' | 'edit' | 'reset';
const handleParams = (params: any)=> { const handleParams = (params: any) => {
const newParams = (params?.terms as any[])?.map((item1) => {
const newParams = (params?.terms as any[])?.map(item1 => { item1.terms = item1.terms.map((item2: any) => {
item1.terms = item1.terms.map((item2: any) => { if (['telephone', 'email'].includes(item2.column)) {
if (['telephone', 'email'].includes(item2.column)) { return {
return { column: 'id$user-detail',
column: 'id$user-detail', value: [item2],
value: [item2] };
} }
} return item2;
return item2 });
}) return item1;
return item1 });
}) queryParams.value = { terms: newParams || [] };
queryParams.value = { terms: newParams || [] } };
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>