fix: 修改设备物模型-属性来源可以选择规则

* feat: 修改bug优化ui

* fix: 修改物模型导入bug

* fix: 优化设备物模型-规则调试

* fix: 优化设备物模型-属性来源可以修改产品规则

* fix: 修改设备物模型-属性来源可以选择规则
This commit is contained in:
XieYongHong 2023-08-02 11:30:09 +08:00 committed by GitHub
parent 074f351fa1
commit fe0b235405
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 579 additions and 370 deletions

View File

@ -11,7 +11,7 @@ export const save = (data: any) => server.post(`/data-collect/channel`, data);
export const update = (id: string, data: any) => export const update = (id: string, data: any) =>
server.put(`/data-collect/channel/${id}`, data); server.put(`/data-collect/channel/${id}`, data);
export const getProviders = () => server.get(`/gateway/device/providers`); export const getProviders = () => server.get(`/data-collect/channel/providers`);
export const queryOptionsList = (type: string) => export const queryOptionsList = (type: string) =>
server.get(`/data-collect/opc/${type}`); server.get(`/data-collect/opc/${type}`);

View File

@ -61,4 +61,6 @@ export const scanOpcUAList = (data: any) =>
export const queryTypeList = () => server.get(`/data-collect/opc/data-types`); export const queryTypeList = () => server.get(`/data-collect/opc/data-types`);
export const getProviders = () => server.get('/data-collect/channel/gateway/codec/providers') export const getProviders = () => server.get('/data-collect/channel/gateway/codec/providers')
export const getStates = () => server.get('/dictionary/running-state/items')

View File

@ -3,7 +3,9 @@
<j-input-search @search="search" allow-clear placeholder="搜索关键字" /> <j-input-search @search="search" allow-clear placeholder="搜索关键字" />
<div class="tree"> <div class="tree">
<j-tree @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent <j-tree @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent
:tree-data="data"> :tree-data="data"
:showLine="{ showLeafIcon: false }"
:show-icon="true">
<template #title="node"> <template #title="node">
<div class="node"> <div class="node">
<div style="max-width: 180px"><Ellipsis>{{ node.name }}</Ellipsis></div> <div style="max-width: 180px"><Ellipsis>{{ node.name }}</Ellipsis></div>

View File

@ -15,6 +15,8 @@
:field-names="{ title: 'name', key: 'id' }" :field-names="{ title: 'name', key: 'id' }"
auto-expand-parent auto-expand-parent
:tree-data="data" :tree-data="data"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="node"> <template #title="node">
<div class="node"> <div class="node">

View File

@ -40,7 +40,7 @@
</j-button> </j-button>
</template> </template>
</template> </template>
<j-tooltip v-else title="暂无权限,请联系管理员"> <j-tooltip v-else title="暂无权限,请联系管理员" :placement="placement">
<slot v-if="noButton"></slot> <slot v-if="noButton"></slot>
<j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style"> <j-button v-else v-bind="props" :disabled="_isPermission" :style="props.style">
<slot></slot> <slot></slot>
@ -89,6 +89,10 @@ const props = defineProps({
style: { style: {
type: Object as PropType<CSSProperties> type: Object as PropType<CSSProperties>
}, },
placement:{
type: String,
default: 'top'
},
...omit(buttonProps(), 'icon') ...omit(buttonProps(), 'icon')
}) })

View File

@ -53,7 +53,7 @@ export const USER_CENTER_MENU_BUTTON_CODE = 'user-center-passwd-update'
/**协议列表 */ /**协议列表 */
export const protocolList = [ export const protocolList = [
{ label: 'OPC-UA', value: 'OPC_UA', alias: 'opc-ua' }, { label: 'OPC_UA', value: 'OPC_UA', alias: 'opc-ua' },
{ label: 'Modbus/TCP', value: 'MODBUS_TCP', alias: 'modbus-tcp' }, { label: 'MODBUS_TCP', value: 'MODBUS_TCP', alias: 'Modbus/TCP' },
{ label: 'GATEWAY', value: 'COLLECTOR_GATEWAY', alias: 'collector-gateway' }, { label: 'COLLECTOR_GATEWAY', value: 'COLLECTOR_GATEWAY', alias: 'GATEWAY' },
] ]

View File

@ -304,9 +304,9 @@ const getProvidersList = async () => {
if (resp.status === 200) { if (resp.status === 200) {
const arr = resp.result const arr = resp.result
.filter( .filter(
(item: any) => ['collector-gateway', 'modbus-tcp', 'opc-ua'].includes(item.id), (item: any) => ['GATEWAY', 'Modbus/TCP', 'opc-ua'].includes(item.name),
) )
.map((it: any) => it.id); .map((it: any) => it.name);
const providers: any = protocolList.filter((item: any) => const providers: any = protocolList.filter((item: any) =>
arr.includes(item.alias), arr.includes(item.alias),
); );

View File

@ -143,11 +143,12 @@
<script lang="ts" setup name="DataCollectPage"> <script lang="ts" setup name="DataCollectPage">
import type { ActionsType } from '@/components/Table/index'; import type { ActionsType } from '@/components/Table/index';
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import { query, remove, update } from '@/api/data-collect/channel'; import { query, remove, update ,getProviders} from '@/api/data-collect/channel';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import { StatusColorEnum, updateStatus } from './data'; import { StatusColorEnum, updateStatus } from './data';
import { useMenuStore } from 'store/menu'; import { useMenuStore } from 'store/menu';
import Save from './Save/index.vue'; import Save from './Save/index.vue';
import { protocolList } from '@/utils/consts';
import _ from 'lodash'; import _ from 'lodash';
const menuStory = useMenuStore(); const menuStory = useMenuStore();
@ -174,10 +175,20 @@ const columns = [
ellipsis: true, ellipsis: true,
search: { search: {
type: 'select', type: 'select',
options: [ options: async() =>{
{ label: 'OPC_UA', value: 'OPC_UA' }, const resp: any = await getProviders();
{ label: 'MODBUS_TCP', value: 'MODBUS_TCP' }, if (resp.status === 200) {
], const arr = resp.result
.filter(
(item: any) => ['GATEWAY', 'Modbus/TCP', 'opc-ua'].includes(item.name),
)
.map((it: any) => it.name);
const providers: any = protocolList.filter((item: any) =>
arr.includes(item.alias),
);
return providers
}
}
}, },
}, },
{ {
@ -204,8 +215,7 @@ const columns = [
type: 'select', type: 'select',
options: [ options: [
{ label: '运行中', value: 'running' }, { label: '运行中', value: 'running' },
{ label: '部分错误', value: 'partialError' }, { label: '已停止', value: 'stopped' },
{ label: '错误', value: 'failed' },
], ],
}, },
}, },

View File

@ -15,6 +15,8 @@
checkable checkable
@check="onCheck" @check="onCheck"
:height="600" :height="600"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ name, key }"> <template #title="{ name, key }">
<span <span

View File

@ -329,6 +329,8 @@ import {
batchDeletePoint, batchDeletePoint,
removePoint, removePoint,
readPoint, readPoint,
getProviders,
getStates
} from '@/api/data-collect/collector'; } from '@/api/data-collect/collector';
import { onlyMessage } from '@/utils/comm'; import { onlyMessage } from '@/utils/comm';
import PointCardBox from './components/PointCardBox/index.vue'; import PointCardBox from './components/PointCardBox/index.vue';
@ -342,6 +344,7 @@ import { cloneDeep, isNumber, throttle } from 'lodash-es';
import { getWebSocket } from '@/utils/websocket'; import { getWebSocket } from '@/utils/websocket';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { responsiveArray } from 'ant-design-vue/lib/_util/responsiveObserve';
const props = defineProps({ const props = defineProps({
data: { data: {
@ -409,16 +412,14 @@ const columns = [
key: 'provider', key: 'provider',
search: { search: {
type: 'select', type: 'select',
options: [ options: async () =>{
{ const resp:any = await getProviders();
label: 'OPC_UA', if(resp.success){
value: 'OPC_UA', return resp.result.map((item: any) => ({ label: item.name, value: item.id }))
}, }else{
{ return []
label: 'MODBUS_TCP', }
value: 'MODBUS_TCP', }
},
],
}, },
}, },
{ {
@ -436,16 +437,16 @@ const columns = [
key: 'runningState', key: 'runningState',
search: { search: {
type: 'select', type: 'select',
options: [ options: async() =>{
{ const resq:any = await getStates();
label: '运行中', if(resq.status === 200){
value: 'running', return resq.result.map((item:any)=>(
}, {label:item.text,value:item.value}
{ ))
label: '已停止', }else{
value: 'stopped', return []
}, }
], }
}, },
}, },
{ {
@ -542,7 +543,7 @@ const getQuantity = (item: Partial<Record<string, any>>) => {
}; };
const getAddress = (item: Partial<Record<string, any>>) => { const getAddress = (item: Partial<Record<string, any>>) => {
const { address } = item.configuration?.parameter || ''; const { address } = item.configuration?.parameter || '';
return !!address ? address + '(地址)' : ''; return (!!address || address === 0) ? address + '(地址)' : '';
}; };
const getScaleFactor = (item: Partial<Record<string, any>>) => { const getScaleFactor = (item: Partial<Record<string, any>>) => {
const { scaleFactor } = item.configuration?.codec?.configuration || ''; const { scaleFactor } = item.configuration?.codec?.configuration || '';

View File

@ -134,16 +134,16 @@
</div> </div>
<j-form-item <j-form-item
:name="['configuration', 'requsetTimeout']" :name="['configuration', 'requsetTimeout']"
label="请求超时时间配置" :rules="LeftTreeRules.requsetTimeout"
> >
<j-input-number <j-input-number
style="width: 100%" style="width: 100%"
placeholder="请输入请求超时时间配置" placeholder="请输入请求超时时间配置"
v-model:value="formData.configuration.requsetTimeout" v-model:value="formData.configuration.requsetTimeout"
addon-after="ms" addon-after="ms"
:max="2147483648" :max="60000"
:min="1" :min="2000"
/> />
</j-form-item> </j-form-item>
<j-form-item label="说明" name="description"> <j-form-item label="说明" name="description">
<j-textarea <j-textarea

View File

@ -31,6 +31,8 @@
:height="660" :height="660"
@select='treeSelect' @select='treeSelect'
defaultExpandAll defaultExpandAll
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ name, data }"> <template #title="{ name, data }">
<Ellipsis class="tree-left-title"> <Ellipsis class="tree-left-title">
@ -54,7 +56,7 @@
class="tree-left-tag" class="tree-left-tag"
v-if="data.id !== '*'" v-if="data.id !== '*'"
:color="colorMap.get(data?.uniformState?.value)" :color="colorMap.get(data?.uniformState?.value)"
>{{ data?.uniformState?.text }}</j-tag >{{ data?.pointNumber === 0 ? '--' : (data?.uniformState?.value === 'normal' ? '运行中' : data?.uniformState?.text) }}</j-tag
> >
<j-tag <j-tag
class="tree-left-tag2" class="tree-left-tag2"
@ -65,7 +67,7 @@
" "
> >
{{ {{
data?.state?.text data?.state?.value === 'disabled' ? '禁用' : '运行中'
}} }}
</j-tag </j-tag
> >

View File

@ -57,7 +57,7 @@ export const ModBusRules = {
}, },
{ {
pattern: regOnlyNumber, pattern: regOnlyNumber,
message: '请输入0-999999999之间的正整数', message: '请输入0-999999之间的正整数',
}, },
], ],
quantity: [ quantity: [
@ -170,6 +170,9 @@ export const LeftTreeRules = {
endianIn: [ endianIn: [
{ required: true, message: '请选择单字高低位切换', trigger: 'blur' }, { required: true, message: '请选择单字高低位切换', trigger: 'blur' },
], ],
requsetTimeout:[
{ pattern: /^\d+$/, message:'请输入2000-60000的正整数',trigger: 'change'}
]
}; };
export const FormTableColumns = [ export const FormTableColumns = [

View File

@ -83,7 +83,8 @@ const handleOptions = (x = [], y = []) => {
const chart: any = chartRef.value; const chart: any = chartRef.value;
if (chart) { if (chart) {
const myChart = echarts.init(chart); const myChart = echarts.init(chart);
const maxY: number = y.sort((a,b)=>{ const _y = [...y];
const maxY: number = _y.sort((a,b)=>{
return b-a return b-a
})?.[0] })?.[0]
const options = { const options = {

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="box"> <j-scrollbar :height="`calc(100% - 51px)`">
<div class="box">
<div class="content"> <div class="content">
<template v-if="bindList.length"> <template v-if="bindList.length">
<div <div
@ -47,7 +48,8 @@
</template> </template>
<j-empty v-else style="margin: 200px 0" /> <j-empty v-else style="margin: 200px 0" />
</div> </div>
</div> </div>
</j-scrollbar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="notification-record-container"> <div class="notification-record-container">
<pro-search <pro-search
:columns="columns" :columns="columns"
:target="type" :target="type"
@ -14,6 +14,7 @@
:params="queryParams" :params="queryParams"
:bodyStyle="{ padding: 0 }" :bodyStyle="{ padding: 0 }"
:defaultParams="defaultParams" :defaultParams="defaultParams"
:scroll="{y:420}"
> >
<!-- <template #rightExtraRender> <!-- <template #rightExtraRender>
<j-popconfirm title="确认全部已读?" @confirm="onAllRead"> <j-popconfirm title="确认全部已读?" @confirm="onAllRead">
@ -75,7 +76,7 @@
:data="viewItem" :data="viewItem"
:type="type" :type="type"
/> />
</div> </div>
</template> </template>
<script setup lang="ts" name="NotificationRecord"> <script setup lang="ts" name="NotificationRecord">

View File

@ -11,7 +11,7 @@
:key="item.provider" :key="item.provider"
:tab="item.name" :tab="item.name"
> >
<NotificationRecord :type="item.provider" /> <NotificationRecord :type="item.provider" />
</j-tab-pane> </j-tab-pane>
</j-tabs> </j-tabs>
<j-empty v-else style="margin: 200px 0" /> <j-empty v-else style="margin: 200px 0" />

View File

@ -1,5 +1,6 @@
<template> <template>
<j-spin :spinning="loading"> <j-scrollbar :height="`calc(100% - 51px)`">
<j-spin :spinning="loading">
<div style="padding: 0 10px"> <div style="padding: 0 10px">
<div class="alert"> <div class="alert">
<AIcon type="InfoCircleOutlined" /> <AIcon type="InfoCircleOutlined" />
@ -47,7 +48,8 @@
<j-empty style="margin: 200px 0" v-else /> <j-empty style="margin: 200px 0" v-else />
</div> </div>
</div> </div>
</j-spin> </j-spin>
</j-scrollbar>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -53,9 +53,7 @@
:tab="item.title" :tab="item.title"
/> />
</j-tabs> </j-tabs>
<j-scrollbar :height="`calc(100% - 51px)`"> <component :is="tabs[user.tabKey]" />
<component :is="tabs[user.tabKey]" />
</j-scrollbar>
</div> </div>
</div> </div>
</div> </div>

View File

@ -21,7 +21,6 @@
<j-form-item label="名称" name="name"> <j-form-item label="名称" name="name">
<j-input <j-input
v-model:value="formModel.name" v-model:value="formModel.name"
:maxlength="64"
placeholder="请输入名称" placeholder="请输入名称"
/> />
</j-form-item> </j-form-item>
@ -31,6 +30,7 @@
id="inputNumber" id="inputNumber"
v-model:value="formModel.sortIndex" v-model:value="formModel.sortIndex"
:min="1" :min="1"
:max="9999"
placeholder="请输入排序" placeholder="请输入排序"
/> />
</j-form-item> </j-form-item>
@ -102,7 +102,11 @@ const rules = ref({
message: '最多可输入64个字符', message: '最多可输入64个字符',
}, },
], ],
sortIndex: [{ required: true, message: '请输入排序', trigger: 'blur' }], sortIndex: [{ required: true, message: '请输入排序', trigger: 'blur' },{
pattern:/^\d+$/,
message:'请输入正整数',
trigger:'change'
}],
}); });
const visible = ref(false); const visible = ref(false);
const { resetFields, validate, validateInfos } = useForm( const { resetFields, validate, validateInfos } = useForm(
@ -174,7 +178,7 @@ const show = async (row: any) => {
childArr.value = row.children.sort(compare('sortIndex')); childArr.value = row.children.sort(compare('sortIndex'));
formModel.value = { formModel.value = {
name: '', name: '',
sortIndex: sortIndex:childArr.value[childArr.value.length - 1].sortIndex === 9999 ? childArr.value[childArr.value.length - 1].sortIndex :
childArr.value[childArr.value.length - 1].sortIndex + 1, childArr.value[childArr.value.length - 1].sortIndex + 1,
description: '', description: '',
}; };
@ -186,7 +190,7 @@ const show = async (row: any) => {
if (arr.value.length > 0) { if (arr.value.length > 0) {
formModel.value = { formModel.value = {
name: '', name: '',
sortIndex: arr.value[arr.value.length - 1].sortIndex + 1, sortIndex: arr.value[arr.value.length - 1].sortIndex === 9999 ? arr.value[arr.value.length - 1].sortIndex : arr.value[arr.value.length - 1].sortIndex + 1,
description: '', description: '',
}; };
} }

View File

@ -107,11 +107,15 @@ const query = reactive({
key: 'sortIndex', key: 'sortIndex',
search: { search: {
type: 'number', type: 'number',
componentProps:{
precision:0,
min:0
}
}, },
scopedSlots: true, scopedSlots: true,
}, },
{ {
title: '描述', title: '说明',
key: 'description', key: 'description',
dataIndex: 'description', dataIndex: 'description',
search: { search: {

View File

@ -150,7 +150,7 @@ const menuStore = useMenuStore();
* 获取产品数量 * 获取产品数量
*/ */
const getProductData = () => { const getProductData = () => {
if (menuStore.hasMenu('device/Product')) { // if (menuStore.hasMenu('device/Product')) {
productCount().then((res) => { productCount().then((res) => {
if (res.status == 200) { if (res.status == 200) {
productTotal.value = res.result; productTotal.value = res.result;
@ -180,14 +180,14 @@ const getProductData = () => {
productFooter.value[1].value = res.result; productFooter.value[1].value = res.result;
} }
}); });
} // }
}; };
getProductData(); getProductData();
/** /**
* 获取设备数量 * 获取设备数量
*/ */
const getDeviceData = () => { const getDeviceData = () => {
if (menuStore.hasMenu('device/Instance')) { // if (menuStore.hasMenu('device/Instance')) {
deviceCount().then((res) => { deviceCount().then((res) => {
if (res.status == 200) { if (res.status == 200) {
deviceTotal.value = res.result; deviceTotal.value = res.result;
@ -206,7 +206,7 @@ const getDeviceData = () => {
} }
}, },
); );
} // }
}; };
getDeviceData(); getDeviceData();
/** /**
@ -214,7 +214,7 @@ getDeviceData();
*/ */
const getOnline = () => { const getOnline = () => {
const startTime = dayjs().subtract(0, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'); const startTime = dayjs().subtract(0, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss');
const endTime = dayjs().subtract(0, 'days').endOf('day').format('YYYY-MM-DD HH:mm:ss'); const endTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
dashboard([ dashboard([
{ {
@ -481,7 +481,7 @@ const setDevMesChartOption = (
// //
const getDevice = () => { const getDevice = () => {
const startTime = dayjs().subtract(0, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'); const startTime = dayjs().subtract(0, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss');
const endTime = dayjs().subtract(0, 'days').endOf('day').format('YYYY-MM-DD HH:mm:ss'); const endTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
dashboard([ dashboard([
{ {

View File

@ -21,6 +21,8 @@
:load-data="onLoadData" :load-data="onLoadData"
@check="onCheck" @check="onCheck"
v-model:expandedKeys="expandedKeys" v-model:expandedKeys="expandedKeys"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
/> />
</j-card> </j-card>
<div style="width: 100px"> <div style="width: 100px">

View File

@ -177,7 +177,7 @@ const funcChange = (val: string) => {
}; };
const saveBtn = async () => { const saveBtn = async () => {
const _inputs = await inputsRef.value.onSave(); const _inputs = await inputsRef.value?.onSave();
console.log(_inputs) console.log(_inputs)
if(!_inputs){ if(!_inputs){
return return

View File

@ -21,6 +21,8 @@
:load-data="onLoadData" :load-data="onLoadData"
@check="onCheck" @check="onCheck"
v-model:expandedKeys="expandedKeys" v-model:expandedKeys="expandedKeys"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
/> />
</j-card> </j-card>
<div style="width: 100px"> <div style="width: 100px">

View File

@ -19,6 +19,8 @@
:tree-data="dataSource" :tree-data="dataSource"
:checkedKeys="checkedKeys" :checkedKeys="checkedKeys"
@check="onCheck" @check="onCheck"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
/> />
</j-card> </j-card>
<div style="width: 100px"> <div style="width: 100px">

View File

@ -26,18 +26,22 @@
<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="接入方式">
<PermissionButton <PermissionButton
type="link" type="link"
@click="changeTables" style="width:100%;padding:0"
hasPermission="device/Product:update" @click="changeTables"
>{{ hasPermission="device/Product:update"
productStore.current.accessName >
? productStore.current.accessName <div style="white-space: normal">
: '配置接入方式' <Ellipsis>{{
}}</PermissionButton productStore.current.accessName
> ? productStore.current.accessName
: '配置接入方式'
}}</Ellipsis>
</div>
</PermissionButton
>
</j-descriptions-item> </j-descriptions-item>
<j-descriptions-item label="创建时间">{{ <j-descriptions-item label="创建时间">{{
dayjs(productStore.current.createTime).format('YYYY-MM-DD HH:mm:ss') dayjs(productStore.current.createTime).format('YYYY-MM-DD HH:mm:ss')

View File

@ -23,11 +23,13 @@
v-if="productStore.current.state === 1" v-if="productStore.current.state === 1"
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
:disabled="!permissionStore.hasPermission('device/Product:action')"
> >
<j-switch <j-switch
:checked="productStore.current.state === 1" :checked="productStore.current.state === 1"
checked-children="正常" checked-children="正常"
un-checked-children="禁用" un-checked-children="禁用"
:disabled="!permissionStore.hasPermission('device/Product:action')"
/> />
</j-popconfirm> </j-popconfirm>
<j-popconfirm <j-popconfirm
@ -36,6 +38,7 @@
v-if="productStore.current.state === 0" v-if="productStore.current.state === 0"
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
:disabled="!permissionStore.hasPermission('device/Product:action')"
> >
<j-switch <j-switch
:unCheckedValue=" :unCheckedValue="
@ -43,6 +46,7 @@
" "
checked-children="正常" checked-children="正常"
un-checked-children="禁用" un-checked-children="禁用"
:disabled="!permissionStore.hasPermission('device/Product:action')"
/> />
</j-popconfirm> </j-popconfirm>
</div> </div>
@ -87,6 +91,7 @@
: undefined : undefined
" "
hasPermission="device/Product:update" hasPermission="device/Product:update"
placement="topRight"
>应用配置</PermissionButton >应用配置</PermissionButton
> >
</template> </template>
@ -124,7 +129,9 @@ import { getImage, handleParamsToString, onlyMessage } from '@/utils/comm';
import { useMenuStore } from '@/store/menu'; import { useMenuStore } from '@/store/menu';
import { useRouterParams } from '@/utils/hooks/useParams'; import { useRouterParams } from '@/utils/hooks/useParams';
import {EventEmitter} from "@/utils/utils"; import {EventEmitter} from "@/utils/utils";
import { usePermissionStore } from '@/store/permission';
const permissionStore = usePermissionStore()
const menuStory = useMenuStore(); const menuStory = useMenuStore();
const route = useRoute(); const route = useRoute();
const checked = ref<boolean>(true); const checked = ref<boolean>(true);

View File

@ -101,6 +101,10 @@ defineExpose({
} }
.product-id { .product-id {
margin: 10px 15px 10px 0px; margin: 10px 15px 10px 0px;
max-width: 520px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
} }
.product-btn { .product-btn {
margin: 10px 0px 10px 0; margin: 10px 0px 10px 0;

View File

@ -306,6 +306,7 @@ const getActions = (
icon: 'icon-xiazai', icon: 'icon-xiazai',
onClick: () => { onClick: () => {
console.log(data);
const extra = omit(data, [ const extra = omit(data, [
'transportProtocol', 'transportProtocol',
'protocolName', 'protocolName',
@ -314,7 +315,7 @@ const getActions = (
'accessProvider', 'accessProvider',
'messageProtocol', 'messageProtocol',
]); ]);
downloadObject(extra, '产品'); downloadObject(extra, data.name+'产品');
}, },
}, },
{ {
@ -389,6 +390,7 @@ const beforeUpload = (file: any) => {
reader.onload = async (result) => { reader.onload = async (result) => {
const text = result.target?.result; const text = result.target?.result;
console.log('text: ', text); console.log('text: ', text);
console.log(file);
if (!file.type.includes('json')) { if (!file.type.includes('json')) {
onlyMessage('请上传json格式文件', 'error'); onlyMessage('请上传json格式文件', 'error');
return false; return false;
@ -398,7 +400,7 @@ const beforeUpload = (file: any) => {
// //
data.state = 0; data.state = 0;
if (Array.isArray(data)) { if (Array.isArray(data)) {
onlyMessage('请上传json格式文件', 'error'); onlyMessage('文件内容不能为空', 'error');
return false; return false;
} }
delete data.state; delete data.state;
@ -409,7 +411,7 @@ const beforeUpload = (file: any) => {
} }
return true; return true;
} catch { } catch {
// onlyMessage('json', 'error'); onlyMessage('请上传json格式文件', 'error');
} }
return true; return true;
}; };

View File

@ -26,6 +26,7 @@
: '新增', : '新增',
}" }"
@click="handleAddClick()" @click="handleAddClick()"
placement="topRight"
> >
新增 新增
</PermissionButton> </PermissionButton>

View File

@ -97,6 +97,7 @@ const handleConvertMetadata = (key: Key) => {
convertMetadata('to', 'alink', JSON.parse(metadata.value)).then(res => { convertMetadata('to', 'alink', JSON.parse(metadata.value)).then(res => {
if (res.status === 200) { if (res.status === 200) {
value.value = JSON.stringify(res.result) value.value = JSON.stringify(res.result)
monacoValue.value = JSON.stringify(res.result)
} }
}); });
} }

View File

@ -1,318 +1,443 @@
<template> <template>
<j-modal :mask-closable="false" title="导入物模型" destroy-on-close v-model:visible="_visible" @cancel="close" <j-modal
@ok="handleImport" :confirm-loading="loading"> :mask-closable="false"
<div class="import-content"> title="导入物模型"
<p class="import-tip"> destroy-on-close
<AIcon type="ExclamationCircleOutlined" style="margin-right: 5px" /> v-model:visible="_visible"
<template v-if="type === 'product'"> @cancel="close"
导入的物模型会覆盖原来的属性功能事件标签请谨慎操作 @ok="handleImport"
</template> :confirm-loading="loading"
<template v-else> >
导入时会根据标识跳过继承自产品物模型的属性功能事件标签 <div class="import-content">
</template> <p class="import-tip">
</p> <AIcon
</div> type="ExclamationCircleOutlined"
<j-form layout="vertical" v-model="formModel"> style="margin-right: 5px"
<j-form-item v-if="type === 'product'" label="导入方式" v-bind="validateInfos.type"> />
<j-select v-model:value="formModel.type"> <template v-if="type === 'product'">
<j-select-option value="copy">拷贝产品</j-select-option> 导入的物模型会覆盖原来的属性功能事件标签请谨慎操作
<j-select-option value="import">导入物模型</j-select-option> </template>
</j-select> <template v-else>
</j-form-item> 导入时会根据标识跳过继承自产品物模型的属性功能事件标签
<j-form-item label="选择产品" v-bind="validateInfos.copy" v-if="formModel.type === 'copy'"> </template>
<j-select :options="productList" v-model:value="formModel.copy" option-filter-prop="label" showSearch></j-select> </p>
</j-form-item> </div>
<j-form-item label="物模型类型" v-bind="validateInfos.metadata" v-if="type === 'device' || formModel.type === 'import'"> <j-form layout="vertical" ref="formRef" :model="formModel">
<j-select v-model:value="formModel.metadata"> <j-form-item
<j-select-option value="jetlinks">Jetlinks物模型</j-select-option> v-if="type === 'product'"
<j-select-option value="alink">阿里云物模型TSL</j-select-option> label="导入方式"
</j-select> name="type"
</j-form-item> :rules="[
<j-form-item label="导入类型" v-bind="validateInfos.metadataType" {
v-if="type === 'device' || formModel.type === 'import'"> required: true,
<j-select v-model:value="formModel.metadataType"> message: '请选择导入方式',
<j-select-option value="file">文件上传</j-select-option> },
<j-select-option value="script">脚本</j-select-option> ]"
</j-select> >
</j-form-item> <j-select v-model:value="formModel.type">
<j-form-item v-if="formModel.type === 'import' && formModel.metadataType === 'file'" label="文件上传" v-bind="validateInfos.upload"> <j-select-option value="copy">拷贝产品</j-select-option>
<j-input v-model:value="formModel.upload"> <j-select-option value="import">导入物模型</j-select-option>
<template #addonAfter> </j-select>
<j-upload v-model:file-list="fileList" :before-upload="beforeUpload" accept=".json" :show-upload-list="false" </j-form-item>
:action="FILE_UPLOAD" @change="fileChange" :headers="{ 'X-Access-Token': getToken() }"> <j-form-item
<AIcon type="UploadOutlined" class="upload-button" /> label="选择产品"
</j-upload> :rules="[
</template> {
</j-input> required: true,
</j-form-item> message: '请选择产品',
<j-form-item v-bind="validateInfos.import" v-if="(type === 'device' || formModel.type === 'import') && formModel.metadataType === 'script'"> },
<template #label> ]"
<j-space> name="copy"
物模型 v-if="formModel.type === 'copy'"
<j-tooltip title="在线编辑器中编写物模型脚本"> >
<AIcon type="QuestionCircleOutlined" style="color: rgb(136, 136, 136);"/> <j-select
</j-tooltip> :options="productList"
</j-space> v-model:value="formModel.copy"
</template> option-filter-prop="label"
<JMonacoEditor v-model="formModel.import" theme="vs" style="height: 300px" lang="json"></JMonacoEditor> placeholder="请选择产品"
</j-form-item> showSearch
</j-form> ></j-select>
</j-modal> </j-form-item>
<j-form-item
label="物模型类型"
:rules="[
{
required: true,
message: '请选择物模型类型',
},
]"
name="metadata"
v-if="type === 'device' || formModel.type === 'import'"
>
<j-select v-model:value="formModel.metadata">
<j-select-option value="jetlinks"
>Jetlinks物模型</j-select-option
>
<j-select-option value="alink"
>阿里云物模型TSL</j-select-option
>
</j-select>
</j-form-item>
<j-form-item
label="导入类型"
:rules="[
{
required: true,
message: '请选择导入类型',
},
]"
name="metadataType"
v-if="type === 'device' || formModel.type === 'import'"
>
<j-select v-model:value="formModel.metadataType" @change="formModel.import = undefined">
<j-select-option value="file">文件上传</j-select-option>
<j-select-option value="script">脚本</j-select-option>
</j-select>
</j-form-item>
<j-form-item
v-if="
formModel.type === 'import' &&
formModel.metadataType === 'file'
"
label="文件上传"
name="import"
:rules="[
{
required: true,
message: '请上传文件',
},
]"
>
<!-- <j-input v-model:value="formModel.upload">
<template #addonAfter>
<j-upload
v-model:file-list="fileList"
:before-upload="beforeUpload"
accept=".json"
:show-upload-list="false"
:action="FILE_UPLOAD"
@change="fileChange"
:headers="{ 'X-Access-Token': getToken() }"
>
<AIcon
type="UploadOutlined"
class="upload-button"
/>
</j-upload>
</template>
</j-input> -->
<j-upload
v-model:file-list="fileList"
:before-upload="beforeUpload"
accept=".json"
:show-upload-list="false"
:action="FILE_UPLOAD"
@change="fileChange"
:headers="{ 'X-Access-Token': getToken() }"
>
<j-button>
<template #icon><AIcon type="UploadOutlined" /></template>
上传文件
</j-button>
</j-upload>
<div style="margin-left: 10px; color: rgba(0, 0, 0, .6);">支持扩展名.json</div>
</j-form-item>
<j-form-item
:rules="[
{
required: true,
message: '请输入物模型',
},
]"
name="import"
v-if="
(type === 'device' || formModel.type === 'import') &&
formModel.metadataType === 'script'
"
>
<template #label>
<j-space>
物模型
<j-tooltip title="在线编辑器中编写物模型脚本">
<AIcon
type="QuestionCircleOutlined"
style="color: rgb(136, 136, 136)"
/>
</j-tooltip>
</j-space>
</template>
<JMonacoEditor
v-model:modelValue="formModel.import"
theme="vs"
style="height: 300px"
lang="json"
></JMonacoEditor>
</j-form-item>
</j-form>
</j-modal>
</template> </template>
<script setup lang="ts" name="Import"> <script setup lang="ts" name="Import">
import { useForm } from 'ant-design-vue/es/form'; import { saveMetadata } from '@/api/device/instance';
import { saveMetadata } from '@/api/device/instance' import {
import { queryNoPagingPost, convertMetadata, modify } from '@/api/device/product' queryNoPagingPost,
convertMetadata,
modify,
} from '@/api/device/product';
import type { DefaultOptionType } from 'ant-design-vue/es/select'; import type { DefaultOptionType } from 'ant-design-vue/es/select';
import type { UploadProps, UploadFile, UploadChangeParam } from 'ant-design-vue/es'; import type {
import type { DeviceMetadata } from '@/views/device/Product/typings' UploadProps,
import { useInstanceStore } from '@/store/instance' UploadFile,
UploadChangeParam,
} from 'ant-design-vue/es';
import type { DeviceMetadata } from '@/views/device/Product/typings';
import { useInstanceStore } from '@/store/instance';
import { useProductStore } from '@/store/product'; import { useProductStore } from '@/store/product';
import { FILE_UPLOAD } from '@/api/comm'; import { FILE_UPLOAD } from '@/api/comm';
import { getToken, onlyMessage } from '@/utils/comm'; import { getToken, onlyMessage } from '@/utils/comm';
import { useMetadataStore } from '@/store/metadata'; import { useMetadataStore } from '@/store/metadata';
import {omit} from "lodash-es"; import { omit, uniqBy } from 'lodash-es';
import { Modal } from 'jetlinks-ui-components' import { Modal } from 'jetlinks-ui-components';
const route = useRoute() const route = useRoute();
const instanceStore = useInstanceStore() const instanceStore = useInstanceStore();
const productStore = useProductStore() const productStore = useProductStore();
interface Props { interface Props {
visible: boolean, visible: boolean;
type: 'device' | 'product', type: 'device' | 'product';
} }
interface Emits { interface Emits {
(e: 'update:visible', data: boolean): void; (e: 'update:visible', data: boolean): void;
(e: 'submit', data: any): void; (e: 'submit', data: any): void;
} }
const props = defineProps<Props>() const props = defineProps<Props>();
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>();
const loading = ref(false) const loading = ref(false);
const _visible = computed({ const _visible = computed({
get: () => { get: () => {
return props.visible; return props.visible;
}, },
set: (val: any) => { set: (val: any) => {
emits('update:visible', val); emits('update:visible', val);
}, },
}) });
const close = () => { const close = () => {
emits('update:visible', false); emits('update:visible', false);
} };
/** form表单 */ /** form表单 */
const formModel = reactive<Record<string, any>>({ const formModel = reactive<Record<string, any>>({
type: 'import', type: 'import',
metadata: 'jetlinks', metadata: 'jetlinks',
metadataType: 'script', metadataType: 'script',
}) });
const rules = reactive({ // const { validate, validateInfos } = useForm(formModel, rules);
type: [ const fileList = ref<UploadFile[]>([]);
{ const hasVirtualRule = ref(false);
required: true, const formRef = ref();
message: '请选择导入方式',
},
],
copy: [
{
required: true,
message: '请选择产品',
},
],
metadata: [
{
required: true,
message: '请选择物模型类型',
},
],
metadataType: [
{
required: true,
message: '请选择导入类型',
},
],
upload: [
{
required: true,
message: '请上传文件',
},
],
import: [
{
required: true,
message: '请输入物模型',
},
],
})
const { validate, validateInfos } = useForm(formModel, rules);
const fileList = ref<UploadFile[]>([])
const hasVirtualRule = ref(false)
const productList = ref<DefaultOptionType[]>([]) const productList = ref<DefaultOptionType[]>([]);
const loadData = async () => { const loadData = async () => {
const { id } = route.params || {} const { id } = route.params || {};
const product = await queryNoPagingPost({ const product = (await queryNoPagingPost({
paging: false, paging: false,
sorts: [{ name: 'createTime', order: 'desc' }], sorts: [{ name: 'createTime', order: 'desc' }],
terms: [{ column: 'id$not', value: id }], terms: [{ column: 'id$not', value: id }],
}) as any })) as any;
productList.value = product.result.filter((i: any) => i?.metadata).map((item: any) => ({ productList.value = product.result
label: item.name, .filter((i: any) => i?.metadata)
value: item.metadata, .map((item: any) => ({
key: item.id label: item.name,
})) as DefaultOptionType[] value: item.metadata,
} key: item.id,
loadData() })) as DefaultOptionType[];
};
loadData();
const beforeUpload: UploadProps['beforeUpload'] = file => { const beforeUpload: UploadProps['beforeUpload'] = (file) => {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsText(file); reader.readAsText(file);
reader.onload = (json) => { reader.onload = (json) => {
formModel.import = json.target?.result; formModel.import = json.target?.result;
}; };
} };
const fileChange = (info: UploadChangeParam) => { const fileChange = (info: UploadChangeParam) => {
if (info.file.status === 'done') { if (info.file.status === 'done') {
const { response } = info.file const { response } = info.file;
if (response.status === 200) { if (response.status === 200) {
formModel.upload = response.result formModel.upload = response.result;
}
} }
} };
}
const operateLimits = (mdata: DeviceMetadata) => { const operateLimits = (mdata: DeviceMetadata) => {
hasVirtualRule.value = false hasVirtualRule.value = false;
const obj: DeviceMetadata = { ...mdata }; const obj: DeviceMetadata = { ...mdata };
const old = JSON.parse(instanceStore.detail?.metadata || '{}'); const old = JSON.parse(instanceStore.detail?.metadata || '{}');
const fid = instanceStore.detail?.features?.map(item => item.id); const fid = instanceStore.detail?.features?.map((item) => item.id);
if (fid?.includes('eventNotModifiable')) { const _data: DeviceMetadata = {
obj.events = old?.events || []; properties: [],
} events: [],
if (fid?.includes('propertyNotModifiable')) { functions: [],
obj.properties = old?.properties || []; tags: []
}
(obj?.events || []).map((item, index) => {
return { ...item, sortsIndex: index };
});
(obj?.properties || []).map((item, index) => {
if (item.expands?.source === 'rule') {
hasVirtualRule.value = true
item.expands = omit(item.expands, ['virtualRule'])
} }
return { ...item, sortsIndex: index }; _data.properties = uniqBy([...(obj?.properties || []), ...(old?.properties || [])], 'id')
}); _data.events = uniqBy([...(obj?.events || []), ...(old?.events || [])], 'id')
(obj?.functions || []).map((item, index) => { _data.functions = uniqBy([...(obj?.functions || []), ...(old?.functions || [])], 'id')
return { ...item, sortsIndex: index }; _data.tags = uniqBy([...(obj?.tags || []), ...(old?.tags || [])], 'id')
});
(obj?.tags || []).map((item, index) => { if (fid?.includes('eventNotModifiable')) {
return { ...item, sortsIndex: index }; _data.events = old?.events || [];
}); }
return obj; if (fid?.includes('propertyNotModifiable')) {
_data.properties = old?.properties || [];
}
(_data?.properties || []).map((item) => {
if (item.expands?.source === 'rule') {
hasVirtualRule.value = true;
item.expands = omit(item.expands, ['virtualRule']);
}
return item
});
return _data;
}; };
const metadataStore = useMetadataStore() const metadataStore = useMetadataStore();
const handleImport = async () => { const handleImport = async () => {
validate().then(async (data) => { formRef.value.validate().then(async (data: any) => {
loading.value = true const { id } = route.params || {};
const { id } = route.params || {} if (data.metadata === 'alink') {
if (data.metadata === 'alink') { try {
const res = await convertMetadata('from', 'alink', JSON.parse(data.import)).catch(err => err) const _import = JSON.parse(data.import);
if (res.status === 200) { loading.value = true;
const metadata = operateLimits(res.result) const res = await convertMetadata(
let result; 'from',
if (props?.type === 'device') { 'alink',
result = await saveMetadata(id as string, metadata).catch(err => err) _import,
).catch((err) => err);
if (res.status === 200) {
const metadata = operateLimits(res.result);
let result;
if (props?.type === 'device') {
result = await saveMetadata(
id as string,
metadata,
).catch((err) => err);
} else {
result = await modify(id as string, {
id,
metadata: JSON.stringify(metadata),
}).catch((err) => err);
}
if (result.success) {
onlyMessage('导入成功');
}
loading.value = false;
} else {
loading.value = false;
return;
}
if (props?.type === 'device') {
await instanceStore.refresh(id as string);
} else {
await productStore.getDetail(id as string);
}
metadataStore.set('importMetadata', true);
close();
} catch (e) {
onlyMessage(
e === 'error'
? '物模型数据不正确'
: '上传json格式的物模型文件',
'error',
);
}
} else { } else {
result = await modify(id as string, { id, metadata: JSON.stringify(metadata) }).catch(err => err) try {
const _object = JSON.parse(
data[data?.type === 'copy' ? 'copy' : 'import'] ||
'{}',
);
if (
!(
!!_object?.properties ||
!!_object?.events ||
!!_object?.functions ||
!!_object?.tags
)
) {
onlyMessage('物模型数据不正确', 'error');
loading.value = false;
return;
}
const { id } = route.params || {};
const copyOperateLimits = operateLimits(
_object as DeviceMetadata,
);
const params = {
id,
metadata: JSON.stringify(copyOperateLimits),
};
const paramsDevice = copyOperateLimits;
let resp = undefined;
loading.value = true;
if (props?.type === 'device') {
resp = await saveMetadata(id as string, paramsDevice);
} else {
resp = await modify(id as string, params);
}
loading.value = false;
if (resp.success) {
onlyMessage('导入成功');
if (hasVirtualRule.value) {
setTimeout(() => {
Modal.info({
title: '导入数据存在虚拟属性,请及时添加虚拟属性计算规则。',
okText: '确认',
});
}, 300);
}
}
if (props?.type === 'device') {
await instanceStore.refresh(id as string);
} else {
await productStore.getDetail(id as string);
}
metadataStore.set('importMetadata', true);
close();
} catch (e) {
loading.value = false;
onlyMessage(
e === 'error'
? '物模型数据不正确'
: '上传json格式的物模型文件',
'error',
);
}
} }
if (result.success) { });
onlyMessage('导入成功') };
}
loading.value = false
} else {
loading.value = false
// onlyMessage('!', 'error')
return
}
if (props?.type === 'device') {
await instanceStore.refresh(id as string)
} else {
await productStore.getDetail(id as string)
}
metadataStore.set('importMetadata', true)
// Store.set(SystemConst.GET_METADATA, true)
// Store.set(SystemConst.REFRESH_METADATA_TABLE, true)
close()
} else {
try {
const _object = JSON.parse(data[props?.type === 'device' ? 'import' : data?.type] || '{}')
if (
!(!!_object?.properties || !!_object?.events || !!_object?.functions || !!_object?.tags)
) {
onlyMessage('物模型数据不正确', 'error')
loading.value = false;
return;
}
const { id } = route.params || {}
const copyOperateLimits = operateLimits(_object as DeviceMetadata)
const params = {
id,
metadata: JSON.stringify(copyOperateLimits),
};
const paramsDevice = copyOperateLimits
let resp = undefined
if (props?.type === 'device') {
resp = await saveMetadata(id as string, paramsDevice)
} else {
resp = await modify(id as string, params)
}
loading.value = false
if (resp.success) {
onlyMessage('导入成功')
if (hasVirtualRule.value) {
setTimeout(() => {
Modal.info({
title: '导入数据存在虚拟属性,请及时添加虚拟属性计算规则。',
okText: '确认'
})
}, 300)
}
}
if (props?.type === 'device') {
await instanceStore.refresh(id as string)
} else {
await productStore.getDetail(id as string)
}
metadataStore.set('importMetadata', true)
close();
} catch (e) {
loading.value = false
onlyMessage(e === 'error' ? '物模型数据不正确' : '上传json格式的物模型文件', 'error')
}
}
})
}
// const showProduct = computed(() => formModel.type === 'copy') // const showProduct = computed(() => formModel.type === 'copy')
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.import-content { .import-content {
background: rgb(236, 237, 238); background: rgb(236, 237, 238);
.import-tip { .import-tip {
padding: 10px; padding: 10px;
} }
} }
.upload-button { .upload-button {
width: 37px; width: 37px;
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;
margin: 0 -11px; margin: 0 -11px;
} }
</style> </style>

View File

@ -7,7 +7,7 @@
<j-radio-group <j-radio-group
button-style="solid" button-style="solid"
v-model:value="data.type" v-model:value="data.type"
@change="() => { getNetworkEcharts(data) }" @change="changeType"
> >
<j-radio-button value="bytesRead"> <j-radio-button value="bytesRead">
上行 上行
@ -109,7 +109,9 @@ const pickerTimeChange = (value: any) => {
data.value.time.type = undefined; data.value.time.type = undefined;
getNetworkEcharts(data.value); getNetworkEcharts(data.value);
}; };
const changeType = (value:any) =>{
getNetworkEcharts(data.value);
}
const getNetworkEcharts = async (val: any) => { const getNetworkEcharts = async (val: any) => {
loading.value = true; loading.value = true;
const resp: any = await dashboard(networkParams(val)); const resp: any = await dashboard(networkParams(val));
@ -215,6 +217,7 @@ watch(
() => data.value.time.type, () => data.value.time.type,
(value) => { (value) => {
if (value === undefined) return; if (value === undefined) return;
console.log(value);
const date = getTimeByType(value); const date = getTimeByType(value);
data.value.time.time = [dayjs(date), dayjs(new Date())]; data.value.time.time = [dayjs(date), dayjs(new Date())];

View File

@ -21,6 +21,8 @@
} }
}" }"
:fieldNames="{ key: 'id', title: 'name' }" :fieldNames="{ key: 'id', title: 'name' }"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
/> />
</div> </div>
</div> </div>

View File

@ -30,6 +30,8 @@
:fieldNames="{ title: 'name', key: 'id' }" :fieldNames="{ title: 'name', key: 'id' }"
:selectedKeys="[deptId]" :selectedKeys="[deptId]"
@select="onTreeSelect" @select="onTreeSelect"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
</j-tree> </j-tree>
<j-empty v-if="!deptTreeData.length" /> <j-empty v-if="!deptTreeData.length" />

View File

@ -38,6 +38,8 @@
:height='450' :height='450'
:virtual='true' :virtual='true'
@select='treeSelect' @select='treeSelect'
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ name, description }"> <template #title="{ name, description }">
<j-space> <j-space>

View File

@ -55,6 +55,8 @@
:height='450' :height='450'
:virtual='true' :virtual='true'
@select='treeSelect' @select='treeSelect'
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ name, description }"> <template #title="{ name, description }">
<j-space> <j-space>

View File

@ -1773,7 +1773,7 @@ function clickSave() {
} }
//-api id?clientId:appId //-api id?clientId:appId
if (params.provider === 'internal-standalone') { if (['internal-standalone', 'third-party'].includes(params.provider)) {
if (params.integrationModes.includes('apiClient')) { if (params.integrationModes.includes('apiClient')) {
params.id = params.apiClient.authConfig.oauth2.clientId; params.id = params.apiClient.authConfig.oauth2.clientId;
} }
@ -1806,7 +1806,6 @@ function clickSave() {
key: m.label, key: m.label,
})); }));
} }
loading.value = true; loading.value = true;
const request = routeQuery.id const request = routeQuery.id
? updateApp_api(routeQuery.id as string, params) ? updateApp_api(routeQuery.id as string, params)

View File

@ -33,6 +33,8 @@
:fieldNames="{ key: 'code', title: 'name' }" :fieldNames="{ key: 'code', title: 'name' }"
@check="treeCheck" @check="treeCheck"
:height="300" :height="300"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ name }"> <template #title="{ name }">
<span>{{ name }}</span> <span>{{ name }}</span>

View File

@ -10,11 +10,12 @@
<!-- 使用v-if用于解决异步加载数据后不展开的问题 --> <!-- 使用v-if用于解决异步加载数据后不展开的问题 -->
<j-tree <j-tree
v-if="leftData.treeData.length > 0" v-if="leftData.treeData.length > 0"
showLine
defaultExpandAll defaultExpandAll
:tree-data="leftData.treeData" :tree-data="leftData.treeData"
v-model:selectedKeys="leftData.selectedKeys" v-model:selectedKeys="leftData.selectedKeys"
@select="onSelect" @select="onSelect"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ dataRef }"> <template #title="{ dataRef }">
<div <div

View File

@ -30,6 +30,8 @@
v-model:selected-keys="selectedKeys" v-model:selected-keys="selectedKeys"
v-model:expandedKeys="expandedKeys" v-model:expandedKeys="expandedKeys"
:fieldNames="{ key: 'id' }" :fieldNames="{ key: 'id' }"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ name, data }"> <template #title="{ name, data }">
<div class='department-tree-item-content'> <div class='department-tree-item-content'>

View File

@ -13,7 +13,6 @@
<j-scrollbar> <j-scrollbar>
<j-tree <j-tree
v-if="treeData.length !== 0" v-if="treeData.length !== 0"
show-line
defaultExpandAll defaultExpandAll
multiple multiple
draggable draggable
@ -22,6 +21,8 @@
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
@drop="onDrop" @drop="onDrop"
@dragend="onDragend" @dragend="onDragend"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="row"> <template #title="row">
<div class="tree-content"> <div class="tree-content">

View File

@ -69,9 +69,11 @@ onMounted(() => {
iconUrl: iconMap.get(item.id), iconUrl: iconMap.get(item.id),
}; };
}); });
emit('update:value', options.value?.[0]?.value); if(!props.value){
emit('update:name', options.value?.[0]?.label); emit('update:value', options.value?.[0]?.value);
emit('change', {label: options.value?.[0]?.label, value: options.value?.[0]?.value}); emit('update:name', options.value?.[0]?.label);
emit('change', {label: options.value?.[0]?.label, value: options.value?.[0]?.value});
}
} }
loading.value = false; loading.value = false;
}); });

View File

@ -7,8 +7,9 @@
:tree-data="treeData" :tree-data="treeData"
@select="clickSelectItem" @select="clickSelectItem"
v-model:selected-keys="selectedKeys" v-model:selected-keys="selectedKeys"
showLine
class="left-tree-container" class="left-tree-container"
:showLine="{ showLeafIcon: false }"
:show-icon="true"
> >
<template #title="{ name }"> <template #title="{ name }">
{{ name }} {{ name }}

View File

@ -137,7 +137,7 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="basis.recommend" class="bottom"> <div v-if="basis.recommend === 'true'" class="bottom">
<div class="view"> <div class="view">
JETLINKS团队全新力作可视化大屏系统 JETLINKS团队全新力作可视化大屏系统
</div> </div>

View File

@ -94,7 +94,7 @@ export default defineConfig(({ mode}) => {
[env.VITE_APP_BASE_API]: { [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://192.168.32.244:8881',
// target: 'http://192.168.32.163:8844', //张季本地 // target: 'http://192.168.32.163:8844', //张季本地
// target: 'http://120.77.179.54:8844', // 120测试 // target: 'http://120.77.179.54:8844', // 120测试
target: 'http://192.168.33.46:8844', // 本地开发环境 target: 'http://192.168.33.46:8844', // 本地开发环境
ws: 'ws://192.168.33.46:8844', ws: 'ws://192.168.33.46:8844',