Merge branch 'dev'

# Conflicts:
#	package.json
#	src/components/Form/rules.ts
#	src/style.less
#	vite.config.ts
#	yarn.lock
This commit is contained in:
xieyonghong 2023-04-19 17:36:39 +08:00
commit 4d9263f4fa
29 changed files with 1683 additions and 84 deletions

View File

@ -25,7 +25,7 @@
"event-source-polyfill": "^1.0.31",
"global": "^4.4.0",
"jetlinks-store": "^0.0.3",
"jetlinks-ui-components": "1.0.5",
"jetlinks-ui-components": "^1.0.8",
"js-cookie": "^3.0.1",
"less": "^4.1.3",
"less-loader": "^11.1.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
public/images/cascade.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
public/images/channel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
public/images/plug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -43,3 +43,7 @@ export const getResourcesCurrent = () =>
export const getClusters = () =>
server.get(`network/resources/clusters`);
export const getPluginList = (data: any) => server.post('/plugin/driver/_query/no-paging', data)
export const getPluginConfig = (id: string) => server.get(`/plugin/driver/${id}/description`)

30
src/api/link/plugin.ts Normal file
View File

@ -0,0 +1,30 @@
import { post, get, remove, patch } from '@/utils/request'
import { BASE_API_PATH } from '@/utils/variable';
export const queryPage = (data: any) => post(`/plugin/driver/_query`, data)
export const uploadFile = `${BASE_API_PATH}/plugin/driver/upload`
export const add = (data: any) => post('/plugin/driver', data)
export const removeFn = (id: string) => remove(`/plugin/driver/${id}`)
export const detail = (id: string) => get(`/plugin/driver/${id}`)
/**
*
*
* ID映射关系
* @param id
*/
export const getProductsById = (id: string) => get(`/plugin/driver/${id}/products`)
export const savePluginData = (type: string, pluginId: string, internalId: string, externalId: string ) => patch(`/plugin/mapping/${type}/${pluginId}/${internalId}`, externalId)
export const getPluginData = (type: string, pluginId: string, internalId: string ) => get(`/plugin/mapping/${type}/${pluginId}/${internalId}`)
export const getPublic = (id: string, path: string) => get(`/plugin/driver/${id}/${path}`)
export const getTypes = () => get(`/dictionary/internal-plugin-type/items`)
export const vailIdFn = (id: string ) => get(`/plugin/driver/id/_validate`, { id })

View File

@ -1,7 +1,7 @@
const MaxLengthStringFn = (len: number = 64) => ({
max: len,
message: `最多输入${64}个字符`,
message: `最多输入${len}个字符`,
})
export const Max_Length_64 = [MaxLengthStringFn()]
@ -25,3 +25,66 @@ export const ID_Rule = [
},
Max_Length_64[0]
]
export const CreteRuleByType = (type: string) => {
switch (type){
case 'int':
return [
{
validator: (_: any, value: number) => {
const baseNumber = 2147483648
if (value < -baseNumber) {
return Promise.reject(`最小仅输入-${baseNumber}`);
}
if (value > baseNumber) {
return Promise.reject(`最大可输入${baseNumber}`);
}
return Promise.resolve();
}
}
]
case'long':
return [
{
validator: (_: any, value: number) => {
const baseNumber = 340282346638528860000000000000000000000
if (value < -baseNumber) {
return Promise.reject(`最小仅输入-${baseNumber}`);
}
if (value > baseNumber) {
return Promise.reject(`最大可输入${baseNumber}`);
}
return Promise.resolve();
}
}
]
case'float':
return [
{
validator: (_: any, value: number) => {
const baseNumber = 9223372036854775807
if (value < -baseNumber) {
return Promise.reject(`最小仅输入-${baseNumber}`);
}
if (value > baseNumber) {
return Promise.reject(`最大可输入${baseNumber}`);
}
return Promise.resolve();
}
}
]
// case'double':
// return [
// {
// max: 1.7976931348623157,
// message: '最大可输入64位字符'
// }
// ]
case 'string':
return [MaxLengthStringFn()];
case 'description':
return [MaxLengthStringFn(200)]
default:
return []
}
}

View File

@ -1,3 +1,7 @@
@import "./style/variable";
@DarkMenuItemColor: #808491 !important;
.ant-form-item-required:before {
position: absolute;
right: -12px;
@ -16,20 +20,16 @@
}
.ant-pro-top-nav-header {
.ant-menu-item {
padding: 0 10px !important;
&:not(:first-child) {
margin-left: 8px;
}
&:hover {
background-color: transparent;
margin-left: 8px !important;
}
}
}
.ant-menu-item-selected,& .ant-menu-item-active {
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.dark {
background-color: #3F4960 !important;
h1,& .right-content, & .anticon-bell {
color: #fff !important;
@ -39,42 +39,111 @@
color: rgba(#fff, 0.55) !important;
}
.ant-menu-item-selected {
& .ant-pro-menu-item-title,& .anticon {
color: #fff !important;
}
}
.ant-menu-item-selected,& .ant-menu-item-active {
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
background: linear-gradient(0deg, rgba(#fff, 0.25) 0%, rgba(#fff, 0) 82%);
&::after {
left: 0;
right: 0;
border-bottom-color: rgba(#fff, .8);
}
}
&.light {
background: #3F4960;
box-shadow: 0 1px 0px 0px #E9E9E9;
h1,& .right-content, & .anticon-bell {
color: #fff !important;
}
.ant-menu {
background-color: #3F4960 !important;
.ant-menu-item-selected,& .ant-menu-item-active {
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
background: linear-gradient(0deg, rgba(#fff, 0.15) 0%, rgba(#fff, 0) 82%);
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
}
.ant-menu-item-selected {
span {
color: #fff !important;
}
}
.ant-menu-item {
span{
color: rgba(255, 255, 255, 0.55) !important;
}
&:hover {
background-color: transparent !important;
}
&::after {
left: 0;
right: 0;
border-bottom-color: rgba(#fff, .7);
}
}
.ant-menu-item-selected,& .ant-menu-item-active {
transition: background 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
background: linear-gradient(0deg, rgba(#fff, 0.25) 0%, rgba(#fff, 0) 82%) !important;
&::after {
border-bottom-color: rgba(#fff, .8);
}
}
}
}
.ant-layout-sider-dark {
background: #fff !important;
.ant-menu-dark {
background: #fff !important;
span {
color: @DarkMenuItemColor;
}
.ant-menu-submenu-title {
span {
color: @DarkMenuItemColor;
}
i {
&::after {
background-color: @DarkMenuItemColor;
}
&::before {
background-color: @DarkMenuItemColor;
}
}
}
.ant-menu-sub {
background: transparent;
.ant-menu-item {
span {
color: @DarkMenuItemColor;
}
}
}
.ant-menu-submenu-selected {
.ant-menu-submenu-title {
span {
color: @primary-color !important;
}
i {
&::after {
background-color: @primary-color !important;
}
&::before {
background-color: @primary-color !important;
}
}
}
.ant-menu-item-selected {
span {
color: @primary-color !important;
}
}
}
.ant-menu-item-selected {
background-color: transparent !important;
span {
color: @primary-color !important;
}
}
}
}
.ant-layout-sider {
box-shadow: 1px 0 0 0 #E9E9E9 !important;
}

View File

@ -0,0 +1,69 @@
<template>
<j-modal
:maskClosable="false"
:visible="true"
width="800px"
title="导入"
>
<div>
<!-- 选择产品 -->
<div v-if='steps === 0'>
<Product
v-model:rowKey='importData.productId'
/>
</div>
</div>
<template #footer>
<j-button v-if='steps === 0' @click='cancel' >取消</j-button>
<j-button v-if='steps !== 0' @click='prev' >上一步</j-button>
<j-button v-if='steps !== 2' @click='next' type='primary'>下一步</j-button>
<j-button v-if='steps === 2' @click='save' type='primary'>确认</j-button>
</template>
</j-modal>
</template>
<script lang='ts' setup name='DeviceImport'>
import Product from './product.vue'
import { onlyMessage } from '@/utils/comm'
import { queryList } from '@/api/device/product';
const emit = defineEmits(['cancel', 'save']);
const steps = ref(0) //
const importData = reactive({
productId: undefined,
type: undefined,
})
const next = () => {
if (steps.value === 0 && !importData.productId) {
return onlyMessage('请选择产品', 'error')
}
if (steps.value === 1 && !importData.type) {
return onlyMessage('请选择导入方式', 'error')
}
steps.value += 1
}
const prev = () => {
if (steps.value === 2 && importData.type) {
steps.value = 0
} else {
steps.value -= 1
}
}
const cancel = () => {
emit('cancel')
}
const save = () => {
emit('save')
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,254 @@
<template>
<j-advanced-search
:columns="columns"
type='simple'
@search="handleSearch"
class="scene-search"
target="device-import-product"
/>
<j-divider style='margin: 0' />
<j-pro-table
model='CARD'
:columns='columns'
:params='params'
:request='productQuery'
:gridColumn='2'
:gridColumns='[2,2,2]'
:bodyStyle='{
paddingRight: 0,
paddingLeft: 0
}'
>
<template #card="slotProps">
<CardBox
:value='slotProps'
:active="rowKey === slotProps.id"
:status="slotProps.state"
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
:statusNames="{ 1: 'processing', 0: 'error', }"
@click="handleClick"
>
<template #img>
<slot name="img">
<img width='80' height='80' :src="slotProps.photoUrl || getImage('/device-product.png')" />
</slot>
</template>
<template #content>
<div style='width: calc(100% - 100px)'>
<Ellipsis>
<span style="font-size: 16px;font-weight: 600" >
{{ slotProps.name }}
</span>
</Ellipsis>
</div>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>直连设备</div>
</j-col>
</j-row>
</template>
</CardBox>
</template>
</j-pro-table>
</template>
<script setup lang='ts' name='Product'>
import { getProviders, queryGatewayList, queryProductList } from '@/api/device/product'
import { queryTree } from '@/api/device/category'
import { getTreeData_api } from '@/api/system/department'
import { getImage } from '@/utils/comm'
import { accessConfigTypeFilter } from '@/utils/setting'
type Emit = {
(e: 'update:rowKey', data: string): void
(e: 'change', data: string): void
}
const params = ref({})
const props = defineProps({
rowKey: {
type: String,
default: ''
},
})
const emit = defineEmits<Emit>()
const firstFind = ref(true)
const columns = [
{
title: 'ID',
dataIndex: 'id',
width: 300,
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '名称',
dataIndex: 'name',
width: 200,
ellipsis: true,
search: {
type: 'string',
first: true
}
},
{
title: '网关类型',
dataIndex: 'accessProvider',
width: 150,
ellipsis: true,
hideInTable: true,
search: {
type: 'select',
options: () => getProviders().then((resp: any) => {
return accessConfigTypeFilter(resp.result || [])
})
}
},
{
title: '接入方式',
dataIndex: 'accessName',
width: 150,
ellipsis: true,
search: {
type: 'select',
options: () => queryGatewayList().then((resp: any) =>
resp.result.map((item: any) => ({
label: item.name, value: item.id
}))
)
}
},
{
title: '设备类型',
dataIndex: 'deviceType',
width: 150,
search: {
type: 'select',
options: [
{ label: '直连设备', value: 'device' },
{ label: '网关子设备', value: 'childrenDevice' },
{ label: '网关设备', value: 'gateway' },
]
}
},
{
title: '状态',
dataIndex: 'state',
width: '90px',
search: {
type: 'select',
options: [
{ label: '禁用', value: 0 },
{ label: '正常', value: 1 },
]
}
},
{
title: '说明',
dataIndex: 'describe',
ellipsis: true,
width: 300,
},
{
dataIndex: 'classifiedId',
title: '分类',
hideInTable: true,
search: {
type: 'treeSelect',
options: () => {
return new Promise((res => {
queryTree({ paging: false }).then(resp => {
res(resp.result)
})
}))
},
componentProps: {
fieldNames: {
label: 'name',
value: 'id',
}
}
}
},
{
dataIndex: 'id$dim-assets',
title: '所属组织',
hideInTable: true,
search: {
type: 'treeSelect',
options: () => new Promise((resolve) => {
getTreeData_api({ paging: false }).then((resp: any) => {
const formatValue = (list: any[]) => {
return list.map((item: any) => {
if (item.children) {
item.children = formatValue(item.children);
}
return {
...item,
value: JSON.stringify({
assetType: 'product',
targets: [
{
type: 'org',
id: item.id,
},
],
}),
}
})
}
resolve(formatValue(resp.result) || [])
})
})
}
}
]
const handleSearch = (p: any) => {
params.value = p
}
const productQuery = async (p: any) => {
const sorts: any = [];
if (props.rowKey) {
sorts.push({
name: 'id',
value: props.rowKey,
});
}
sorts.push({ name: 'createTime', order: 'desc' });
p.sorts = sorts
const resp = await queryProductList(p)
if (resp.success && props.rowKey && firstFind.value) {
const productItem = (resp.result as { data: any[]}).data.find((item: any) => item.id === props.rowKey)
emit('update:detail', productItem)
firstFind.value = false
}
return {
...resp
}
}
const handleClick = (detail: any) => {
emit('update:rowKey', detail.id)
emit('change', detail)
}
</script>
<style scoped lang='less'>
.search {
margin-bottom: 0;
padding-right: 0px;
padding-left: 0px;
}
</style>

View File

@ -90,7 +90,7 @@
<j-form ref="formRef" :model="formData.data" layout="vertical">
<j-form-item
:name="item.property"
v-for="item in metadata.properties"
v-for="item in metadata?.properties || []"
:key="item"
:label="item.name"
:rules="[

View File

@ -13,11 +13,19 @@
<div class="go-back" v-if="id === ':id'">
<a @click="goBack">返回</a>
</div>
<template v-if="showType === 'network'">
<Network
v-if="showType === 'network'"
:provider="provider"
:data="data"
v-if="provider.id !== 'plugin_gateway'"
:provider="provider"
:data="data"
/>
<Plugin
v-else
:provider="provider"
:data="data"
/>
</template>
<Media
v-if="showType === 'media'"
:provider="provider"
@ -52,6 +60,7 @@ import Media from '../components/Media/index.vue';
import Channel from '../components/Channel/index.vue';
import Edge from '../components/Edge/index.vue';
import Cloud from '../components/Cloud/index.vue';
import Plugin from '../components/Plugin/index.vue'
import { getProviders, detail } from '@/api/link/accessConfig';
import { accessConfigTypeFilter } from '@/utils/setting';
@ -125,13 +134,7 @@ const getTypeList = (result: Record<string, any>) => {
edge.push(item);
} else {
item.type = 'network';
// network.push(item);
/**
* 插件设备接入 暂时不开发 todo
*/
if (item.id !== 'plugin_gateway' || item.name !== '插件设备接入') {
network.push(item);
}
network.push(item);
}
});

View File

@ -34,8 +34,8 @@ const emit = defineEmits(['checkedChange']);
const props = defineProps({
checked: {
type: Array,
default: () => [],
type: String,
default: undefined,
},
data: {
type: Object,
@ -115,15 +115,15 @@ const checkedChange = (id: string) => {
}
}
.access-media {
background: url('/public/images/access-media.png') no-repeat;
background: url('/images/access-media.png') no-repeat;
background-position: bottom right;
}
.access-network {
background: url('/public/images/access-network.png') no-repeat;
.access-network, .access-plugin {
background: url('/images/access-network.png') no-repeat;
background-position: bottom right;
}
.access-protocol {
background: url('/public/images/access-protocol.png') no-repeat;
background: url('/images/access-protocol.png') no-repeat;
background-position: bottom right;
}
</style>

View File

@ -294,6 +294,7 @@
:hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update'
}`"
:loading='loading'
>
保存
</PermissionButton>
@ -378,6 +379,7 @@ const formData = ref({
name: '',
description: '',
});
const loading = ref(false)
const { resetFields, validate, validateInfos } = useForm(
formData,
@ -515,10 +517,12 @@ const saveData = () => {
? 'Gateway'
: ProtocolMapping.get(props.provider.id),
};
loading.value = true
const resp =
id === ':id'
? await save(params)
: await update({ ...params, id });
loading.value = false
if (resp.status === 200) {
onlyMessage('操作成功', 'success');
history.back();

View File

@ -0,0 +1,426 @@
<template>
<div>
<j-steps :current="current">
<j-step disabled :key="0" title="选择插件" />
<j-step disabled :key="1" title="完成" />
</j-steps>
<div class='steps-content'>
<div class="steps-box" v-if="current === 0">
<div class="search">
<j-input-search
allowClear
placeholder="请输入"
style="width: 300px"
@search="pluginSearch"
/>
<PermissionButton
type="primary"
@click="addPlugin"
hasPermission="link/plugin:add"
>
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</div>
<j-scrollbar height="480">
<j-row
:gutter="[24, 24]"
style="width: 100%"
v-if="pluginList.length > 0"
>
<j-col
:span="8"
v-for="item in pluginList"
:key="item.id"
>
<AccessCard
@checkedChange="AccessChange"
:checked="AccessCurrent"
:disabled='paramsId !== ":id"'
:data="{ ...item, type: 'plugin' }"
>
<template #other>
<div class='plugin-other'>
<div class='plugin-id'>
插件ID
<div class='other-content'>
<Ellipsis >
{{ item.id }}
</Ellipsis>
</div>
</div>
<div class='plugin-version'>
版本号
<div class='other-content'>
<Ellipsis >
{{ item.version }}
</Ellipsis>
</div>
</div>
</div>
</template>
</AccessCard>
</j-col>
</j-row>
<j-empty
style="margin-top: 10%"
v-else
description="暂无数据"
/>
</j-scrollbar>
</div>
<div class="steps-box" v-else-if="current === 1">
<div
class="card-last"
:style="`max-height:${ clientHeight > 900 ? 750 : clientHeight * 0.7 }px`"
>
<j-row :gutter="[24, 24]">
<j-col :span="16">
<title-component data="基本信息" />
<j-form
ref="formRef"
:model="formData"
layout="vertical"
>
<j-form-item
label="名称"
:rules="[
{ required: true, message: '请输入名称', trigger: 'blur' },
{
max: 64,
message: '最多可输入64个字符',
trigger: 'blur',
},
]"
name='name'
>
<j-input
v-model:value="formData.name"
allowClear
placeholder="请输入名称"
/>
</j-form-item>
<j-form-item
label="说明"
:rules="[{ max: 200, message: '最多可输入200个字符' }]"
name='description'
>
<j-textarea
placeholder="请输入说明"
:rows="4"
v-model:value="formData.description"
show-count
:maxlength="200"
/>
</j-form-item>
<template v-if='config.length' >
<title-component data="通用配置" />
<j-form-item
v-for='item in config'
:key='item.name'
:name='["configuration", item.name]'
:label='item.label'
:rules='item.rules'
>
<ValueItem v-model:modelValue='formData.configuration[item.name]' :itemType='item.type' />
</j-form-item>
</template>
</j-form>
</j-col>
</j-row>
</div>
</div>
</div>
<div class="steps-action">
<j-button
v-if="current === 0"
type="primary"
style="margin-right: 8px"
@click="next"
>
下一步
</j-button>
<PermissionButton
v-if="current === 1 && view === 'false'"
type="primary"
style="margin-right: 8px"
@click="saveData"
:hasPermission="`link/AccessConfig:${
id === ':id' ? 'add' : 'update'
}`"
:loading='loading'
>
保存
</PermissionButton>
<j-button
v-if="current > 0"
@click="prev"
>
上一步
</j-button>
</div>
</div>
</template>
<script lang='ts' setup name='AccessConfigPlugin'>
import {
save,
update,
getPluginList,
getPluginConfig } from '@/api/link/accessConfig'
import AccessCard from '../AccessCard/index.vue';
import { useMenuStore } from 'store/menu'
import { onlyMessage } from '@/utils/comm';
import { CreteRuleByType } from 'components/Form/rules'
const props = defineProps({
provider: {
type: Object,
default: () => {},
},
data: {
type: Object,
default: () => {},
},
});
const route = useRoute();
const menuStory = useMenuStore();
const current = ref(0);
const pluginList = ref([])
const AccessCurrent = ref(props.data.channelId)
const paramsId = route.params.id as string;
const view = route.query.view as string;
const clientHeight = document.body.clientHeight;
const loading = ref(false)
const formData = reactive({
name: undefined,
description: undefined,
configuration: {}
})
const formRef = ref();
const config = ref<any>([])
const queryPlugin = (params = {}) => {
getPluginList({
...params,
sorts: [{ name: 'createTime', order: 'desc' }],
paging: false
}).then(res => {
pluginList.value = []
if (res.success) {
pluginList.value = res.result || []
}
})
}
const getRules = (item: any) => {
let typeName = '输入'
let rules: any[] = []
if (['select', 'date'].includes(item.type?.type || 'string')) {
typeName = '选择'
}
if (item.required) {
rules.push(
{
required: true,
message: `${typeName}${item.name}`
}
)
}
const typeRules = CreteRuleByType(item.type?.type)
rules = [...rules, ...typeRules]
return rules
}
const queryPluginConfig = (id: string, update: boolean = true) => {
getPluginConfig(id).then(res => {
if (res.success) {
const properties = res.result?.others?.configMetadata?.properties || []
config.value = properties.map((item: any) => {
if (update) {
formData.configuration[item.property] = undefined
}
return {
label: item.name,
name: item.property,
type: item.type?.type || 'string',
rules: getRules(item)
}
})
}
})
}
const pluginSearch = (val: string) => {
queryPlugin({
terms: [{
column: 'name',
termType: 'like',
value: `%${val}%`
}]
})
}
const AccessChange = (id: string) => {
if (!props.data.id) {
AccessCurrent.value = id;
}
};
const addPlugin = () => {
const url = menuStory.menus['link/plugin']?.path;
const wd: any = window.open(
`${window.location.origin + window.location.pathname}#${url}?save=true`,
);
wd.onTabSaveSuccess = (value: any) => {
if (value.success) {
AccessCurrent.value = value.result?.id;
pluginList.value.unshift(value.result as any)
}
}
}
/**
* 下一步
*/
const next = () => {
if (!AccessCurrent.value) {
return onlyMessage('请选择插件!', 'error');
}
current.value += 1
}
/**
* 上一步
*/
const prev = () => {
current.value -= 1
}
/**
* 保存
*/
const saveData = () => {
formRef.value.validate().then(async (data: any) => {
if (data) {
const params = {
...props.data,
...data,
protocol: 'plugin',
channel: 'plugin', //
channelId: AccessCurrent.value,
provider: props.provider.id,
transport: 'plugin'
};
loading.value = true
const resp =
paramsId === ':id'
? await save(params)
: await update({ ...params, id: paramsId });
loading.value = false
if (resp.status === 200) {
onlyMessage('操作成功', 'success');
history.back();
if ((window as any).onTabSaveSuccess) {
if (resp.result?.id) {
(window as any).onTabSaveSuccess(resp);
setTimeout(() => window.close(), 300);
}
}
}
}
})
}
watchEffect(() => {
if (current.value === 1 && AccessCurrent.value) {
queryPluginConfig(AccessCurrent.value, AccessCurrent.value !== props.data.channelId)
}
})
onMounted(() => {
if (paramsId !== ':id') { //
formData.name = props.data.name
formData.description = props.data.description
formData.configuration = props.data.configuration
}
})
queryPlugin()
</script>
<style scoped lang='less'>
.steps-content {
margin-top: 20px;
}
.steps-box {
min-height: 400px;
.card-last {
padding-right: 5px;
overflow-y: auto;
overflow-x: hidden;
}
}
.steps-action {
width: 100%;
margin-top: 24px;
}
.alert {
height: 40px;
padding-left: 10px;
color: rgba(0, 0, 0, 0.55);
line-height: 40px;
background-color: #f6f6f6;
}
.search {
display: flex;
margin: 15px 0;
justify-content: space-between;
}
.other {
width: 100%;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
.item {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.plugin-other {
display: flex;
gap: 16px;
.plugin-id, .plugin-version {
color: rgba(0, 0, 0, 0.85);
opacity: .45;
display: flex;
}
.plugin-id {
width: 50%;
.other-content {
display: flex;
width: 0;
flex-grow: 1;
}
}
}
</style>

View File

@ -98,7 +98,7 @@ const click = (value: object) => {
width: 15%;
min-width: 64px;
height: 2px;
background-image: url('/public/images/access/rectangle.png');
background-image: url('/images/access/rectangle.png');
background-repeat: no-repeat;
background-size: 100% 100%;
content: ' ';

View File

@ -33,6 +33,7 @@ BackMap.set('modbus-tcp', getImage('/access/modbus.png'));
BackMap.set('coap-server-gateway', getImage('/access/coap.png'));
BackMap.set('tcp-server-gateway', getImage('/access/tcp.png'));
BackMap.set('Ctwing', getImage('/access/ctwing.png'));
BackMap.set('plugin_gateway', getImage('/access/plugin.png'));
BackMap.set('child-device', getImage('/access/child-device.png'));
BackMap.set('opc-ua', getImage('/access/opc-ua.png'));
BackMap.set('http-server-gateway', getImage('/access/http.png'));

View File

@ -13,18 +13,7 @@
:columns="columns"
:request="list"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
terms: [
{
terms: [
{
termType: 'nin',
column: 'provider',
value: 'plugin_gateway', //todo
},
],
},
],
sorts: [{ name: 'createTime', order: 'desc' }]
}"
gridColumn="2"
:gridColumns="[1, 2]"

View File

@ -0,0 +1,171 @@
<template>
<j-modal
:maskClosable='false'
:visible='true'
:title="!!data?.id ? '编辑' : '新增'"
:confirmLoading='loading'
@ok='handleSave'
@cancel='handleCancel'
width='650px'
>
<div>
<j-form :layout="'vertical'" ref='formRef' :model='modelRef'>
<j-form-item
name='id'
:rules='IdRules'
>
<template #label>
<span>
插件ID
<j-tooltip
title='若不填写系统将自动生成唯一ID'
>
<AIcon
type='QuestionCircleOutlined'
style='margin-left: 2px'
/>
</j-tooltip>
</span>
</template>
<j-input v-model:value='modelRef.id' :disabled='!!data.id' />
</j-form-item>
<j-form-item
label='插件名称'
name='name'
:rules="nameRules"
>
<j-input v-model:value='modelRef.name' />
</j-form-item>
<j-form-item
label='文件'
name='version'
:rules='[{ required: true, message: "请上传文件" }]'
>
<UploadFile v-model:modelValue='modelRef.version' @change='uploadChange' />
</j-form-item>
<div v-if='modelRef.version' class='file-detail'>
<div>
<span>插件类型</span>
<span class='file-detail-item'>{{ TypeMap[modelRef.type] }}</span>
</div>
<div>
<span>版本</span>
<span class='file-detail-item'>{{ modelRef.version }}</span>
</div>
</div>
<j-form-item
label='说明'
name='describe'
:rules='Max_Length_200'
>
<j-textarea
v-model:value='modelRef.description'
placeholder='请输入说明'
showCount
:maxlength='200'
/>
</j-form-item>
</j-form>
</div>
</j-modal>
</template>
<script setup lang='ts' name='PluginSave'>
import { ID_Rule, Max_Length_64, Max_Length_200, RequiredStringFn } from '@/components/Form/rules'
import UploadFile from './UploadFile.vue'
import { FileUploadResult } from '@/views/link/plugin/typings'
import { add, vailIdFn } from '@/api/link/plugin'
import { message } from 'jetlinks-ui-components'
import { TypeMap } from './util'
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['cancel', 'ok'])
const route = useRoute()
const formRef = ref()
const fileType = ref(props.data.type)
const loading = ref(false)
const vailId = async (_: any, value: string) => {
if (!!props.data.id && value) { //
const resp = await vailIdFn(value)
if (resp.success && resp.result) {
return Promise.reject('ID重复');
}
}
return Promise.resolve();
}
const nameRules = [
RequiredStringFn('插件名称'),
...Max_Length_64
]
const IdRules = [
...ID_Rule,
{
validator: vailId,
trigger: 'blur',
},
]
const modelRef = reactive<any>({
id: props.data.id,
name: props.data.name,
description: props.data.description,
type: props.data.type,
provider: props.data.provider || 'jar',
version: props.data.version,
filename: props.data.filename,
configuration: props.data.configuration || {}
})
const uploadChange = (data: FileUploadResult) => {
modelRef.type = data.type.value
modelRef.filename = data.filename
modelRef.configuration.location = data.accessUrl
}
const handleSave = async () => {
const data = await formRef.value.validate()
if (data) {
loading.value = true
const resp = await add(modelRef).catch(() => { success: false })
loading.value = false
if (resp.success) {
message.success('操作成功!');
if (route.query.save && (window as any).onTabSaveSuccess) {
(window as any).onTabSaveSuccess(resp);
setTimeout(() => window.close(), 300);
return
}
emit('ok');
formRef.value.resetFields();
}
}
}
const handleCancel = () => {
emit('cancel')
}
</script>
<style scoped lang='less'>
.file-detail {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 16px;
.file-detail-item {
color: #4F4F4F;
}
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<j-upload
name="file"
accept=".jar"
:action="uploadFile"
:headers="{
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
}"
@change="handleChange"
class="upload-box"
:before-upload="beforeUpload"
:disabled='loading'
:maxCount='1'
>
<div>
<j-button>上传文件</j-button>
<span class='upload-tip'>格式要求{文件名}.jar/{文件名}.zip</span>
</div>
</j-upload>
</template>
<script setup lang="ts" name="FileUpload">
import { LocalStore } from '@/utils/comm';
import { TOKEN_KEY } from '@/utils/variable';
import { uploadFile } from '@/api/link/plugin';
import { onlyMessage } from '@/utils/comm';
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
import { notification as Notification } from 'jetlinks-ui-components';
import { useSystem } from '@/store/system';
const emit = defineEmits(['update:modelValue', 'change']);
const props = defineProps({
modelValue: {
type: String,
default: () => '',
},
disabled: {
type: Boolean,
default: false
}
});
const paths: string = useSystem().$state.configInfo.paths?.[
'base-path'
] as string;
const value = ref(props.modelValue);
const loading = ref(false);
const beforeUpload: UploadProps['beforeUpload'] = (file, fl) => {
const arr = file.name.split('.');
const isFile = ['jar', 'zip'].includes(arr[arr.length - 1]); // file.type === 'application/zip' || file.type === 'application/javj-archive'
if (!isFile) {
onlyMessage('请上传.jar,.zip格式的文件', 'error');
loading.value = false;
}
return isFile;
};
const handleChange = async (info: UploadChangeParam) => {
loading.value = true;
if (info.file.status === 'done') {
loading.value = false;
console.log(info.file)
const result = info.file.response?.result;
const f = result.accessUrl;
onlyMessage('上传成功!', 'success');
value.value = f;
emit('update:modelValue', result.version);
emit('change', result);
} else {
if (info.file.error) {
Notification.error({
// key: '403',
message: '系统提示',
description: '系统未知错误,请反馈给管理员',
});
loading.value = false;
} else if (info.file.response) {
loading.value = false;
}
}
};
</script>
<style lang="less" scoped>
.upload-tip {
color: #999;
padding-left: 12px;
}
</style>

View File

@ -0,0 +1,274 @@
<template>
<page-container>
<pro-search
:columns="columns"
target="link-plugin"
@search="handleSearch"
/>
<FullPage>
<JProTable
ref="instanceRef"
:columns="columns"
:request="queryPage"
:defaultParams="{sorts: [{ name: 'createTime', order: 'desc' }]}"
:params='params'
>
<template #headerTitle>
<PermissionButton
type="primary"
@click="handleAdd"
hasPermission="link/plugin:add"
>
<template #icon><AIcon type="PlusOutlined" /></template>
新增
</PermissionButton>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:showStatus='false'
:actions='getActions(slotProps)'
:statusNames='{
processing: "processing"
}'
status='processing'
>
<template #img>
<img
:width="80"
:height="80"
:src="getImage('/plug.png')"
/>
</template>
<template #content>
<div>
<div>
<j-tag class='plugin-version'>{{ slotProps.version }}</j-tag>
</div>
<Ellipsis style="width: calc(100% - 100px); margin-bottom: 18px;">
<span style="font-size: 16px; font-weight: 600">
{{ slotProps.name }}
</span>
</Ellipsis>
</div>
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
插件ID
</div>
<Ellipsis style="width: 100%">
{{ slotProps.id }}
</Ellipsis>
</j-col>
<j-col :span="12">
<div class="card-item-content-text">
插件类型
</div>
<Ellipsis style="width: 100%">
{{ TypeMap[slotProps.type] }}
</Ellipsis>
</j-col>
</j-row>
</template>
<template #actions="item">
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
:hasPermission="'link/plugin:' + 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 #type='slotProps'>
<span>{{ TypeMap[slotProps.type] }}</span>
</template>
<template #action="slotProps">
<j-space>
<template
v-for="i in getActions(slotProps)"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
:danger="i.key === 'delete'"
style="padding: 0 5px"
:hasPermission="'link/plugin:' + i.key"
>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</j-space>
</template>
</JProTable>
</FullPage>
</page-container>
<SaveModal
v-if='visible'
:data='editData'
@cancel='cancel'
@ok='save'
/>
</template>
<script setup lang='ts' name='PluginIndex'>
import SaveModal from './Save.vue'
import { getImage } from '@/utils/comm'
import { queryPage, removeFn, getTypes } from '@/api/link/plugin'
import { message } from 'jetlinks-ui-components'
import { TypeMap } from './util'
const route = useRoute()
const visible = ref(false)
const params = ref<any>()
const editData = ref()
const instanceRef = ref()
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'type',
fixed: 'left',
width: 200,
ellipsis: true,
search: {
type: 'input',
},
},
{
title: '插件名称',
dataIndex: 'name',
key: 'type',
fixed: 'left',
width: 200,
ellipsis: true,
search: {
type: 'input',
},
},
{
title: '插件类型',
dataIndex: 'type',
key: 'type',
scopedSlots: true,
search: {
type: 'select',
options: () => {
return new Promise(resolve => {
getTypes().then(res => {
resolve(res.result?.map(item => ({ ...item, label: item.text })))
})
})
}
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
ellipsis: true,
search: {
type: 'string',
},
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 120,
scopedSlots: true,
},
]
const handleAdd = () => {
visible.value = true
}
const handleSearch = (p: any) => {
params.value = p
}
const save = () => {
if (instanceRef.value) {
instanceRef.value?.reload();
}
visible.value = false
}
const cancel = () => {
visible.value = false
editData.value = undefined
}
const getActions = (data: any) => {
return [
{
key: 'update',
text: '编辑',
tooltip: {
title: '编辑',
},
icon: 'EditOutlined',
onClick: () => {
visible.value = true;
editData.value = data;
},
},
{
key: 'delete',
text: '删除',
tooltip: {
title: '删除',
},
popConfirm: {
title: '确认删除?',
onConfirm: async () => {
const resp = await removeFn(data.id);
if (resp.status === 200) {
message.success('操作成功!');
instanceRef.value?.reload();
} else {
message.error('操作失败!');
}
},
},
icon: 'DeleteOutlined',
},
]
}
onMounted(() => {
if (route.query.save) {
visible.value = true
}
})
</script>
<style scoped lang='less'>
.plugin-version {
border-radius: 4px;
}
</style>

View File

@ -0,0 +1,16 @@
export type FileUploadResult = {
id: string
name: string
description: string
version: string
type: {
text: string
value: string
}
accessUrl: string
filename: string
extension: string
length: string
md5: string
sha256: string
}

View File

@ -0,0 +1,4 @@
export const TypeMap = {
'deviceGateway': '设备接入网关',
'thingsManager': '物管理',
}

View File

@ -185,7 +185,7 @@
<script lang="ts" setup>
import InputSave from './Save/input.vue';
import OutputSave from './save/output.vue';
import OutputSave from './Save/output.vue';
import { getDataExchange } from '@/api/rule-engine/config';
import { getImage } from '@/utils/comm';
import { marked } from 'marked';

View File

@ -101,7 +101,7 @@ import { useRoute } from 'vue-router';
import type { ActionsType } from '@/components/Table';
import { getImage } from '@/utils/comm';
import { message } from 'jetlinks-ui-components';
import Save from './save/index.vue';
import Save from './Save/index.vue';
import { useAlarmConfigurationStore } from '@/store/alarm';
import { storeToRefs } from 'pinia';
const route = useRoute();

View File

@ -374,10 +374,10 @@ const form = reactive<formType>({
form.formValue = {
title: configInfo.front?.title,
headerTheme: configInfo.front?.headerTheme,
logo: configInfo.front?.logo || '/public/logo.png',
ico: configInfo.front?.ico || '/public/favicon.ico',
logo: configInfo.front?.logo || '/logo.png',
ico: configInfo.front?.ico || '/favicon.ico',
backgroud:
configInfo.front?.backgroud || '/public/images/login.png',
configInfo.front?.backgroud || '/images/login.png',
apiKey: configInfo.amap?.apiKey,
'base-path': configInfo.paths?.['base-path'],
};

View File

@ -92,9 +92,9 @@ export default defineConfig(({ mode}) => {
proxy: {
[env.VITE_APP_BASE_API]: {
target: 'http://192.168.32.226:8844',
// target: 'http://192.168.32.226:8844',
// target: 'http://192.168.32.244:8881',
// target: 'http://120.77.179.54:8844', // 120测试
target: 'http://120.77.179.54:8844', // 120测试
// target: 'http://192.168.33.46:8844', // 本地开发环境
ws: 'ws://192.168.33.46:8844',
changeOrigin: true,

150
yarn.lock
View File

@ -1102,6 +1102,16 @@
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-core@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fcompiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8"
integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-dom@3.2.45", "@vue/compiler-dom@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz"
@ -1110,6 +1120,14 @@
"@vue/compiler-core" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-dom@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fcompiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
dependencies:
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/compiler-sfc@3.2.45", "@vue/compiler-sfc@^3.2.29", "@vue/compiler-sfc@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz"
@ -1126,6 +1144,22 @@
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-sfc@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fcompiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.47"
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-ssr" "3.2.47"
"@vue/reactivity-transform" "3.2.47"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-ssr@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz"
@ -1134,6 +1168,14 @@
"@vue/compiler-dom" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/compiler-ssr@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fcompiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee"
integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==
dependencies:
"@vue/compiler-dom" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/devtools-api@^6.4.5":
version "6.4.5"
resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz"
@ -1150,6 +1192,17 @@
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity-transform@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2freactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.47"
"@vue/shared" "3.2.47"
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.45", "@vue/reactivity@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz"
@ -1157,6 +1210,13 @@
dependencies:
"@vue/shared" "3.2.45"
"@vue/reactivity@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2freactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6"
integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==
dependencies:
"@vue/shared" "3.2.47"
"@vue/runtime-core@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz"
@ -1165,6 +1225,14 @@
"@vue/reactivity" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/runtime-core@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fruntime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d"
integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==
dependencies:
"@vue/reactivity" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/runtime-dom@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz"
@ -1174,6 +1242,15 @@
"@vue/shared" "3.2.45"
csstype "^2.6.8"
"@vue/runtime-dom@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fruntime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382"
integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==
dependencies:
"@vue/runtime-core" "3.2.47"
"@vue/shared" "3.2.47"
csstype "^2.6.8"
"@vue/server-renderer@3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz"
@ -1182,11 +1259,24 @@
"@vue/compiler-ssr" "3.2.45"
"@vue/shared" "3.2.45"
"@vue/server-renderer@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fserver-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0"
integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==
dependencies:
"@vue/compiler-ssr" "3.2.47"
"@vue/shared" "3.2.47"
"@vue/shared@3.2.45", "@vue/shared@^3.2.45":
version "3.2.45"
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz"
integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==
"@vue/shared@3.2.47":
version "3.2.47"
resolved "https://registry.jetlinks.cn/@vue%2fshared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
"@vuemap/layer-3dtiles@^0.0.3":
version "0.0.3"
resolved "https://registry.npmjs.org/@vuemap/layer-3dtiles/-/layer-3dtiles-0.0.3.tgz"
@ -1217,13 +1307,13 @@
"@vueuse/core@^7.5.5":
version "7.7.1"
resolved "https://registry.jetlinks.cn/@vueuse%2fcore/-/core-7.7.1.tgz"
resolved "https://registry.jetlinks.cn/@vueuse%2fcore/-/core-7.7.1.tgz#fc284f4103de73c7fb79bc06579d8066790db511"
integrity sha512-PRRgbATMpoeUmkCEBtUeJgOwtew8s+4UsEd+Pm7MhkjL2ihCNrSqxNVtM6NFE4uP2sWnkGcZpCjPuNSxowJ1Ow==
dependencies:
"@vueuse/shared" "7.7.1"
vue-demi "*"
"@vueuse/core@^9.10.0", "@vueuse/core@^9.12.0":
"@vueuse/core@^9.10.0":
version "9.12.0"
resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.12.0.tgz"
integrity sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg==
@ -1233,14 +1323,29 @@
"@vueuse/shared" "9.12.0"
vue-demi "*"
"@vueuse/core@^9.12.0":
version "9.13.0"
resolved "https://registry.jetlinks.cn/@vueuse%2fcore/-/core-9.13.0.tgz#2f69e66d1905c1e4eebc249a01759cf88ea00cf4"
integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==
dependencies:
"@types/web-bluetooth" "^0.0.16"
"@vueuse/metadata" "9.13.0"
"@vueuse/shared" "9.13.0"
vue-demi "*"
"@vueuse/metadata@9.12.0":
version "9.12.0"
resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.12.0.tgz"
integrity sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ==
"@vueuse/metadata@9.13.0":
version "9.13.0"
resolved "https://registry.jetlinks.cn/@vueuse%2fmetadata/-/metadata-9.13.0.tgz#bc25a6cdad1b1a93c36ce30191124da6520539ff"
integrity sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==
"@vueuse/router@^9.13.0":
version "9.13.0"
resolved "http://47.108.170.157:9013/@vueuse%2frouter/-/router-9.13.0.tgz#cfc757fa89c654ab749c60bc2445f945cbb86b32"
resolved "https://registry.jetlinks.cn/@vueuse%2frouter/-/router-9.13.0.tgz#cfc757fa89c654ab749c60bc2445f945cbb86b32"
integrity sha512-lcL6auSUGMGZMdDzZJb02QDe909AChzMXoxqFS3gL2E8mHmIx0SrNor+33UkqvvBPi18vXpDq/R7tPd9fxWwTg==
dependencies:
"@vueuse/shared" "9.13.0"
@ -1248,7 +1353,7 @@
"@vueuse/shared@7.7.1":
version "7.7.1"
resolved "https://registry.jetlinks.cn/@vueuse%2fshared/-/shared-7.7.1.tgz"
resolved "https://registry.jetlinks.cn/@vueuse%2fshared/-/shared-7.7.1.tgz#77e312de7275380efce86b0079bd7938791a076b"
integrity sha512-rN2qd22AUl7VdBxihagWyhUNHCyVk9IpvBTTfHoLH9G7rGE552X1f+zeCfehuno0zXif13jPw+icW/wn2a0rnQ==
dependencies:
vue-demi "*"
@ -1262,7 +1367,7 @@
"@vueuse/shared@9.13.0":
version "9.13.0"
resolved "http://47.108.170.157:9013/@vueuse%2fshared/-/shared-9.13.0.tgz#089ff4cc4e2e7a4015e57a8f32e4b39d096353b9"
resolved "https://registry.jetlinks.cn/@vueuse%2fshared/-/shared-9.13.0.tgz#089ff4cc4e2e7a4015e57a8f32e4b39d096353b9"
integrity sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==
dependencies:
vue-demi "*"
@ -2065,7 +2170,7 @@ colorette@^2.0.16, colorette@^2.0.19:
colorpicker-v3@^2.10.2:
version "2.10.2"
resolved "https://registry.jetlinks.cn/colorpicker-v3/-/colorpicker-v3-2.10.2.tgz"
resolved "https://registry.jetlinks.cn/colorpicker-v3/-/colorpicker-v3-2.10.2.tgz#f5e2f9ea603eee4d227ba10fa436d86963aa2bd0"
integrity sha512-ZWPq5wcugS3NcL7DwYqVSP5mE/x45FK31olGpig+Tko5jUXk0danfEYi1Aei3lgYs+Z0zAfhbhqVuDgOdUs5Mw==
dependencies:
"@vueuse/core" "^7.5.5"
@ -3720,13 +3825,27 @@ jetlinks-store@^0.0.3:
jetlinks-ui-components@1.0.5:
version "1.0.5"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#9fa4680c69471ee9abf518782faf1b4c276aa305"
integrity sha512-pFZ0ol0jjIrrIEqPOFmrS5K623QzczYVMFPf8NQ3XeSBLksW9dncgVEPa6cZZ+9jjwAgWHo2MyPGXZcY6SF8PQ==
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz#f91a7e1e0c72addcc6f2cadb260039010c481eaf"
integrity sha512-ZcR0ukT9bZn2syyOk9lKjjZ1cHpmMBvdHuTqayZgXwq6+pZSM5nqtVMgdUu0AXQ+pL0KbWes4L0NweYSW7XJOg==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"
ant-design-vue "^3.2.15"
colorpicker-v3 "^2.10.2"
jetlinks-ui-components "1.0.5"
lodash-es "^4.17.21"
monaco-editor "^0.35.0"
jetlinks-ui-components@^1.0.8:
version "1.0.8"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.8.tgz#bcbdbbdc6c0011207f15cbb51fadcc8389803f53"
integrity sha512-FdXSS4Wdnq5cCUKP5f6Z/3FHu3XHFkRIzSAvkUQdneHbYO6iHkEjMJyHChttlP9cp4s6ydRpeqY2jjtoftYhtA==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"
ant-design-vue "^3.2.15"
colorpicker-v3 "^2.10.2"
jetlinks-ui-components "1.0.5"
lodash-es "^4.17.21"
monaco-editor "^0.35.0"
@ -4609,7 +4728,7 @@ moment@*, moment@^2.29.4:
monaco-editor@^0.35.0:
version "0.35.0"
resolved "https://registry.jetlinks.cn/monaco-editor/-/monaco-editor-0.35.0.tgz"
resolved "https://registry.jetlinks.cn/monaco-editor/-/monaco-editor-0.35.0.tgz#49c4220c815262a900dacf0ae8a59bef66efab8b"
integrity sha512-BJfkAZ0EJ7JgrgWzqjfBNP9hPSS8NlfECEDMEIIiozV2UaPq22yeuOjgbd3TwMh3anH0krWZirXZfn8KUSxiOA==
monaco-editor@^0.36.0:
@ -6916,7 +7035,18 @@ vue3-ts-jsoneditor@^2.7.1:
vanilla-jsoneditor "^0.7.9"
vue "^3.2.37"
vue@^3.2.25, vue@^3.2.37, vue@^3.2.45:
vue@^3.2.25:
version "3.2.47"
resolved "https://registry.jetlinks.cn/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==
dependencies:
"@vue/compiler-dom" "3.2.47"
"@vue/compiler-sfc" "3.2.47"
"@vue/runtime-dom" "3.2.47"
"@vue/server-renderer" "3.2.47"
"@vue/shared" "3.2.47"
vue@^3.2.37, vue@^3.2.45:
version "3.2.45"
resolved "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz"
integrity sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==